diff --git a/lua/autorun/wire_load.lua b/lua/autorun/wire_load.lua index 0f070912b0..ca6ea3ff0c 100644 --- a/lua/autorun/wire_load.lua +++ b/lua/autorun/wire_load.lua @@ -87,6 +87,7 @@ if SERVER then include("wire/server/modelplug.lua") include("wire/server/debuggerlib.lua") include("wire/server/sents_registry.lua") + include("wire/server/wire_map_interface.lua") if CreateConVar("wire_force_workshop", "1", FCVAR_ARCHIVE, "Should Wire force all clients to download the Workshop edition of Wire, for models? (requires restart to disable)"):GetBool() then if select(2, WireLib.GetVersion()):find("Workshop", 1, true) then diff --git a/lua/entities/info_wiremapinterface/convert.lua b/lua/entities/info_wiremapinterface/convert.lua index 0180b4dabf..84c1e4ecd6 100644 --- a/lua/entities/info_wiremapinterface/convert.lua +++ b/lua/entities/info_wiremapinterface/convert.lua @@ -2,142 +2,280 @@ -- Per type converting functions for -- converting from map inputs to wire outputs. (String to Value) -local MapToWireTypes = { - [0] = {"NORMAL", function(str) -- Number, default - return tonumber(str) or 0 - end}, - - [1] = {"NORMAL", function(self, ent, I) -- switches between 0 and 1 each call, useful for toggling. - if (!IsValid(self) or !IsValid(ent) or !I) then return 0 end - - self.WireOutputToggle = self.WireOutputToggle or {} - self.WireOutputToggle[ent] = self.WireOutputToggle[ent] or {} - self.WireOutputToggle[ent][I] = !self.WireOutputToggle[ent][I] - - return self.WireOutputToggle[ent][I] and 1 or 0 - end, true}, - - [2] = {"STRING", function(str) -- String - return str or "" - end}, - - [3] = {"VECTOR2", function(str) -- 2D Vector - local x, y = unpack(string.Explode(" ", str or "")) - x = tonumber(x) or 0 - y = tonumber(y) or 0 - - return {x, y} - end}, - - [4] = {"VECTOR", function(str) -- 3D Vector - local x, y, z = unpack(string.Explode(" ", str or "")) - x = tonumber(x) or 0 - y = tonumber(y) or 0 - z = tonumber(z) or 0 - - return Vector(x, y, z) - end}, - - [5] = {"VECTOR4", function(str) -- 4D Vector - local x, y, z, w = unpack(string.Explode(" ", str or "")) - x = tonumber(x) or 0 - y = tonumber(y) or 0 - z = tonumber(z) or 0 - w = tonumber(w) or 0 - - return {x, y, z, w} - end}, - - [6] = {"ANGLE", function(str) -- Angle - local p, y, r = unpack(string.Explode(" ", str or "")) - p = tonumber(p) or 0 - y = tonumber(y) or 0 - r = tonumber(r) or 0 - - return Angle(p, y, r) - end}, - - [7] = {"ENTITY", function(val) -- Entity - return Entity(tonumber(val) or 0) or NULL - end}, - - [8] = {"ARRAY", function(str) -- Array/Table - return string.Explode(" ", str or "") - end}, -} --- Per type converting functions for --- converting from wire inputs to map outputs. (Value to String) -local WireToMapTypes = { - [0] = {"NORMAL", function(val) -- Number, default - return tostring(val or 0) - end}, +local function isEqualList(_, listA, listB) + if listA == listB then + return true + end + + if not listA or not listB then + return false + end + + if #listA ~= #listB then + return false + end + + for i, va in ipairs(listA) do + local vb = listB[i] + + if va ~= vb then + return false + end + end + + return true +end + +local function isEqualGeneric(_, varA, varB) + return varA == varB +end + +local function isEqualEntity(_, entA, entB) + if not IsValid(entA) or not IsValid(entB) then + return false + end + + return entA == entB +end + +local g_supportedTypesById = { + [0] = { -- Number, default + wireType = "NORMAL", + + toHammer = function(_, wireValue) + wireValue = tonumber(wireValue or 0) or 0 + return string.format("%.20g", wireValue) + end, + + toWire = function(_, hammerValue) + return tonumber(hammerValue or 0) or 0 + end, + + wireIsEqual = function(_, wireValueA, wireValueB) + wireValueA = tonumber(wireValueA) + wireValueB = tonumber(wireValueB) + + if not wireValueA then + return false + end + + if not wireValueB then + return false + end + + return wireValueA == wireValueB + end + }, + + [1] = { -- Number, toggle + wireType = "NORMAL", + + -- Wire Input: Trigger Hammer output only if true. Does not pass the input value from Wire to Hammer. + -- Wire Output: Toggle the Wire Output when triggered by Hammer. Does not pass the input value from Hammer or Wire. + isToggle = true, + + toHammer = function(_, wireValue) -- Return a boolean, 0 = false, 1 = true, useful for toggling. It triggers Hammer output only if true. + return tobool(wireValue) + end, + + toWire = function(_, hammerValue) -- Is being switched between 0 and 1 each call from Hammer input. + return tobool(hammerValue) and 1 or 0 + end, + + wireIsEqual = isEqualGeneric, + }, + + [2] = { -- String + wireType = "STRING", + + toHammer = function(_, wireValue) + return tostring(wireValue or "") + end, + + toWire = function(_, hammerValue) + return hammerValue or "" + end, + + wireIsEqual = isEqualGeneric, + }, + + [3] = { -- 2D Vector + wireType = "VECTOR2", + + toHammer = function(_, wireValue) + wireValue = wireValue or {0, 0} + + local x = tonumber(wireValue[1] or 0) or 0 + local y = tonumber(wireValue[2] or 0) or 0 - [1] = {"NORMAL", function(val) -- Return a boolean, 0 = false, 1 = true, useful for toggling. - return (tonumber(val) or 0) > 0 - end, true}, + return string.format("%.20g %.20g", x, y) + end, - [2] = {"STRING", function(val) -- String - return val or "" - end}, + toWire = function(_, hammerValue) + local x, y = unpack(string.Explode(" ", hammerValue or "")) - [3] = {"VECTOR2", function(val) -- 2D Vector - val = val or {0, 0} + x = tonumber(x or 0) or 0 + y = tonumber(y or 0) or 0 - local x = math.Round(val[1] or 0) - local y = math.Round(val[2] or 0) + return {x, y} + end, - return x.." "..y - end}, + wireIsEqual = isEqualList, + }, - [4] = {"VECTOR", function(val) -- 3D Vector - val = val or Vector(0, 0, 0) + [4] = { -- 3D Vector + wireType = "VECTOR", - local x = math.Round(val.x or 0) - local y = math.Round(val.y or 0) - local z = math.Round(val.z or 0) + toHammer = function(_, wireValue) + wireValue = wireValue or Vector(0, 0, 0) - return x.." "..y.." "..z - end}, + local x = tonumber(wireValue.x or 0) or 0 + local y = tonumber(wireValue.y or 0) or 0 + local z = tonumber(wireValue.z or 0) or 0 - [5] = {"VECTOR4", function(val) --4D Vector - val = val or {0, 0, 0, 0} + return string.format("%.20g %.20g %.20g", x, y, z) + end, - local x = math.Round(val[1] or 0) - local y = math.Round(val[2] or 0) - local z = math.Round(val[3] or 0) - local w = math.Round(val[4] or 0) + toWire = function(_, hammerValue) + local x, y, z = unpack(string.Explode(" ", hammerValue or "")) - return x.." "..y.." "..z.." "..w - end}, + x = tonumber(x or 0) or 0 + y = tonumber(y or 0) or 0 + z = tonumber(z or 0) or 0 - [6] = {"ANGLE", function(val) -- Angle - val = val or Angle(0, 0, 0) + return Vector(x, y, z) + end, - local p = math.Round(val.p or 0) - local y = math.Round(val.y or 0) - local r = math.Round(val.r or 0) + wireIsEqual = isEqualGeneric, + }, - return p.." "..y.." "..r - end}, + [5] = { -- 4D Vector + wireType = "VECTOR4", - [7] = {"ENTITY", function(val) -- Entity - if (!IsValid(val)) then return "0" end + toHammer = function(_, wireValue) + val = val or {0, 0, 0, 0} - return tostring(val:EntIndex()) - end}, + local x = tonumber(val[1] or 0) or 0 + local y = tonumber(val[2] or 0) or 0 + local z = tonumber(val[3] or 0) or 0 + local w = tonumber(val[4] or 0) or 0 - [8] = {"ARRAY", function(val) -- Array/Table - return table.concat(val or {}, " ") - end}, + return string.format("%.20g %.20g %.20g %.20g", x, y, z, w) + end, + + toWire = function(_, hammerValue) + local x, y, z, w = unpack(string.Explode(" ", hammerValue or "")) + + x = tonumber(x or 0) or 0 + y = tonumber(y or 0) or 0 + z = tonumber(z or 0) or 0 + w = tonumber(w or 0) or 0 + + return {x, y, z, w} + end, + + wireIsEqual = isEqualList, + }, + + [6] = { -- Angle + wireType = "ANGLE", + + toHammer = function(_, wireValue) + wireValue = wireValue or Angle(0, 0, 0) + + local p = tonumber(wireValue.p or 0) or 0 + local y = tonumber(wireValue.y or 0) or 0 + local r = tonumber(wireValue.r or 0) or 0 + + return string.format("%.20g %.20g %.20g", p, y, r) + end, + + toWire = function(_, hammerValue) + local p, y, r = unpack(string.Explode(" ", hammerValue or "")) + p = tonumber(p or 0) or 0 + y = tonumber(y or 0) or 0 + r = tonumber(r or 0) or 0 + + return Angle(p, y, r) + end, + + wireIsEqual = isEqualGeneric, + }, + + [7] = { -- Entity + wireType = "ENTITY", + + toHammer = function(_, wireValue) + if not IsValid(wireValue) then return "0" end + return tostring(wireValue:EntIndex()) + end, + + toWire = function(self, hammerValue) + local id = tonumber(hammerValue or 0) + + if id ~= nil then + if id == 0 then + return game.GetWorld() + end + + return ents.GetByIndex(id) + end + + return self:GetFirstEntityByTargetnameOrClass(hammerValue) or NULL + end, + + wireIsEqual = isEqualEntity, + }, + + [8] = { -- Array/Table + wireType = "ARRAY", + + toHammer = function(_, wireValue) + return table.concat(wireValue or {}, " ") + end, + + toWire = function(_, hammerValue) + return string.Explode(" ", hammerValue or "") + end, + + wireIsEqual = isEqualList, + }, } --- Converting functions -function ENT:Convert_MapToWire(n) - local typetab = MapToWireTypes[n or 0] or MapToWireTypes[0] - return typetab[1], typetab[2], typetab[3] +function ENT:GetMapToWireConverter(typeId) + local typeData = g_supportedTypesById[typeId or 0] or g_supportedTypesById[0] + if not typeData then + return + end + + return typeData.toWire, typeData.isToggle or false +end + +function ENT:GetWireToMapConverter(typeId) + local typeData = g_supportedTypesById[typeId or 0] or g_supportedTypesById[0] + if not typeData then + return + end + + return typeData.toHammer, typeData.isToggle or false +end + +function ENT:IsEqualWireValue(typeId, wireValueA, wireValueB) + local typeData = g_supportedTypesById[typeId or 0] or g_supportedTypesById[0] + if not typeData then + return false + end + + return typeData.wireIsEqual(self, wireValueA, wireValueB) end -function ENT:Convert_WireToMap(n) - local typetab = WireToMapTypes[n or 0] or WireToMapTypes[0] - return typetab[1], typetab[2], typetab[3] + +function ENT:GetWireTypenameByTypeId(typeId) + local typeData = g_supportedTypesById[typeId or 0] or g_supportedTypesById[0] + if not typeData then + return + end + + return typeData.wireType end + diff --git a/lua/entities/info_wiremapinterface/entitycontrol.lua b/lua/entities/info_wiremapinterface/entitycontrol.lua index 9e9bf31286..99ff024b8f 100644 --- a/lua/entities/info_wiremapinterface/entitycontrol.lua +++ b/lua/entities/info_wiremapinterface/entitycontrol.lua @@ -1,254 +1,302 @@ -- This part of the wire map interface entity controls -- the adding and removing of its in-/outputs entities. +function ENT:HandleWireEntNameUpdated() + if not self.WireEntNameUpdated then + return + end --- The removing function --- Its for removing all the wiremod stuff from unused entities. -local function RemoveWire(Entity, SendToCL) - if (not IsValid(Entity)) then return end - - Wire_Remove(Entity, not SendToCL) + self:AddEntitiesByName(self.WireEntName) + self.WireEntNameUpdated = nil +end - local self = Entity._WireMapInterfaceEnt - if (IsValid(self)) then - self.WireEntsCount = math.max(self.WireEntsCount - 1, 0) - if (self.WireEnts) then - self.WireEnts[Entity] = nil - end - if (self.WireOutputToggle) then - self.WireOutputToggle[Entity] = nil - end - if (self.Wired) then - self.Wired[Entity] = nil - end +function ENT:HandleWireEntsUpdated() + if not self.WireEntsUpdated then + return end - if (not SendToCL) then return end + local ready = false - Entity:_RemoveOverrides() - WireLib._RemoveWire(Entity:EntIndex(), true) -- Remove entity from the list, so it doesn't count as a wire able entity anymore. + if self.WireEntsRemoved then + self:TriggerHammerOutputSafe("OnWireEntsRemoved", self) + self.WireEntsRemoved = nil + ready = true + end - umsg.Start("WireMapInterfaceEnt") - umsg.Entity(Entity) - umsg.Char(-1) - umsg.End() + if self.WireEntsAdded then + if self:GetWiredEntityCount() > 0 then + -- Avoid triggering "created" event if the list if entity is actually empty. + self:TriggerHammerOutputSafe("OnWireEntsCreated", self) + ready = true + end - Entity:RemoveCallOnRemove("WireMapInterface_OnRemove") - if (table.IsEmpty(Entity.OnDieFunctions)) then - Entity.OnDieFunctions = nil + self.WireEntsAdded = nil end -end -function ENT:Timedpairs(name, tab, steps, cb, endcb, ...) - if (table.IsEmpty(tab)) then return end + if ready then + -- Only trigger "ready" if one of the other both had been triggered. + self:TriggerHammerOutputSafe("OnWireEntsReady", self) + end - local name = self:EntIndex().."_"..tostring(name) - self.TimedpairsTable = self.TimedpairsTable or {} + self.NextNetworkTime = math.max(self.NextNetworkTime or 0, CurTime() + self.MIN_THINK_TIME * 2) + self.WireEntsUpdated = nil +end - WireLib.Timedpairs(name, tab, steps, function(...) +-- Entity add functions +function ENT:AddEntitiesByName(name) + local entities = self:GetEntitiesByTargetnameOrClass(name) + self:AddEntitiesByTable(entities) +end - if (IsValid(self)) then - self.TimedpairsTable[name] = true - end - return cb(...) +function ENT:AddEntitiesByTable(entitiesToAdd) + if not entitiesToAdd then return end - end, function(...) + local tmp = {} - if (IsValid(self)) then - self.TimedpairsTable[name] = nil + for key, value in pairs(entitiesToAdd) do + if isentity(key) and IsValid(key) then + tmp[key] = key end - if (endcb) then - return endcb(...) + + if isentity(value) and IsValid(value) then + tmp[value] = value end + end - end, ...) + for _, wireEnt in pairs(tmp) do + self:AddSingleEntity(wireEnt) + end end -local function CallOnEnd(self, AddedEnts) - if (not IsValid(self)) then return end +function ENT:AddSingleEntity(wireEnt) + if not self:IsWireableEntity(wireEnt) then + return + end - self.WirePortsChanged = true - self:GiveWireInterfeceClient(nil, AddedEnts) - self:TriggerOutput("onwireentscreated", self) - self:TriggerOutput("onwireentsready", self) -end + local hardLimit = self.WireEntsHardLimit or 0 -function ENT:GiveWireInterfece(EntsToAdd) - if (not EntsToAdd) then return end - if (table.IsEmpty(EntsToAdd) or not self.WirePortsChanged) then return end - local AddedEnts = {} - self:UpdateData() + if hardLimit >= self:GetMaxSubEntities() * 3 then + -- Stop adding more things until cleaned up. + return + end - self:Timedpairs("WireMapInterface_Adding", EntsToAdd, 1, function(obj1, obj2, self) - if (not IsValid(self)) then return false end -- Stop loop when the entity gets removed. - if (self.WirePortsChanged) then - self:TriggerOutput("onwireentsstartchanging", self) - end - self.WirePortsChanged = nil + local id = wireEnt:EntIndex() + local wireEnts = self.WireEntsRegister - local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + local item = wireEnts[id] + local oldWireEnt = item and item.ent - local Ent, Func = self:AddSingleEntity(Entity, CallOnEnd, AddedEnts) - if (Ent == "limid_exceeded") then return false end -- Stop loop when maximum got exceeded - if (not IsValid(Ent) or not Func) then return end + local isInList = IsValid(oldWireEnt) and oldWireEnt == wireEnt - AddedEnts[Ent] = Func - end, function(k, v, self) CallOnEnd(self, AddedEnts) end, self) -end + if isInList and wireEnt._WireMapInterfaceEnt_HasPorts then + return + end -function ENT:GiveWireInterfeceClient(ply, EntsToAdd) - if (not self.WireEnts) then return end + if not isInList then + if not self.WireEntsUpdated then + self:TriggerHammerOutputSafe("OnWireEntsStartChanging", self) + end - self:Timedpairs((IsValid(ply) and (ply:EntIndex().."") or "").."WireMapInterface_Adding_CL", EntsToAdd or self.WireEnts, 1, function(Entity, Func, self) - if (not IsValid(self)) then return false end -- Stop loop when the entity gets removed. - if (not IsValid(Entity)) then return end - if (not self:IsWireableEntity(Entity)) then return end + self:OverrideEnt(wireEnt) + end - Func(self, Entity, ply, not IsValid(ply)) - end, nil, self) -end + if wireEnt._WMI_AddPorts then + wireEnt:_WMI_AddPorts(self.WireInputRegister, self.WireOutputRegister) + end + wireEnts[id] = { + ent = wireEnt, + cid = wireEnt:GetCreationID(), + } --- Entity add functions -function ENT:AddEntitiesByName(Name) - Name = tostring(Name or "") - if (Name == "") then return end + if not isInList then + self.WireEntsHardLimit = hardLimit + 1 + + self.WireEntsUpdated = true + self.WireEntsAdded = true + + self:RequestNetworkEntities() + self.WireEntsSorted = nil + self.WireEntsCount = nil + end - self:AddEntitiesByTable(ents.FindByName(Name)) + self:ApplyWireOutputBufferSingle(wireEnt) + + -- Sometimes CurTime() may get lag compensated and "time travels" when the entity is being added by duplicator/gun trigger. + -- So we delay the think call past the predicted event time to avoid any race conditions. + self:NextThink(CurTime() + self.MIN_THINK_TIME) + return wireEnt end -function ENT:AddEntitiesByTable(Table) - if (not Table) then return end +-- Entity remove functions +function ENT:RemoveAllEntities() + local wireEnts = self.WireEntsRegister + + for id, item in pairs(wireEnts) do + self:RemoveSingleEntity(item.ent) + wireEnts[id] = nil + end - self:GiveWireInterfece(Table) + self.WireEntsSorted = nil + self.WireEntsCount = nil + self.WireEntsHardLimit = nil end -local function AddSingleEntityCL(self, Entity, ply, SendToAll) - if (not IsValid(Entity)) then return end - if (not IsValid(self)) then return end - if (not self.WireEnts[Entity]) then return end - if (not SendToAll and not IsValid(ply)) then return end - - if (SendToAll) then - umsg.Start("WireMapInterfaceEnt") - else - umsg.Start("WireMapInterfaceEnt", ply) - end - umsg.Entity(Entity) - umsg.Char(self.flags % 64) - -- Allow valid spawnflags only. - umsg.End() +function ENT:RemoveEntitiesByName(name) + local entities = self:GetEntitiesByTargetnameOrClass(name) + self:RemoveEntitiesByTable(entities) end -function ENT:AddSingleEntity(Entity, callOnEnd, AddedEnts) - if (not IsValid(Entity)) then return end - if (not self:IsWireableEntity(Entity)) then return end - if (not self:CheckEntLimid(callOnEnd, AddedEnts)) then return "limid_exceeded" end +function ENT:RemoveEntitiesByTable(entitiesToRemove) + if not entitiesToRemove then return end - if (IsValid(Entity._WireMapInterfaceEnt)) then - RemoveWire(Entity, true) - end + local tmp = {} - self:OverrideEnt(Entity) - Entity:CallOnRemove("WireMapInterface_OnRemove", RemoveWire) + for key, value in pairs(entitiesToRemove) do + if isentity(key) and IsValid(key) then + tmp[key] = key + end - if (self.Inames) then - Entity.Inputs = WireLib.CreateSpecialInputs(Entity, self.Inames, self.Itypes, self.Idescs) + if isentity(value) and IsValid(value) then + tmp[value] = value + end end - if (self.Onames) then - Entity.Outputs = WireLib.CreateSpecialOutputs(Entity, self.Onames, self.Otypes, self.Odescs) + + for _, wireEnt in pairs(tmp) do + self:RemoveSingleEntity(wireEnt) end +end - self.WireEnts = self.WireEnts or {} - self.WireEnts[Entity] = AddSingleEntityCL +function ENT:RemoveSingleEntity(wireEnt) + if not IsValid(wireEnt) then return end + if not IsValid(wireEnt._WireMapInterfaceEnt) then return end + if wireEnt._WireMapInterfaceEnt ~= self then return end - return Entity, AddSingleEntityCL + local id = wireEnt:EntIndex() + local wireEnts = self.WireEntsRegister + + if not wireEnts[id] then + return + end + + if wireEnt._WMI_RemoveOverrides then + wireEnt:_WMI_RemoveOverrides(self) + end + + wireEnts[id] = nil + self.WireEntsSorted = nil + self.WireEntsCount = nil end +function ENT:UnregisterWireEntityInternal(wireEnt) + if not IsValid(wireEnt) then + return + end + + local wireEnts = self.WireEntsRegister + local id = wireEnt:EntIndex() --- Entity remove functions -function ENT:RemoveAllEntities(callback) - for name, _ in pairs(self.TimedpairsTable or {}) do - WireLib.TimedpairsStop(name) + if not wireEnts[id] then + return end - self.WirePortsChanged = true + if not self.WireEntsUpdated then + self:TriggerHammerOutputSafe("OnWireEntsStartChanging", self) + end - self:RemoveEntitiesByTable(self.WireEnts, callback) - self.WireEntsCount = 0 -end + wireEnts[id] = nil + wireEnt._WireMapInterfaceEnt = nil + local hardLimit = self.WireEntsHardLimit or 0 + self.WireEntsHardLimit = math.max(hardLimit - 1, 0) -function ENT:RemoveEntitiesByName(Name, callback) - Name = tostring(Name or "") - if (Name == "") then return end + self.WireEntsUpdated = true + self.WireEntsRemoved = true - self:RemoveEntitiesByTable(ents.FindByName(Name), callback) + self:RequestNetworkEntities() + self.WireEntsSorted = nil + self.WireEntsCount = nil + + -- Sometimes CurTime() may get lag compensated and "time travels" when the entity is being destoryed by gun fire. + -- So we delay the think past the predicted event time to avoid any race conditions. + self:NextThink(CurTime() + self.MIN_THINK_TIME) end -function ENT:RemoveEntitiesByTable(Table, callback) - if (not Table) then return end - if (table.IsEmpty(Table) or not self.WirePortsChanged) then return end +function ENT:SanitizeAndSortWiredEntities() + local wireEnts = self.WireEntsRegister - local Removed = nil - self:Timedpairs("WireMapInterface_Removing", Table, 1, function(obj1, obj2, self) - local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + if not wireEnts or table.IsEmpty(wireEnts) then + self.WireEntsSorted = {} + self.WireEntsCount = 0 + self.WireEntsHardLimit = nil - if (not IsValid(Entity)) then return end - if (not IsValid(Entity._WireMapInterfaceEnt)) then return end - if (Entity._WireMapInterfaceEnt ~= self) then return end - if (self and self.WirePortsChanged) then - self:TriggerOutput("onwireentsstartchanging", self) - self.WirePortsChanged = nil - end + return + end + + for id, item in pairs(wireEnts) do + local wireEnt = item.ent + + if not self:IsWireableEntity(wireEnt) or + not wireEnt._IsWireMapInterfaceSubEntity or + not wireEnt._WireMapInterfaceEnt_Data or + not wireEnt._WMI_GetSpawnId + then + -- Remove invalid/broken wire entities. - RemoveWire(Entity, true) - Removed = true - end, - function(k, v, self, callback) - if (not IsValid(self)) then return end - self.WirePortsChanged = true + if IsValid(wireEnt) then + if wireEnt._WMI_RemoveOverrides then + wireEnt:_WMI_RemoveOverrides(self) + end + end - if (callback) then - callback(self, Removed) + wireEnts[id] = nil end + end - if (not Removed) then return end - self:TriggerOutput("onwireentsremoved", self) - self:TriggerOutput("onwireentsready", self) - end, self, callback) + local count = 0 + local wireEntsSorted = {} -end + for id, item in SortedPairsByMemberValue(wireEnts, "cid", false) do + local wireEnt = item.ent -function ENT:RemoveSingleEntity(Entity) - if (not IsValid(Entity)) then return end - if (not IsValid(Entity._WireMapInterfaceEnt)) then return end - if (Entity._WireMapInterfaceEnt ~= self) then return end + if self:CheckEntLimit(count, wireEnt) then + count = count + 1 + wireEntsSorted[count] = wireEnt + else + -- Remove newest wire entities first if limit is exhausted. + if wireEnt._WMI_RemoveOverrides then + wireEnt:_WMI_RemoveOverrides(self) + end - RemoveWire(Entity, true) - self:TriggerOutput("onwireentsremoved", self) - self:TriggerOutput("onwireentsready", self) + wireEnts[id] = nil + end + end + + self.WireEntsSorted = wireEntsSorted + self.WireEntsCount = count + self.WireEntsHardLimit = nil end +function ENT:GetWiredEntities() + if not self.WireEntsSorted then + self:SanitizeAndSortWiredEntities() + end + return self.WireEntsSorted +end -function ENT:GetWiredEntities() - return table.Copy(self.WireEnts or {}) +function ENT:GetWiredEntityCount() + if not self.WireEntsCount then + self:SanitizeAndSortWiredEntities() + end + + return self.WireEntsCount end -function ENT:SetWiredEntities(Table) - if (not Table) then return end +function ENT:SetWiredEntities(entities) + if not entities then return end - local Ents = {} - local Count = 0 - for obj1, obj2 in pairs(Table) do -- Filter invalid stuff out! - local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) - if (IsValid(Entity)) then - Count = Count + 1 - Ents[Count] = Entity - end - end - self:RemoveAllEntities(function(self) - self:AddEntitiesByTable(Ents) - end) + self:RemoveAllEntities() + self:AddEntitiesByTable(entities) end + diff --git a/lua/entities/info_wiremapinterface/entityoverride.lua b/lua/entities/info_wiremapinterface/entityoverride.lua index 7962bf7379..55524255c2 100644 --- a/lua/entities/info_wiremapinterface/entityoverride.lua +++ b/lua/entities/info_wiremapinterface/entityoverride.lua @@ -1,71 +1,868 @@ -- Stuff that the entity gets for its wire stuff. +local WireLib = WireLib +local WireMapInterfaceLookup = WireLib.WireMapInterfaceLookup + local WIREENT = {} + +local g_wirelinkName = "wirelink" +local g_wireTools = { + "wire", + "wire_adv", + "wire_debugger", + "wire_wirelink", + "multi_wire", +} + +local g_memberBlacklist = { + _IsWireMapInterfaceSubEntity = true, + _WireMapInterfaceEnt_TmpPorts = true, + _WireMapInterfaceEnt_HasPorts = true, + _WireMapInterfaceEnt_SpawnId = true, + _WireMapInterfaceEnt_MapId = true, + _WireMapInterfaceEnt_Data = true, + _WireMapInterfaceEnt = true, + _WMI_OverrideEnt = true, + _WMI_RemoveOverrides = true, + PhysgunDisabled = true, + m_tblToolsAllowed = true, + Inputs = true, + Outputs = true, + IsWire = true, + WireDebugName = true, +} + +function WIREENT:_WMI_GetConnectedWireInputSource(name) + local wireinputs = self.Inputs + if not wireinputs then return nil end + + local wireinput = wireinputs[name] + if not wireinput then return nil end + if not IsValid(wireinput.Src) then return nil end + + return wireinput.Src +end + +function WIREENT:_WMI_GetConnectedWireWirelinkSource() + local wireoutputs = self.Outputs + if not wireoutputs then return nil end + + local wireoutput = wireoutputs[g_wirelinkName] + if not wireoutput then return nil end + if not wireoutput.Connected then return nil end + + for key, connectedItem in ipairs(wireoutput.Connected) do + if IsValid(connectedItem.Entity) then + return connectedItem.Entity + end + end + + return nil +end + +function WIREENT:_WMI_IsConnectedWireInput(name) + if not self:_WMI_GetConnectedWireInputSource(name) then return false end + return true +end + +function WIREENT:_WMI_IsConnectedWireOutput(name) + local wireoutputs = self.Outputs + if not wireoutputs then return false end + + local wireoutput = wireoutputs[name] + if not wireoutput then return false end + if not wireoutput.Connected then return false end + + for key, connectedItem in ipairs(wireoutput.Connected) do + if IsValid(connectedItem.Entity) then + return true + end + end + + return false +end + +function WIREENT:_WMI_IsConnectedWirelink() + if not self.extended then + -- wirelink had not been created yet + return false + end + + if self:_WMI_IsConnectedWireOutput(g_wirelinkName) then + -- wirelink had been connected via Wire Tool + return true + end + + return false +end + -- Trigger wire input -function WIREENT:TriggerInput(name, value, ...) - if (not name or (name == "") or not value) then return end +function WIREENT:TriggerInput(name, value, ext, ...) + if not name then return end + if name == "" then return end + if not value then return end + + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then return end + + local interfaceEnt = self._WireMapInterfaceEnt + if not IsValid(interfaceEnt) then return end + if not interfaceEnt.TriggerWireInput then return end + + -- Ensure the Wirelink is actually physically connected. Otherwise it is not possible to detect disconnections. + -- It is also needed for the custom ownership management this entity has. This entity may change Wiremod owner depeding on the ownership other connected entities. + -- So using E2 with E:wirelink() does not work for this, as it would bypass prop protection or other protection the map/server has for this map entity. + local isWirelink = self:_WMI_IsConnectedWirelink() and ext and ext.wirelink + local wired = self:_WMI_IsConnectedWireInput(name) or isWirelink + + if not isWirelink then + local realValueBuffer = wmidata.realValueBuffer or {} + wmidata.realValueBuffer = realValueBuffer + + realValueBuffer[name] = value + end + + local device = self:_WMI_GetInputDevice(isWirelink and g_wirelinkName or name) + + if not isWirelink then + wmidata.lastDirectInputDevice = device + else + wmidata.lastDirectInputDevice = nil + end + + interfaceEnt:TriggerWireInput(name, value, wired, self) +end + +function WIREENT:_WMI_GetDirectLinkedInputValue(name) + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return nil + end + + local realValueBuffer = wmidata.realValueBuffer + if not realValueBuffer then + return nil + end + + return realValueBuffer[name] +end + +function WIREENT:_WMI_GetInputDevice(name) + if not name then + local device = self:_WMI_GetConnectedWireWirelinkSource() + + if not IsValid(device) then + device = self:_WMI_GetLastDirectInputDevice() + end + + return device + end + + if name == g_wirelinkName then + return self:_WMI_GetConnectedWireWirelinkSource() + end + + return self:_WMI_GetConnectedWireInputSource(name) +end + +function WIREENT:_WMI_GetLastDirectInputDevice() + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return nil + end + + local device = wmidata.lastDirectInputDevice + if not IsValid(device) then + return nil + end + + return device +end + +function WIREENT:_WMI_FindWireMapInterfaceEnt(interface) + if not interface then + return nil + end + + local mapId = interface.mapId + local spawnId = interface.spawnId + + if not spawnId then + return nil + end + + mapId = tonumber(mapId) + + if not WireLib.WireMapInterfaceValidateId(mapId) then + return nil + end + + local interfaceEnt = ents.GetMapCreatedEntity(mapId) + if not IsValid(interfaceEnt) then + return nil + end + + if not interfaceEnt.IsWireMapInterface then + return nil + end + + if not interfaceEnt:ValidateDupedMapId(mapId, spawnId) then + return nil + end + + return interfaceEnt +end + +local g_functionCallNesting_GetPlayer = 0 - local Entity = self._WireMapInterfaceEnt - if (not IsValid(Entity)) then return end - if (not Entity.TriggerWireInput) then return end +function WIREENT:GetPlayer(...) + -- Fake the owner of the map owned entity if we don't have one. It is needed for E2 wirelink to work. + -- It uses the owner of the contected entity e.g. the E2 chip. - local Input = self.Inputs[name] or {} - Entity:TriggerWireInput(name, value, IsValid(Input.Src) == true, self, ...) + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return nil + end + + if wmidata.oldMethods.GetPlayer then + local ply = wmidata.oldMethods.GetPlayer(self, ...) + + if IsValid(ply) then + return ply + end + end + + local dupe = wmidata.dupe + if dupe and IsValid(dupe.owner) then + return dupe.owner + end + + if not self:_WMI_IsConnectedWirelink() then + -- Owner fake is exclusive to wirelink + return nil + end + + local device = self:_WMI_GetInputDevice() + if not IsValid(device) or device == self then + return nil + end + + if g_functionCallNesting_GetPlayer > 5 then + -- prevent recursive loop + return nil + end + + g_functionCallNesting_GetPlayer = g_functionCallNesting_GetPlayer + 1 + + local fakeOwner = WireLib.GetOwner(device) + + g_functionCallNesting_GetPlayer = math.max(g_functionCallNesting_GetPlayer - 1, 0) + + return fakeOwner +end + +function WIREENT:OnEntityCopyTableFinish(dupedata, ...) + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return + end + + if wmidata.oldMethods.OnEntityCopyTableFinish then + wmidata.oldMethods.OnEntityCopyTableFinish(self, dupedata, ...) + end + + -- Prevent weird stuff from happening on garry dupe/save + dupedata.Inputs = nil + dupedata.Outputs = nil + dupedata.IsWire = nil + + local oldMembers = wmidata.oldMembers or {} + for k, v in pairs(oldMembers) do + dupedata[k] = nil + end + + local oldSettings = wmidata.oldSettings or {} + for k, v in pairs(oldSettings) do + dupedata[k] = nil + end + + for k, v in pairs(g_memberBlacklist) do + dupedata[k] = nil + end +end + +function WIREENT:PreEntityCopy(...) + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return + end + + if wmidata.oldMethods.PreEntityCopy then + wmidata.oldMethods.PreEntityCopy(self, ...) + end + + duplicator.ClearEntityModifier(self, "WireDupeInfo") + duplicator.ClearEntityModifier(self, "WireMapInterfaceEntDupeInfo") + + local dupeData = self:_WMI_BuildDupeData() + + if dupeData.wireDupeInfo then + duplicator.StoreEntityModifier(self, "WireDupeInfo", dupeData.wireDupeInfo) + end + + if dupeData.wireMapInterfaceEntDupeInfo then + duplicator.StoreEntityModifier(self, "WireMapInterfaceEntDupeInfo", dupeData.wireMapInterfaceEntDupeInfo) + end +end + +function WIREENT:PostEntityPaste(ply, ent, createdEntities, ...) + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return + end + + if wmidata.oldMethods.PostEntityPaste then + wmidata.oldMethods.PostEntityPaste(self, ply, ent, createdEntities, ...) + end + + if not IsValid(ent) then + return + end + + local entityMods = ent.EntityMods + if not entityMods then + return + end + + local dupeData = { + wireDupeInfo = entityMods.WireDupeInfo, + wireMapInterfaceEntDupeInfo = entityMods.WireMapInterfaceEntDupeInfo, + } + + ent:_WMI_ApplyDupeData(ply, dupeData, createdEntities) +end + +function WIREENT:_WMI_BuildDupeData(interfaceEnt) + local data = {} + local wmiDupeInfo = {} + + data.wireDupeInfo = WireLib.BuildDupeInfo(self) + data.wireMapInterfaceEntDupeInfo = wmiDupeInfo + + if not IsValid(interfaceEnt) then + interfaceEnt = self._WireMapInterfaceEnt + + if not IsValid(interfaceEnt) then + return data + end + end + + local interfaceMapId = nil + if interfaceEnt:CreatedByMap() then + interfaceMapId = interfaceEnt:MapCreationID() + end + + local wmidata = self._WireMapInterfaceEnt_Data or {} + + wmiDupeInfo.entIdx = self:EntIndex() + wmiDupeInfo.mapId = self:_WMI_MapCreationIdDuped() + wmiDupeInfo.spawnId = self:_WMI_GetSpawnId(interfaceEnt) + + wmiDupeInfo.interface = { + mapId = interfaceMapId, + spawnId = interfaceEnt:GetSpawnId(), + } + + wmiDupeInfo.tmpPorts = { + inputs = wmidata.inputs, + outputs = wmidata.outputs, + } + + return data +end + +function WIREENT:_WMI_ApplyDupeData(ply, dupeData, createdEntities, interfaceEnt) + if not dupeData then + return + end + + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return + end + + local wireMapInterfaceEntDupeInfo = dupeData.wireMapInterfaceEntDupeInfo or {} + local wireDupeInfo = dupeData.wireDupeInfo or {} + + local interface = wireMapInterfaceEntDupeInfo.interface + local tmpPorts = wireMapInterfaceEntDupeInfo.tmpPorts + local spawnId = wireMapInterfaceEntDupeInfo.spawnId + local entIdx = wireMapInterfaceEntDupeInfo.entIdx + local mapId = wireMapInterfaceEntDupeInfo.mapId + + local dupe = wmidata.dupe or {} + wmidata.dupe = dupe + dupe.owner = ply + + dupe.mapId = mapId + dupe.entIdx = entIdx + dupe.spawnId = spawnId + + local dupeInterfaceEnt = dupe.interfaceEnt + dupe.interfaceEnt = nil + + if not IsValid(interfaceEnt) then + interfaceEnt = dupeInterfaceEnt + + if not IsValid(interfaceEnt) then + interfaceEnt = self:_WMI_FindWireMapInterfaceEnt(interface) + + if not IsValid(interfaceEnt) then + self:_WMI_RemoveOverrides() + return + end + end + end + + if not interfaceEnt:IsWireableEntity(self) then + self:_WMI_RemoveOverrides(interfaceEnt) + return + end + + dupe.interfaceEnt = interfaceEnt + + if interfaceEnt:HashSubEntityMapId(entIdx, mapId) ~= spawnId then + self:_WMI_RemoveOverrides(interfaceEnt) + return + end + + self:_WMI_ApplyWireDupeData(ply, wireDupeInfo, createdEntities, tmpPorts) + interfaceEnt:AddSingleEntity(self) +end + +local function GetEntityLookupFunction(createdEntities) + return function(idx, default) + if idx == nil then + return default + end + + if idx == 0 then + return game.GetWorld() + end + + local ent = createdEntities[idx] + if IsValid(ent) then + return ent + end + + return default + end +end + +function WIREENT:_WMI_ApplyWireDupeData(ply, wireDupeInfo, createdEntities, tmpPorts) + self:_WMI_AddTmpPorts(tmpPorts) + + local lookupFunc = GetEntityLookupFunction(createdEntities) + WireLib.ApplyDupeInfo(ply, self, wireDupeInfo, lookupFunc) +end + +function WIREENT:_WMI_MapCreationIdDuped() + local wmidata = self._WireMapInterfaceEnt_Data + + if wmidata then + local dupe = wmidata.dupe + + if dupe and dupe.mapId then + return dupe.mapId + end + end + + local mapId = self._WireMapInterfaceEnt_MapId + if mapId then + return mapId + end + + return self:MapCreationID() +end + +function WIREENT:_WMI_GetSpawnIdDuped(interfaceEnt) + local wmidata = self._WireMapInterfaceEnt_Data + + if wmidata then + local dupe = wmidata.dupe + + if dupe and dupe.spawnId then + return dupe.spawnId + end + end + + return WIREENT._WMI_GetSpawnId(self, interfaceEnt) +end + +function WIREENT:_WMI_GetSpawnId(interfaceEnt) + if self._WireMapInterfaceEnt_SpawnId then + return self._WireMapInterfaceEnt_SpawnId + end + + self._WireMapInterfaceEnt_SpawnId = nil + + if not IsValid(interfaceEnt) then + local interfaceEnt = self._WireMapInterfaceEnt + + if not IsValid(interfaceEnt) then + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return nil + end + + local dupe = wmidata.dupe + if not dupe then + return nil + end + + interfaceEnt = dupe.interfaceEnt + if not IsValid(interfaceEnt) then + return nil + end + end + end + + local id = interfaceEnt:HashSubEntityMapId( + self:EntIndex(), + WIREENT._WMI_MapCreationIdDuped(self) + ) + + self._WireMapInterfaceEnt_SpawnId = id + + return id end -- Remove its overrides -function WIREENT:_RemoveOverrides() - for k, v in pairs(self._Overrides_WireMapInterfaceEnt or {}) do - self[k] = v +function WIREENT:_WMI_RemoveOverrides(interfaceEnt) + local wmidata = self._WireMapInterfaceEnt_Data + + if not IsValid(interfaceEnt) then + interfaceEnt = self._WireMapInterfaceEnt + end + + self:RemoveCallOnRemove("WireMapInterface_OnRemove") + duplicator.ClearEntityModifier(self, "WireDupeInfo") + duplicator.ClearEntityModifier(self, "WireMapInterfaceEntDupeInfo") + + local spawnId = WIREENT._WMI_GetSpawnId(self, interfaceEnt) + local spawnIdDuped = WIREENT._WMI_GetSpawnIdDuped(self, interfaceEnt) + local mapId = WIREENT._WMI_MapCreationIdDuped(self) + + WireMapInterfaceLookup:remove(spawnId, spawnIdDuped, mapId, self) + WireLib.Remove(self) + + if IsValid(interfaceEnt) then + interfaceEnt:UnregisterWireEntityInternal(self) end - self._Overrides_WireMapInterfaceEnt = nil - for k, _ in pairs(self._Added_WireMapInterfaceEnt or {}) do + for k, v in pairs(g_memberBlacklist) do self[k] = nil end - self._Added_WireMapInterfaceEnt = nil - for key, value in pairs(self._Settings_WireMapInterfaceEnt or {}) do - if (not value or (value == 0) or (value == "")) then - self[key] = nil + -- Once we have a mapId, keep it forever, in case this entity is re-added to WMI again. + self._WireMapInterfaceEnt_MapId = mapId + + if not wmidata then + return + end + + local oldMembers = wmidata.oldMembers + if oldMembers then + for memberName, oldMember in pairs(oldMembers) do + if not g_memberBlacklist[memberName] then + self[memberName] = oldMember + end + end + end + + local oldSettings = wmidata.oldSettings + if oldSettings then + if not oldSettings.m_tblToolsAllowed then + self.m_tblToolsAllowed = false else - self[key] = value + self.m_tblToolsAllowed = oldSettings.m_tblToolsAllowed + end + + if not oldSettings.PhysgunDisabled then + self.PhysgunDisabled = nil + else + self.PhysgunDisabled = oldSettings.PhysgunDisabled end end - self._Settings_WireMapInterfaceEnt = nil - self._WireMapInterfaceEnt = nil - self._RemoveOverride = nil + table.Empty(wmidata) end -- Adds its overrides -function ENT:OverrideEnt(Entity) - Entity._Overrides_WireMapInterfaceEnt = Entity._Overrides_WireMapInterfaceEnt or {} - Entity._Added_WireMapInterfaceEnt = Entity._Added_WireMapInterfaceEnt or {} - - for k, v in pairs(WIREENT) do - if ((Entity[k] == nil) or (k == "_Overrides_WireMapInterfaceEnt") or (k == "_Added_WireMapInterfaceEnt") or (k == "_Settings_WireMapInterfaceEnt")) then - Entity._Overrides_WireMapInterfaceEnt[k] = nil - Entity._Added_WireMapInterfaceEnt[k] = true +function WIREENT:_WMI_OverrideEnt(interfaceEnt) + self._WMI_RemoveOverrides = WIREENT._WMI_RemoveOverrides + + local wmidata = self._WireMapInterfaceEnt_Data or {} + self._WireMapInterfaceEnt_Data = wmidata + + self._IsWireMapInterfaceSubEntity = true + + self.WireDebugName = string.format( + "Wire Map Interface [%s]", + self:GetClass() + ) + + local spawnId = WIREENT._WMI_GetSpawnId(self, interfaceEnt) + local spawnIdDuped = WIREENT._WMI_GetSpawnIdDuped(self, interfaceEnt) + local mapId = WIREENT._WMI_MapCreationIdDuped(self) + + self._WireMapInterfaceEnt_MapId = mapId + WireMapInterfaceLookup:add(spawnId, spawnIdDuped, mapId, self) + + local oldMembers = wmidata.oldMembers or {} + wmidata.oldMembers = oldMembers + + local oldMethods = wmidata.oldMethods or {} + wmidata.oldMethods = oldMethods + + for memberName, newMember in pairs(WIREENT) do + local oldMember = self[memberName] + + if not g_memberBlacklist[memberName] and oldMember ~= newMember then + oldMembers[memberName] = oldMember + + if not oldMember or isfunction(oldMember) then + oldMethods[memberName] = oldMember + end + + self[memberName] = newMember + end + end + + self:CallOnRemove("WireMapInterface_OnRemove", function(this) + if this._WMI_RemoveOverrides then + this:_WMI_RemoveOverrides() + end + end) +end + +function WIREENT:_WMI_AddPorts(wireInputRegister, wireOutputRegister) + if not IsValid(self._WireMapInterfaceEnt) then return end + + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return + end + + if wireInputRegister and wireInputRegister:hasPorts() then + local split = wireInputRegister.wire + + wmidata.inputs = table.Copy(split) + wmidata.inputs.descs = nil + + if not self.Inputs then + self.Inputs = WireLib.CreateSpecialInputs(self, split.names, split.types, split.descs) + else + self.Inputs = WireLib.AdjustSpecialInputs(self, split.names, split.types, split.descs) + end + + self._WireMapInterfaceEnt_TmpPorts = nil + self._WireMapInterfaceEnt_HasPorts = true + end + + if wireOutputRegister and wireOutputRegister:hasPorts() then + local split = wireOutputRegister.wire + + wmidata.outputs = table.Copy(split) + wmidata.outputs.descs = nil + + if not self.Outputs then + self.Outputs = WireLib.CreateSpecialOutputs(self, split.names, split.types, split.descs) else - Entity._Overrides_WireMapInterfaceEnt[k] = v - Entity._Added_WireMapInterfaceEnt[k] = nil + self.Outputs = WireLib.AdjustSpecialOutputs(self, split.names, split.types, split.descs) end - Entity[k] = v + + self._WireMapInterfaceEnt_TmpPorts = nil + self._WireMapInterfaceEnt_HasPorts = true + end +end + +function WIREENT:_WMI_AddTmpPorts(tmpPorts) + -- Add Ports from dupe as a dummy, so connections will not get lost when pasted too early. + if not tmpPorts then + return + end + + if self._WireMapInterfaceEnt_HasPorts then + return + end + + local inputs = tmpPorts.inputs + local outputs = tmpPorts.outputs + + if inputs then + if not self.Inputs then + self.Inputs = WireLib.CreateSpecialInputs(self, inputs.names, inputs.types) + else + self.Inputs = WireLib.AdjustSpecialInputs(self, inputs.names, inputs.types) + end + + self._WireMapInterfaceEnt_TmpPorts = true + end + + if outputs then + if not self.Outputs then + self.Outputs = WireLib.CreateSpecialOutputs(self, outputs.names, outputs.types) + else + self.Outputs = WireLib.AdjustSpecialOutputs(self, outputs.names, outputs.types) + end + + self._WireMapInterfaceEnt_TmpPorts = true + end +end + +function WIREENT:_WMI_SetInterface(interfaceEnt) + local wmidata = self._WireMapInterfaceEnt_Data + if not wmidata then + return + end + + local oldSettings = wmidata.oldSettings or {} + wmidata.oldSettings = oldSettings + + -- Only apply tool/physgun limits if the entity was spawned by the map. + -- This prevents impossible-to-remove spam by e.g. rigged dupes. + local isCreatedByMap = self:CreatedByMap() + + -- Protect in-/output entities from non-wire tools + if not self.m_tblToolsAllowed then + oldSettings.m_tblToolsAllowed = false + else + oldSettings.m_tblToolsAllowed = table.Copy(self.m_tblToolsAllowed) + end + + if interfaceEnt:FlagGetProtectFromTools() and isCreatedByMap then + self.m_tblToolsAllowed = self.m_tblToolsAllowed or {} + table.Add(self.m_tblToolsAllowed, g_wireTools) end + -- Protect in-/output entities from the physgun + oldSettings.PhysgunDisabled = self.PhysgunDisabled or false - Entity._Settings_WireMapInterfaceEnt = Entity._Settings_WireMapInterfaceEnt or {} + if interfaceEnt:FlagGetProtectFromPhysgun() and isCreatedByMap then + -- Still save the old values for restore, but do not change them if run time created/duped. + self.PhysgunDisabled = true + end + + self._WireMapInterfaceEnt = interfaceEnt + self._IsWireMapInterfaceSubEntity = true +end - if (bit.band(self.flags, 1) > 0) then -- Protect in-/output entities from non-wire tools - Entity._Settings_WireMapInterfaceEnt.m_tblToolsAllowed = Entity.m_tblToolsAllowed or false - Entity.m_tblToolsAllowed = {"wire", "wire_adv", "wire_debugger", "wire_wirelink", "gui_wiring", "multi_wire"} +function ENT:OverrideEnt(wireEnt) + if not IsValid(wireEnt) then + return end - if (bit.band(self.flags, 2) > 0) then -- Protect in-/output entities from the physgun - Entity._Settings_WireMapInterfaceEnt.PhysgunDisabled = Entity.PhysgunDisabled or false - Entity.PhysgunDisabled = true + if not wireEnt._IsWireMapInterfaceSubEntity then + WIREENT._WMI_OverrideEnt(wireEnt, self) end - Entity._WireMapInterfaceEnt = self + if not IsValid(self._WireMapInterfaceEnt) and wireEnt._WMI_SetInterface then + wireEnt:_WMI_SetInterface(self) + end end + +local function OverrideEntFromDupe(wireEnt, wireMapInterfaceEntDupeInfo, interfaceEnt) + if not IsValid(wireEnt) then + return + end + + if wireEnt._IsWireMapInterfaceSubEntity then + -- Already initialized + return + end + + local mapId = wireMapInterfaceEntDupeInfo.mapId + local entIdx = wireMapInterfaceEntDupeInfo.entIdx + local spawnId = wireMapInterfaceEntDupeInfo.spawnId + local interface = wireMapInterfaceEntDupeInfo.interface + + if not IsValid(interfaceEnt) then + interfaceEnt = WIREENT._WMI_FindWireMapInterfaceEnt(wireEnt, interface) + + if not IsValid(interfaceEnt) then + WIREENT._WMI_RemoveOverrides(wireEnt) + return + end + end + + if not interfaceEnt:IsWireableEntity(wireEnt) then + WIREENT._WMI_RemoveOverrides(wireEnt, interfaceEnt) + return + end + + if interfaceEnt:HashSubEntityMapId(entIdx, mapId) ~= spawnId then + WIREENT._WMI_RemoveOverrides(wireEnt, interfaceEnt) + return + end + + local wmidata = wireEnt._WireMapInterfaceEnt_Data or {} + wireEnt._WireMapInterfaceEnt_Data = wmidata + + local dupe = wmidata.dupe or {} + wmidata.dupe = dupe + + dupe.mapId = mapId + dupe.entIdx = entIdx + dupe.spawnId = spawnId + dupe.interfaceEnt = interfaceEnt + + -- Modify the entity like the Wire Map Interface does, but without attaching it yet. It's needed for wireEnt:PostEntityPaste() to be run properly. + -- The attachment is done in wireEnt:PostEntityPaste(), because it has the CreatedEntities table Wiremod needs. + WIREENT._WMI_OverrideEnt(wireEnt, interfaceEnt) + + -- Ensure that the temporary ports are added super early, so other wire entities can connect to them as soon as they are duped. + wireEnt:_WMI_AddTmpPorts(wireMapInterfaceEntDupeInfo.tmpPorts) +end + +function ENT:OverrideEntFromDupe(wireEnt, wireMapInterfaceEntDupeInfo) + OverrideEntFromDupe(wireEnt, wireMapInterfaceEntDupeInfo, self) +end + +local g_dupeHooksAdded = false + +function ENT:AddDupeHooks() + if g_dupeHooksAdded then + return + end + + duplicator.RegisterEntityModifier("WireMapInterfaceEntDupeInfo", function(ply, wireEnt, wireMapInterfaceEntDupeInfo) + -- Make sure to prepair the Wire Map Interface sub entity when duped, so it can be found and linked too. + OverrideEntFromDupe(wireEnt, wireMapInterfaceEntDupeInfo) + end) + + hook.Add("Wire_ApplyDupeInfo", "Wire_InitFromWireMapInterfaceEntDupeInfo", function(ply, inputEnt, outputEnt, inputData) + -- Make sure we initialize the Wire Map Interface sub entity before connecting our inputs to it. It will not initialize twice. + + local entityMods = inputEnt.EntityMods + if entityMods then + local wireMapInterfaceEntDupeInfo = entityMods.WireMapInterfaceEntDupeInfo + + if wireMapInterfaceEntDupeInfo then + OverrideEntFromDupe(inputEnt, wireMapInterfaceEntDupeInfo) + end + end + + local entityMods = outputEnt.EntityMods + if entityMods then + local wireMapInterfaceEntDupeInfo = entityMods.WireMapInterfaceEntDupeInfo + + if wireMapInterfaceEntDupeInfo then + OverrideEntFromDupe(outputEnt, wireMapInterfaceEntDupeInfo) + end + end + end) + + g_dupeHooksAdded = true +end + diff --git a/lua/entities/info_wiremapinterface/gmodoutputs.lua b/lua/entities/info_wiremapinterface/gmodoutputs.lua new file mode 100644 index 0000000000..c7535faaef --- /dev/null +++ b/lua/entities/info_wiremapinterface/gmodoutputs.lua @@ -0,0 +1,76 @@ +-- Modified gmod base entity hammer outout code to extend it by custom functionalities. + +function ENT:FireSingleOutput(outputName, output, activator, data) + if output.times == 0 then + return false + end + + local targetName = output.entities + local entitiesToFire = nil + + if targetName == "!activator" then + entitiesToFire = {activator} + else + entitiesToFire = self:GetEntitiesByTargetnameOrClass(targetName) + end + + local params = output.param or "" + + if params == "" then + params = data or "" + end + + local inputName = output.input + local remove = false + + if entitiesToFire then + for _, ent in ipairs(entitiesToFire) do + if self:ProtectAgainstDangerousIO(ent, outputName, output, data) then + if self:IsLuaRunEntity(ent) then + self:PrepairEntityForFire(ent, outputName) + ent:Fire(inputName, params, output.delay, activator, self) + else + ent:Fire(inputName, params, output.delay, activator, self) + end + else + -- Remove the unsafe IO, to prevent error message spam. + remove = true + end + end + end + + if output.times > 0 then + output.times = output.times - 1 + end + + if remove then + return false + end + + -- Less then 0 are valid to, e.g. unlimited times. + return output.times ~= 0 +end + +-- This function is used to trigger an output. +function ENT:TriggerOutput(outputName, activator, data) + if not self.m_tOutputs then + return + end + + local outputNameLower = string.lower(outputName) + local outputList = self.m_tOutputs[outputNameLower] + + if not outputList then + return + end + + for idx = #outputList, 1, -1 do + local output = outputList[idx] + + if output and not self:FireSingleOutput(outputName, output, activator, data) then + -- Shift the indexes so this loop doesn't fail later + table.remove(outputList, idx) + end + end +end + diff --git a/lua/entities/info_wiremapinterface/init.lua b/lua/entities/info_wiremapinterface/init.lua index 794f211f93..c9fa5abc6b 100644 --- a/lua/entities/info_wiremapinterface/init.lua +++ b/lua/entities/info_wiremapinterface/init.lua @@ -1,462 +1,646 @@ --[[ -This is the wire map interface entity by Grocel. (info_wiremapinterface) +This is the wire map interface entity. (info_wiremapinterface) This point entity allows you to give other entities wire in-/outputs. Those wire ports allows you to control thinks on the map with Wiremod or to let the map return thinks to wire outputs. -It supports many datatypes and custom lua codes. -A lua code is run when its input triggers. -It has special globals: - WIRE_NAME = Input name - WIRE_VALUE = Input value - WIRE_WIRED = Is the input wired? - WIRE_CALLER = This entity - WIRE_ACTIVATOR = The entity that has the input +It supports many datatypes. +In case it triggers a lua_run entity it temporarily applies these special globals to the Lua environment: + WIRE_NAME -- Input name + WIRE_TYPE -- Input type (NORMAL, STRING, VECTOR, etc.) + WIRE_VALUE -- Input value + WIRE_WIRED -- Is the input wired? + WIRE_CALLER -- This entity + WIRE_ACTIVATOR -- The entity that has the Wire input + WIRE_DEVICE -- The entity where the input data was from, e.g. a Wiremod button + WIRE_OWNER -- The owner of the input device, e.g the player who spawned the Wiremod button +]] -Keep in mind that you have to know what you do -and that you have to activate spawnflag 8 to make it work. -Spawnflag 8 is better known as "Run given Lua codes (For advanced users!)" in the Hammer Editor. +local WireAddon = WireAddon +local WireLib = WireLib -Please don't change things unless you know what you do. You may break maps if do something wrong. -]] +ENT.Base = "base_point" +ENT.Type = "point" + +ENT.Spawnable = false +ENT.AdminOnly = true + +-- Wire Map Interfaces (info_wiremapinterface) should not be allowed to be duplicated/saved. +-- We use an info_wiremapinterface_savestate for that. +ENT.DisableDuplicator = true +ENT.DoNotDuplicate = true +ENT.IsWireMapInterface = nil + +if not WireAddon then + -- Avoids problems with early map spawned entities in case Wiremod fails to load. + return +end + +-- Make sure there is no way to mess around with tools, especially dublicator tools. +-- This entity not traceable nor visible, so tools would not matter. +ENT.m_tblToolsAllowed = {} + +-- Esay way to check if it is a wire map interface. +ENT.IsWireMapInterface = true + +-- This entity supports more than the 8 ports you see in the editor. This value is the port limit. +ENT.MAX_PORTS = 255 + +-- Minimum delay between think calls. +ENT.MIN_THINK_TIME = 0.25 include("convert.lua") include("entitycontrol.lua") include("entityoverride.lua") +include("gmodoutputs.lua") +include("io.lua") +include("networking.lua") +include("savestate.lua") + +local cvar_allow_interface = CreateConVar( + "sv_wire_mapinterface", + "1", + {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL}, + "Enable or disable all Wire Map Interface entities. Default: 1", + 0, + 1 +) + +-- Minimum time between Wiremod input triggers. +local cvar_min_trigger_time = CreateConVar( + "sv_wire_mapinterface_min_trigger_time", + "0.01", + {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL}, + "Sets minimum time between Wiremod input triggers per Wire Map Interface entity and Wiremod input. Default: 0.01", + 0, + 1 +) + +-- The maximum number of entities per interface entitiy that can get wire ports +local cvar_max_sub_entities = CreateConVar( + "sv_wire_mapinterface_max_sub_entities", + "32", + {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL}, + "Sets maximum count of sub entities per Wire Map Interface entity that can get wire ports. Default: 32", + 1, + 128 +) + +local g_classBlacklist = { + lua_run = true, -- No interface for lua_run! + func_water = true, -- No interface for water! + func_water_analog = true, -- No interface for water! + info_wiremapinterface = true, -- No interface for other Wire Map Interfaces! + info_wiremapinterface_savestate = true, -- No interface for Wire Map Interfaces savestate helper! + func_illusionary = true, -- No interface for Non-Solid +} + +local g_classBlacklistPatterns = { + "^(item_[%w_]+)", -- No interface for items! + "^(info_[%w_]+)", -- No interface for info entities! + "^(trigger_[%w_]+)", -- No interface for trigger entities! +} -local ALLOW_INTERFACE = CreateConVar("sv_wire_mapinterface", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL}, "Aktivate or deaktivate the wire map interface. Default: 1") - - -local Ents = {} -hook.Add("PlayerInitialSpawn", "WireMapInterface_PlayerInitialSpawn", function(ply) - if (not IsValid(ply)) then return end - for Ent, time in ipairs(Ents) do - if (not IsValid(Ent)) then break end +-- This checks if you can give an entity wiremod abilities +function ENT:IsWireableEntity(ent) + if not IsValid(ent) then + -- No interface for invalid entities! + return false + end - timer.Simple(time + 0.1, function() - if (not IsValid(ply)) then return end - if (not IsValid(Ent)) then return end - if (not Ent.GiveWireInterfeceClient) then return end + if IsValid(ent._WireMapInterfaceEnt) and ent._WireMapInterfaceEnt ~= self then + -- Only one interface per entity! + return false + end - Ent:GiveWireInterfeceClient(ply) - end) + local hasPorts = WireLib.HasPorts(ent) or ent.IsWire or ent.Inputs or ent.Outputs + if not IsValid(ent._WireMapInterfaceEnt) and not ent._WireMapInterfaceEnt_TmpPorts and hasPorts then + -- Don't destroy wiremod entites! + return false end -end) --- This is a point entity -ENT.Base = "base_point" -ENT.Type = "point" + if not IsValid(ent:GetPhysicsObject()) then return false end -- Only entities with physics can get an interface! + if ent:GetPhysicsObjectCount() ~= 1 then return false end -- Only entities with single bone physics can get an interface! -local MAX_PORTS = 256 -- This entity supports more than the 8 ports you see in the editor. This value is the port limit. -local MAX_ENTITIES = 32 -- The maximum number of entities per interface entitiy that can get wire ports -local MIN_TRIGGER_TIME = 0.01 -- Minimum triggering Time for in- and outputs. + if ent:IsWorld() then return false end -- No interface for the worldspawn! + if ent:IsVehicle() then return false end -- No interface for vehicles! + if ent:IsNPC() then return false end -- No interface for NPCs! + if ent:IsPlayer() then return false end -- No interface for players! + if ent:IsWeapon() then return false end -- No interface for weapons! + if ent:IsConstraint() then return false end -- No interface for constraints! + if ent:IsRagdoll() then return false end -- No interface for ragdolls! --- This checks if you can give an entity wiremod abilities -function ENT:IsWireableEntity(Entity) - if (not IsValid(Entity)) then return false end -- No interface for invalid entities! - if (IsValid(Entity._WireMapInterfaceEnt) and (Entity._WireMapInterfaceEnt ~= self)) then return false end -- Only one interface per entity! - if (not IsValid(Entity._WireMapInterfaceEnt) and (WireLib.HasPorts(Entity) or Entity.IsWire or Entity.Inputs or Entity.Outputs)) then return false end -- Don't destroy wiremod entites! - - if (Entity:IsWorld()) then return false end -- No interface for the worldspawn! - if (Entity:IsVehicle()) then return false end -- No interface for vehicles! - if (Entity:IsNPC()) then return false end -- No interface for NPCs! - if (Entity:IsPlayer()) then return false end -- No interface for players! - if (Entity:IsWeapon()) then return false end -- No interface for weapons! - if (string.match(Entity:GetClass(), "^(item_[%w_]+)")) then return false end -- No interface for items! - if (Entity:IsConstraint()) then return false end -- No interface for constraints! - - if (Entity:GetPhysicsObjectCount() > 1) then return false end -- No interface for ragdolls! - if (IsValid(Entity:GetPhysicsObject())) then return true end -- Everything with a single physics object can get an interface! + local class = ent:GetClass() - return true -end + if g_classBlacklist[class] then + return false + end -function ENT:CheckEntLimid(CallOnMax, ...) - if (self.WireEntsCount > MAX_ENTITIES) then - MsgN(self.ErrorName..": Warning, to many wire entities linked!") - if (CallOnMax) then - CallOnMax(self, ...) + for _, pattern in ipairs(g_classBlacklistPatterns) do + if string.match(class, pattern) then + return false end - return false end - self.WireEntsCount = self.WireEntsCount + 1 + return true end --- Run the given lua code -local function RunLua(I, name, value, wired, self, Ent) - local lua = self.Ins[I].lua or "" - if ((lua == "") or not self.RunLuaCode) then return end - - local func = CompileString(lua, self.ErrorName.." (Input "..I..")", false) - local Err - if isfunction(func) then - -- Globals - WIRE_NAME = name -- Input name - WIRE_VALUE = value -- Input value - WIRE_WIRED = wired -- Is the input wired? - WIRE_CALLER = self -- This entity - WIRE_ACTIVATOR = Ent -- The entity that has the input - - local status, err = xpcall(func, debug.traceback) - if (not status) then - Err = err or "" - end +local g_warningColor = Color(255, 100, 100) - -- Remove globals - WIRE_NAME = nil - WIRE_VALUE = nil - WIRE_WIRED = nil - WIRE_CALLER = nil - WIRE_ACTIVATOR = nil - else - Err = func +function ENT:FormatEntityString(ent) + local entString = tostring(ent or NULL) + + if IsValid(ent) then + local name = ent:GetName() or "" + return string.format("%s[%s]", entString, name) end - if (Err and (Err ~= "")) then - ErrorNoHalt(Err.."\n") + return entString +end + +function ENT:FormatString(message, ...) + message = tostring(message or "") + + if message == "" then + return "" end + + local entString = self:FormatEntityString(self) + + message = string.format("Wire Map Interface: %s" .. message, entString, ...) + return message end --- Wire input -function ENT:TriggerWireInput(name, value, wired, Ent) - if (not WireAddon) then return end - if (not IsValid(Ent)) then return end - if ((not self.Active or not ALLOW_INTERFACE:GetBool()) and wired) then - self.SavedIn = self.SavedIn or {} - self.SavedIn[name] = {value, wired, Ent} +function ENT:PrintWarning(message, ...) + message = self:FormatString(message, ...) + if message == "" then return end - self.Wired = self.Wired or {} - self.Wired[Ent] = self.Wired[Ent] or {} - local WireRemoved = ((self.Wired[Ent][name] or false) ~= wired) and not wired - self.Wired[Ent][name] = wired - - self.Timer = self.Timer or {} - self.Timer.In = self.Timer.In or {} - if (((CurTime() - (self.Timer.In[name] or 0)) < (self.min_trigger_time or MIN_TRIGGER_TIME)) and not WireRemoved) then return end - self.Timer.In[name] = CurTime() - - self.Data = self.Data or {} - self.Data.In = self.Data.In or {} - if ((self.Data.In[name] == value) and not WireRemoved) then return end - self.Data.In[name] = value - - local I = self.InsIDs[name] or 0 - if ((I > 0) and (I <= MAX_PORTS) and self.InsExist[I]) then - local _, Convert, Toggle = self:Convert_WireToMap(self.Ins[I].type) - if (not Convert) then return end - local Output = "onwireinput"..I - - -- Map output - if (not wired) then - if (WireRemoved) then - if (not Toggle) then - self:TriggerOutput(Output, Ent, Convert(value)) - end - self:TriggerOutput("onresetwireinput"..I, Ent) + MsgC(g_warningColor, message, "\n") +end - RunLua(I, name, value, wired, self, Ent) - end - else - if (Toggle) then - if (Convert(value)) then - self:TriggerOutput(Output, Ent) +function ENT:CheckEntLimit(count, ent) + local maxSubEntities = self:GetMaxSubEntities() - RunLua(I, name, value, wired, self, Ent) - end - else - self:TriggerOutput(Output, Ent, Convert(value)) + if count >= maxSubEntities then + self:PrintWarning(": Warning, limit of %d linked wire entities reached! Can not add: %s ", maxSubEntities, self:FormatEntityString(ent)) + return false + end - RunLua(I, name, value, wired, self, Ent) - end + return true +end + +function ENT:CheckPortIdLimit(portId, warn) + portId = tonumber(portId or 0) or 0 + + if portId == 0 then + return false + end + + if portId > self.MAX_PORTS or portId < 0 then + if warn then + self:PrintWarning(": Warning, invaid portId given. Expected 0 < portId < %d, got %d!", self.MAX_PORTS, portId) end + + return false end + + return true end --- Wire output -function ENT:TriggerWireOutput(ent, i, val) - if (not IsValid(ent)) then return false end +-- Protect in-/output entities from non-wire tools +function ENT:FlagGetProtectFromTools() + if not self:CreatedByMap() then + -- Prevent abuse by runtime-spawned instances. + return false + end - local OutputName = self.Outs[i].name or "" - if (OutputName == "") then return false end + local flags = self:GetSpawnFlags() + return bit.band(flags, 1) == 1 +end - local _, Convert, Toggle = self:Convert_MapToWire(self.Outs[i].type) - if (not Convert) then return false end +-- Protect in-/output entities from the physgun +function ENT:FlagGetProtectFromPhysgun() + if not self:CreatedByMap() then + return false + end - if (Toggle) then - Wire_TriggerOutput(ent, OutputName, Convert(self, ent, i)) - else - Wire_TriggerOutput(ent, OutputName, Convert(val)) + local flags = self:GetSpawnFlags() + return bit.band(flags, 2) == 2 +end + +-- Remove in-/output entities on remove +function ENT:FlagGetRemoveEntities() + if not self:CreatedByMap() then + return false end - return true + + local flags = self:GetSpawnFlags() + return bit.band(flags, 4) == 4 end --- Map input -function ENT:AcceptInput(name, activator, caller, data) - if (not WireAddon) then return false end - name = string.lower(tostring(name or "")) - if (name == "") then return false end +-- Note: +-- bit.band(flags, 8) == 8 Was used for running lua code. +-- It must be left unused as it could cause unexpected side effects on older maps. - if (name == "activate") then - self.Active = true - return true +-- Start Active +function ENT:FlagGetStartActive() + local flags = self:GetSpawnFlags() + return bit.band(flags, 16) == 16 +end + +-- Render wires clientside +function ENT:FlagGetRenderWires() + local flags = self:GetSpawnFlags() + return bit.band(flags, 32) == 32 +end + +function ENT:Initialize() + self.Active = self:FlagGetStartActive() + self.oldIsActive = self:IsActive() + + self.WireEntsRegister = self.WireEntsRegister or {} + self.WireEntName = self.WireEntName or "" + + self.WireInputRegisterTmp = self.WireInputRegisterTmp or {} + self.WireOutputRegisterTmp = self.WireOutputRegisterTmp or {} + + self.WireInputTriggerBuffer = self.WireInputTriggerBuffer or {} + + self.PortsUpdated = true + + local recipientFilter = RecipientFilter() + recipientFilter:RemoveAllPlayers() + + self.NetworkRecipientFilter = recipientFilter + + self.NextNetworkTime = CurTime() + (1 + math.random() * 2) * (self.MIN_THINK_TIME * 4) + + self:AddDupeHooks() + self:AttachToSaveStateEntity() +end + +function ENT:OnReloaded() + -- Easier for debugging. + self:RequestNetworkEntities() + self:AttachToSaveStateEntity() +end + +function ENT:IsActive() + return self.Active and cvar_allow_interface:GetBool() +end + +function ENT:GetMinTriggerTime() + local minTriggerTime = math.max( + self.MinTriggerTime or 0, + cvar_min_trigger_time:GetFloat(), + 0 + ) + + minTriggerTime = math.min(minTriggerTime, 1) + return minTriggerTime +end + +function ENT:GetMaxSubEntities() + local maxSubEntities = math.Clamp( + cvar_max_sub_entities:GetInt(), + 1, + 32 + ) + + return maxSubEntities +end + +function ENT:IsLuaRunEntity(ent) + if not IsValid(ent) then + return false end - if (name == "deactivate") then - self.Active = false + return ent:GetClass() == "lua_run" +end + +function ENT:ProtectAgainstDangerousIO(targetEnt, outputName, output, data) + if not string.StartsWith(string.lower(outputName), "onwireinput") then + -- This protection is only relevant for Hammer outputs linked to Wire inputs. return true end - if (name == "toggle") then - self.Active = not self.Active + local inputName = output.input + local inputNameLower = string.lower(inputName) + + local params = output.param or "" + if params ~= "" then + -- We would run code from the override parameter set via Hammer, so it is safe. There is no direct user input. return true end - if (self.Active and ALLOW_INTERFACE:GetBool()) then - local pattern = "(%d+)" - local I = tonumber(string.match(name, "triggerwireoutput"..pattern)) or 0 - - if I > 0 and I <= MAX_PORTS and self.OutsExist[I] then - self.Timer = self.Timer or {} - self.Timer.Out = self.Timer.Out or {} + if inputNameLower == "addoutput" then + -- This can be abused to do all sorts of stuff, so don't allow AddOutput from user input. This could even run unauthorized Lua code. + -- Warn the mapper about their mistake. + self:PrintWarning( + ", Hammer output '%s' -> Hammer input '%s@%s': Dangerous operation!\n Do not trigger AddOutput with user input.\n Change this trigger or use the override parameter instead.\n This trigger has been blocked and removed.", + outputName, + self:FormatEntityString(targetEnt), + inputName + ) - if ((CurTime() - (self.Timer.Out[name] or 0)) < (self.min_trigger_time or MIN_TRIGGER_TIME)) then return false end - self.Timer.Out[name] = CurTime() + return false + end - -- Wire output - for Ent, _ in pairs(self.WireEnts or {}) do - self:TriggerWireOutput(Ent, I, data) - end + if self:IsLuaRunEntity(targetEnt) and inputNameLower == "runpassedcode" then + -- Prevent an potential RCE: Block direct user input from being run as unauthorized Lua code. + -- Warn the mapper about their mistake. + self:PrintWarning( + ", Hammer output '%s' -> Hammer input '%s@%s': Dangerous operation!\n Do not run Lua code with user input!\n This would allow players to take over the Server.\n Use the override parameter or trigger RunCode instead.\n This trigger has been blocked and removed.", + outputName, + self:FormatEntityString(targetEnt), + inputName + ) - return true - end + return false end - if (not self.WirePortsChanged) then return false end + return true +end - if (name == "addentity") then - local Ent, Func = self:AddSingleEntity(caller) +function ENT:GetEntitiesByTargetnameOrClass(nameOrClass) + nameOrClass = tostring(nameOrClass or "") - if (not IsValid(Ent) or not Func) then return false end - timer.Simple(0.02, function() Func( self, Ent, nil, true) end) + if nameOrClass == "" then + return nil + end - self:TriggerOutput("onwireentscreated", self) - self:TriggerOutput("onwireentsready", self) - return true + if nameOrClass == "!null" then + -- Non-empty string for void entity. + return nil end - if (name == "removeentity") then - self:RemoveSingleEntity(caller) - return true + if nameOrClass == "!player" then + -- All players + + local players = player.GetAll() + if #players > 0 then + return players + end + + return nil end - if (name == "addentities") then - self:AddEntitiesByName(data) - return true + if nameOrClass[1] == "!" then + local ent = self:GetFirstEntityByTargetnameOrClass(nameOrClass) + if not IsValid(ent) then + return nil + end + + return {ent} end - if (name == "removeentities") then - self:RemoveEntitiesByName(data) - return true + local byName = ents.FindByName(nameOrClass) + if #byName > 0 then + return byName end - if (name == "removeallentities") then - self:RemoveAllEntities() - return true + local byClass = ents.FindByClass(nameOrClass) + if #byClass > 0 then + return byClass end - return false + return nil end -function ENT:KeyValue(key, value) - if (not WireAddon) then return end - - key = string.lower(tostring(key or "")) - value = tostring(value or "") +function ENT:GetFirstEntityByTargetnameOrClass(nameOrClass) + nameOrClass = tostring(nameOrClass or "") - if ((key == "") or (value == "")) then return end + if nameOrClass == "" then + return nil + end - local pattern = "(%d+)" - local I = tonumber(string.match(key, "onwireinput"..pattern)) or 0 - if ((I > 0) and (I <= MAX_PORTS)) then - self:StoreOutput(key, value) + if nameOrClass == "!null" then + -- Non-empty string for void entity. + return nil end - local I = tonumber(string.match(key, "onresetwireinput"..pattern)) or 0 - if ((I > 0) and (I <= MAX_PORTS)) then - self:StoreOutput(key, value) + if nameOrClass == "!self" then + -- This entity. + return self end - if ((key == "onwireentscreated") or (key == "onwireentsremoved") or - (key == "onwireentsready") or (key == "onwireentsstartchanging")) then - self:StoreOutput(key, value) + if nameOrClass == "!player" then + -- First Player. + + local players = player.GetAll() + if #players > 0 then + local first = next(players) + if not IsValid(first) then + return nil + end + + return first + end + + return nil end - if (key == "wire_entity_name") then - self.WireEntName = value + if nameOrClass == "!caller" then + -- The last entity that called an Hammer input, + -- e.g a trigger_multiple brush. + + local lastCaller = self._lastCaller + if not IsValid(lastCaller) then + return nil + end + + return lastCaller end - if (key == "min_trigger_time") then - self.min_trigger_time = math.max(tonumber(value) or 0, MIN_TRIGGER_TIME) - end - - local pattern = "(%d+)_(%l+)" - local I, name = string.match(key, "input"..pattern) - local I, name = tonumber(I) or 0, tostring(name or "") - if ((I > 0) and (I <= MAX_PORTS) and (name ~= "")) then - self.Ins = self.Ins or {} - self.InsIDs = self.InsIDs or {} - self.InsExist = self.InsExist or {} - - self.Ins[I] = self.Ins[I] or {} - if (name == "lua") then - self.Ins[I][name] = value - elseif (name == "type") then - self.Ins[I][name] = tonumber(value) - elseif (name == "desc") then - self.Ins[I][name] = value - elseif (name == "name") then - self.InsIDs[value] = I - self.InsExist[I] = true - self.Ins[I][name] = value + if nameOrClass == "!activator" then + -- The last entity that triggered !caller to call an Hammer input, + -- e.g. a player that passed though a trigger_multiple brush. + + local lastActivator = self._lastActivator + if not IsValid(lastActivator) then + return nil end + + return lastActivator end - local I, name = string.match(key, "output"..pattern) - local I, name = tonumber(I) or 0, tostring(name or "") - if I > 0 and I <= MAX_PORTS and name ~= "" then - self.Outs = self.Outs or {} - self.OutsExist = self.OutsExist or {} - - self.Outs[I] = self.Outs[I] or {} - if (name == "type") then - self.Outs[I][name] = tonumber(value) - elseif (name == "desc") then - self.Outs[I][name] = value - elseif (name == "name") then - self.OutsExist[I] = true - self.Outs[I][name] = value + if nameOrClass == "!input" then + -- The entity that has the Wire input. (!activator in Hammer Output) + + local lastWireInputEnt = self._lastWireInputEnt + if not IsValid(lastWireInputEnt) then + return nil end + + return lastWireInputEnt end -end -local Count = 1 -function ENT:Initialize() - if (not WireAddon) then return end - self.WireEnts = self.WireEnts or {} - self.WireEntsCount = 0 - self.WireEntName = self.WireEntName or "" + if nameOrClass == "!device" then + -- The entity where the input data was from, e.g. a Wiremod button. - self:UpdateData() - self.Active = (bit.band(self.flags, 16) > 0) -- Start Active - self.oldActive = self.Active - self.old_ALLOW_INTERFACE_bool = ALLOW_INTERFACE:GetBool() + local lastWireActivatorEnt = self._lastWireDeviceEnt + if not IsValid(lastWireActivatorEnt) then + return nil + end - local Name = self:GetName() or "" - local ErrorName = "Wire Map Interface: "..tostring(self) + return lastWireActivatorEnt + end - if (Name == "") then - self.ErrorName = ErrorName - else - self.ErrorName = ErrorName.."['"..Name.."']" + if nameOrClass == "!owner" then + -- The owner of !device the !input entity is connected to, + -- e.g. the player who spawned the Wiremod button. + + local lastOwner = self._lastWireDeviceEntOwner + if not IsValid(lastOwner) then + return nil + end + + return lastOwner end + local byName = ents.FindByName(nameOrClass) + if #byName > 0 then + local first = next(byName) + if not IsValid(first) then + return nil + end - local time = Count * 0.3 + 0.5 + return first + end - if (self.WireEntName == "") then - self.WirePortsChanged = true - else - timer.Simple(time, function() - if (not IsValid(self)) then return end - self.WirePortsChanged = true + local byClass = ents.FindByClass(nameOrClass) + if #byClass > 0 then + local first = next(byClass) + if not IsValid(first) then + return nil + end - self:AddEntitiesByName(self.WireEntName) - end) + return first end - Ents[self] = time - Count = Count + 1 + return nil end --- To cleanup and get the in-/outputs information. -local function SplitTable(tab, self) - if (not IsValid(self)) then return end +function ENT:AcceptInput(name, activator, caller, data) + self._lastActivator = activator + self._lastCaller = caller - if (not tab) then return end - if (#tab == 0) then return end - local tab = table.Copy(tab) + name = string.lower(tostring(name or "")) + if name == "" then return false end - local allowlua = self.RunLuaCode + if name == "activate" then + self.Active = true + return true + end - local names, types, descs = {}, {}, {} - local Index = 0 + if name == "deactivate" then + self.Active = false + return true + end - for i = 1, #tab do - local Port = tab[i] - if (Port) then - local name = Port.name -- The port name for checking - if (name) then -- Do not add ports with no names - Index = Index + 1 + if name == "toggle" then + self.Active = not self.Active + return true + end - names[Index] = name -- The port name - types[Index] = self:Convert_MapToWire(Port.type) -- The port type - descs[Index] = Port.desc -- The port description - if (not allowlua) then - tab[i].lua = nil -- remove lua codes if the lua mode isn't on. - end - else - tab[i] = nil -- Resort and cleanup the given table for later using - end - end + if self:TriggerHammerInput(name, data) then + return true end - return names, types, descs, tab + return false end -function ENT:UpdateData() - self.flags = self:GetSpawnFlags() - self.RunLuaCode = (bit.band(self.flags, 8) > 0) -- Run given Lua codes +function ENT:KeyValue(key, value) + key = string.lower(tostring(key or "")) + value = tostring(value or "") + + if key == "" then return end - self.Inames, self.Itypes, self.Idescs, self.Ins = SplitTable(self.Ins, self) - self.Onames, self.Otypes, self.Odescs, self.Outs = SplitTable(self.Outs, self) + if self:StoreHammerOutputs(key, value) then + return + end + + if key == "wire_entity_name" then + local oldValue = self.WireEntName or "" + self.WireEntName = value + + self.WireEntNameUpdated = oldValue ~= value + return + end + + if key == "min_trigger_time" then + self.MinTriggerTime = math.max(tonumber(value or 0) or 0, 0) + return + end + + if self:RegisterWireIO(key, value) then + return + end end function ENT:Think() - if (not WireAddon) then return end - - local ALLOW_INTERFACE_bool = ALLOW_INTERFACE:GetBool() - if ((self.Active ~= self.oldActive) or (ALLOW_INTERFACE_bool ~= self.old_ALLOW_INTERFACE_bool)) then - if (self.Active and ALLOW_INTERFACE_bool) then - self.SavedIn = self.SavedIn or {} - for name, values in pairs(self.SavedIn) do - self:TriggerWireInput(name, unpack(values)) - self.SavedIn[name] = nil + local active = self:IsActive() + + if active ~= self.oldIsActive then + if active then + self:ApplyWireOutputBufferAll() + end + + self.oldIsActive = active + end + + if active then + local wireInputTriggerBuffer = self.WireInputTriggerBuffer + local wireInputRegister = self.WireInputRegister + local now = CurTime() + + for uid, triggerStateData in pairs(wireInputTriggerBuffer) do + if wireInputRegister.byUid[uid] then + local inputData = triggerStateData.inputData + local wireValue = triggerStateData.wireValue + local wireEnt = triggerStateData.wireEnt + + local debounce = inputData.debounce + local nextTime = debounce.nextTime or 0 + + if nextTime <= now then + self:TriggerHammerOutputFromWire(inputData, wireValue, wireEnt) + wireInputTriggerBuffer[uid] = nil + end + else + wireInputTriggerBuffer[uid] = nil end end - self.oldActive = self.Active - self.old_ALLOW_INTERFACE_bool = ALLOW_INTERFACE_bool end -end -function ENT:OnRemove() - if (not WireAddon) then return end + self:HandleWireEntsUpdated() + self:HandleShouldNetworkEntities() + self:HandlePortsUpdated() + self:HandleWireEntNameUpdated() - self.flags = self:GetSpawnFlags() + self:PollWirelinkStatus() - if (bit.band(self.flags, 4) > 0) then -- Remove in-/output entities on remove - for obj1, obj2 in pairs(self.WireEnts or {}) do - local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + self:NextThink(CurTime() + self.MIN_THINK_TIME) + return true +end + +function ENT:OnRemove() + local wireEnts = self:GetWiredEntities() - if (IsValid(Entity)) then - SafeRemoveEntity(Entity) + if self:FlagGetRemoveEntities() then + for _, wireEnt in ipairs(wireEnts) do + if wireEnt:IsValid() and not wireEnt:IsMarkedForDeletion() and wireEnt:CreatedByMap() then + wireEnt:Remove() end end else self:RemoveAllEntities() end + + table.Empty(wireEnts) end + diff --git a/lua/entities/info_wiremapinterface/io.lua b/lua/entities/info_wiremapinterface/io.lua new file mode 100644 index 0000000000..15e224e487 --- /dev/null +++ b/lua/entities/info_wiremapinterface/io.lua @@ -0,0 +1,936 @@ +-- I/O code between Hammer and Wiremod + +local WireLib = WireLib + +local function newPortRegister() + local register = {} + + register.byPortId = {} + register.byName = {} + register.byUid = {} + register.sequence = {} + + register.wire = {} + register.wire.names = {} + register.wire.types = {} + register.wire.descs = {} + + register.add = function(this, port) + if not port then + return + end + + local portId = port.portId + if not portId then + return + end + + local name = port.name or "" + if name == "" then + return + end + + local uid = port.uid + if not uid then + return + end + + local byPortId = this.byPortId + local byName = this.byName + local byUid = this.byUid + local sequence = this.sequence + + if byUid[uid] and byUid[uid].uid == uid then + return + end + + local typeName = port.typeName + local desc = port.desc or "" + + port = table.Copy(port) + + byPortId[portId] = port + byName[name] = port + byUid[uid] = port + + sequence[#sequence + 1] = port + + local wire = this.wire + local names = wire.names + local types = wire.types + local descs = wire.descs + + local index = #names + 1 + names[index] = name -- The port name + types[index] = typeName -- The port type + descs[index] = desc -- The port description + end + + register.empty = function(this) + table.Empty(this.byPortId) + table.Empty(this.byName) + table.Empty(this.byUid) + table.Empty(this.sequence) + + local wire = register.wire + table.Empty(wire.names) + table.Empty(wire.types) + table.Empty(wire.descs) + end + + register.hasPorts = function(this) + local sequence = this.sequence + return #sequence > 0 + end + + return register +end + +function ENT:HandlePortsUpdated() + if not self.PortsUpdated then + return + end + + self.WireEntNameUpdated = true + + self:UpdatePorts() + + self.PortsUpdated = nil +end + +-- To cleanup and get the in-/outputs information. +local function copyAndSanitizeToPortRegister(register, registerTmp) + register = register or newPortRegister() + register:empty() + + if not registerTmp then + return register + end + + for i = 1, #registerTmp do + -- Ensure to keep things ordered, even with gaps + + local port = registerTmp[i] + register:add(port) + end + + return register +end + +function ENT:UpdatePorts() + local wireInputRegister = self.WireInputRegister + local wireOutputRegister = self.WireOutputRegister + + wireInputRegister = copyAndSanitizeToPortRegister(wireInputRegister, self.WireInputRegisterTmp) + wireOutputRegister = copyAndSanitizeToPortRegister(wireOutputRegister, self.WireOutputRegisterTmp) + + self.WireInputRegister = wireInputRegister + self.WireOutputRegister = wireOutputRegister + + local wireEnts = self:GetWiredEntities() + + for key, wireEnt in ipairs(wireEnts) do + wireEnt:_WMI_AddPorts(self.WireInputRegister, self.WireOutputRegister) + end +end + +local g_hashTmp = {} + +function ENT:GetPortUid(port) + if not port then + return nil + end + + local name = port.name or "" + local portId = port.portId + local portType = port.type + + if name == "" then + return nil + end + + if not portId then + return nil + end + + if not portType then + return nil + end + + table.Empty(g_hashTmp) + + g_hashTmp[1] = "WMI_" + g_hashTmp[2] = self:GetCreationID() + g_hashTmp[3] = self:GetCreationTime() + g_hashTmp[4] = name + g_hashTmp[5] = portId + g_hashTmp[6] = portType + + return util.SHA1(table.concat(g_hashTmp, "_")) +end + +function ENT:PrepairOutputGlobals(inputData, wireValue, wireEnt, wireDevice, owner) + if not IsValid(wireEnt) then + return + end + + -- This can be usefull if a lua_run entity is triggered + -- Because entity.Fire and entity.AcceptInput are not run synchronously in the same frame, we use them in a custom entity.AcceptInput detour. + -- So we store them for later use in a custom entity.AcceptInput detour. + + local name = inputData.name + local typeName = inputData.typeName + local wired = inputData.wiredStateTotal + + local globals = { + WIRE_NAME = name, -- Input name. + WIRE_TYPE = typeName, -- Input type (NORMAL, STRING, VECTOR, etc.) + WIRE_VALUE = wireValue, -- Input value. + WIRE_WIRED = wired, -- Is the input wired? + WIRE_CALLER = self, -- This entity. + WIRE_ACTIVATOR = wireEnt, -- The entity that has the Wire input. + WIRE_DEVICE = wireDevice, -- The entity where the input data was from, e.g. a Wiremod button. + WIRE_OWNER = owner, -- The owner of the input device, e.g the player who spawned the Wiremod button. + } + + local hammerOutputName = inputData.hammerOutputName + local hammerResetOutputName = inputData.hammerResetOutputName + + self._OutputGlobals = self._OutputGlobals or {} + self._OutputGlobals[hammerOutputName] = globals + self._OutputGlobals[hammerResetOutputName] = globals +end + +function ENT:SetupOutputGlobals(globals) + -- This can be usefull if a lua_run entity is triggered + + local G = _G + + globals._old = { + -- In the case the call might be nested, or some other shenanigans happen, we store the old globals for restore after use. + + WIRE_NAME = G.WIRE_NAME, + WIRE_VALUE = G.WIRE_VALUE, + WIRE_TYPE = G.WIRE_TYPE, + WIRE_WIRED = G.WIRE_WIRED, + WIRE_CALLER = G.WIRE_CALLER, + WIRE_ACTIVATOR = G.WIRE_ACTIVATOR, + WIRE_DEVICE = G.WIRE_DEVICE, + WIRE_OWNER = G.WIRE_OWNER, + } + + G.WIRE_NAME = globals.WIRE_NAME + G.WIRE_VALUE = globals.WIRE_VALUE + G.WIRE_TYPE = globals.WIRE_TYPE + G.WIRE_WIRED = globals.WIRE_WIRED + G.WIRE_CALLER = globals.WIRE_CALLER + G.WIRE_ACTIVATOR = globals.WIRE_ACTIVATOR + G.WIRE_DEVICE = globals.WIRE_DEVICE + G.WIRE_OWNER = globals.WIRE_OWNER +end + +function ENT:KillOutputGlobals(globals) + local oldGlobals = globals and globals._old + + local G = _G + + if oldGlobals then + G.WIRE_NAME = oldGlobals.WIRE_NAME + G.WIRE_VALUE = oldGlobals.WIRE_VALUE + G.WIRE_TYPE = oldGlobals.WIRE_TYPE + G.WIRE_WIRED = oldGlobals.WIRE_WIRED + G.WIRE_CALLER = oldGlobals.WIRE_CALLER + G.WIRE_ACTIVATOR = oldGlobals.WIRE_ACTIVATOR + G.WIRE_DEVICE = oldGlobals.WIRE_DEVICE + G.WIRE_OWNER = oldGlobals.WIRE_OWNER + + globals._old = nil + else + G.WIRE_NAME = nil + G.WIRE_VALUE = nil + G.WIRE_TYPE = nil + G.WIRE_WIRED = nil + G.WIRE_CALLER = nil + G.WIRE_ACTIVATOR = nil + G.WIRE_DEVICE = nil + G.WIRE_OWNER = nil + end +end + +local function deturedAcceptInput(this, name, activator, caller, data, ...) + local wmidata = this._WireMapInterfaceEnt_FirePrepairData + + if not wmidata then + return false + end + + local oldAcceptInput = wmidata.AcceptInput + if not oldAcceptInput then + return false + end + + local globals = wmidata.globals + + if not globals then + this.AcceptInput = oldAcceptInput + return oldAcceptInput(this, name, activator, caller, data, ...) + end + + if not IsValid(caller) or not caller.IsWireMapInterface then + return oldAcceptInput(this, name, activator, caller, data, ...) + end + + -- We detoured the AcceptInput hook of this entity to make our WIRE_* globals (for lua_run) available during the input call. + caller:SetupOutputGlobals(globals) + + local status, errOrResult = pcall(oldAcceptInput, this, name, activator, caller, data, ...) + + if not status then + errOrResult = tostring(errOrResult or "") + + if errOrResult ~= "" then + message = caller:FormatString(": Lua error in target AcceptInput:\n Wire input '%s [%s]' -> Hammer output '%s@%s'\n %s", globals.WIRE_NAME, globals.WIRE_TYPE, caller:FormatEntityString(this), name, errOrResult) + ErrorNoHaltWithStack(message) + end + + return false + end + + caller:KillOutputGlobals(globals) + + return errOrResult +end + +function ENT:PrepairEntityForFire(targetEnt, outputName) + -- Because entity.Fire and entity.AcceptInput are not run synchronously in the same frame, we use add a custom entity.AcceptInput detour to lua_run entities. + + if not self._OutputGlobals then + return + end + + local globals = self._OutputGlobals[outputName] + if not globals then + return + end + + local wmidata = targetEnt._WireMapInterfaceEnt_FirePrepairData or {} + targetEnt._WireMapInterfaceEnt_FirePrepairData = wmidata + + wmidata.globals = globals + + local oldAcceptInput = wmidata.AcceptInput or targetEnt.AcceptInput + wmidata.AcceptInput = oldAcceptInput + + targetEnt.AcceptInput = deturedAcceptInput +end + +function ENT:TriggerWireOutputSafe(wireEnt, wireOutputName, wireValue, ...) + if not IsValid(wireEnt) then + return false + end + + local status, err = pcall(WireLib.TriggerOutput, wireEnt, wireOutputName, wireValue, ...) + + if status then + return true + end + + err = tostring(err or "") + + if err ~= "" then + message = self:FormatString(": Lua error at Wire output '%s':\n%s", wireOutputName, err) + ErrorNoHaltWithStack(message) + end + + return false +end + +function ENT:TriggerHammerOutputSafe(hammerOutputName, activator, hammerValue, ...) + if not IsValid(activator) then + return false + end + + local status, err = pcall(self.TriggerOutput, self, hammerOutputName, activator, hammerValue, ...) + if status then + return true + end + + err = tostring(err or "") + + if err ~= "" then + message = self:FormatString(": Lua error at Hammer output '%s':\n%s", hammerOutputName, err) + ErrorNoHaltWithStack(message) + end + + return false +end + +function ENT:ApplyWireOutputBufferSingle(wireEnt) + local wireOutputRegister = self.WireOutputRegister + + if not wireOutputRegister then + return + end + + for _, outputData in pairs(wireOutputRegister.sequence) do + local wireValue = outputData.bufferedWireValue + + self:TriggerWireOutputSingle(wireEnt, outputData, wireValue) + end +end + +function ENT:ApplyWireOutputBufferAll() + local wireOutputRegister = self.WireOutputRegister + + if not wireOutputRegister then + return + end + + for _, outputData in pairs(wireOutputRegister.sequence) do + local wireValue = outputData.bufferedWireValue + + self:TriggerWireOutputAll(outputData, wireValue) + end +end + +-- Wire input +function ENT:TriggerWireInput(wireInputName, wireValue, wired, wireEnt) + if not IsValid(wireEnt) then return end + + local wireInputRegister = self.WireInputRegister + if not wireInputRegister then + return + end + + local inputData = wireInputRegister.byName[wireInputName] + if not inputData then + return + end + + local portId = inputData.portId + if not self:CheckPortIdLimit(portId, false) then + return + end + + local uid = inputData.uid + local wireInputTriggerBuffer = self.WireInputTriggerBuffer + + local wiredState = inputData.wiredState or {} + inputData.wiredState = wiredState + + local oldWired = wiredState[wireEnt] or false + inputData.wiredStateTotalChanged = false + + if oldWired ~= wired then + inputData.wiredStateTotal = nil + inputData.wiredStateTotalChanged = true + + if wired then + wiredState[wireEnt] = true + else + wiredState[wireEnt] = nil + end + end + + -- Check if any of known entities are connected and cache it. + if inputData.wiredStateTotal == nil then + local wiredStateTotal = false + + for ent, thisWiredState in pairs(wiredState) do + if not IsValid(ent) then + thisWiredState = false + wiredState[ent] = nil + end + + if thisWiredState then + wiredStateTotal = true + end + end + + inputData.wiredStateTotal = wiredStateTotal + end + + -- The wired/unwired state across all entities as been changed. + local wiredStateTotalChanged = inputData.wiredStateTotalChanged or false + + if not self:IsActive() then + -- Keep the last given trigger if turned off, so we apply it after it was turned on. + + local triggerStateData = wireInputTriggerBuffer[uid] or {} + wireInputTriggerBuffer[uid] = triggerStateData + + triggerStateData.inputData = inputData + triggerStateData.wireValue = wireValue + triggerStateData.wireEnt = wireEnt + + return + end + + local debounce = inputData.debounce or {} + inputData.debounce = debounce + + if not wiredStateTotalChanged then + local oldWireValue = debounce.oldWireValue + + if oldWireValue ~= nil and self:IsEqualWireValue(inputData.type, oldWireValue, wireValue) then + return + end + + local nextTime = debounce.nextTime or 0 + if nextTime > CurTime() then + -- Keep the last given trigger during cool down, so we apply it after next tick, in case the signal "misses the bus". + + local triggerStateData = wireInputTriggerBuffer[uid] or {} + wireInputTriggerBuffer[uid] = triggerStateData + + triggerStateData.inputData = inputData + triggerStateData.wireValue = wireValue + triggerStateData.wireEnt = wireEnt + + return + end + end + + wireInputTriggerBuffer[uid] = nil + self:TriggerHammerOutputFromWire(inputData, wireValue, wireEnt) +end + +-- Wire output +function ENT:TriggerWireOutput(portId, hammerValue) + if not self:CheckPortIdLimit(portId, false) then + return + end + + local outputData = self.WireOutputRegister.byPortId[portId] + if not outputData then + return + end + + local wireOutputType = outputData.type + + local convertFunc, isToggle = self:GetMapToWireConverter(wireOutputType) + if not convertFunc then + return + end + + if isToggle then + -- Toggle state each time the output is triggered + outputData.toggleState = not outputData.toggleState + hammerValue = outputData.toggleState + end + + local debounce = outputData.debounce or {} + outputData.debounce = debounce + + local oldHammerValue = debounce.oldHammerValue + if oldHammerValue ~= nil and hammerValue == oldHammerValue then + return + end + + local wireValue = convertFunc(self, hammerValue) + outputData.bufferedWireValue = wireValue + + if not self:IsActive() then + return + end + + local oldWireValue = debounce.oldWireValue + if oldWireValue ~= nil and self:IsEqualWireValue(wireOutputType, oldWireValue, wireValue) then + return + end + + self:TriggerWireOutputAll(outputData, wireValue) + + debounce.oldHammerValue = hammerValue + debounce.oldWireValue = wireValue +end + +function ENT:TriggerWireOutputSingle(wireEnt, outputData, wireValue) + if not outputData then + return + end + + if wireValue == nil then + return + end + + local wireOutputName = outputData.name + + self:TriggerWireOutputSafe(wireEnt, wireOutputName, wireValue) +end + +function ENT:TriggerWireOutputAll(outputData, wireValue) + if not outputData then + return + end + + if wireValue == nil then + return + end + + local wireOutputName = outputData.name + local wireEnts = self:GetWiredEntities() + + for _, wireEnt in ipairs(wireEnts) do + self:TriggerWireOutputSafe(wireEnt, wireOutputName, wireValue) + end +end + +-- Hammer input +function ENT:TriggerHammerInput(name, data) + local portId = tonumber(string.match(name, "triggerwireoutput(%d+)")) or 0 + if self:CheckPortIdLimit(portId, false) then + self:TriggerWireOutput(portId, data) + return true + end + + local caller = self._lastCaller + + if name == "addentity" then + self:AddSingleEntity(caller) + return true + end + + if name == "removeentity" then + self:RemoveSingleEntity(caller) + return true + end + + if name == "addentities" then + self:AddEntitiesByName(data) + return true + end + + if name == "removeentities" then + self:RemoveEntitiesByName(data) + return true + end + + if name == "removeallentities" then + self:RemoveAllEntities() + return true + end + + return false +end + +-- Hammer output +function ENT:TriggerHammerOutputFromWire(inputData, wireValue, wireEnt) + if not inputData then + return + end + + local uid = inputData.uid + local wireConnectionChange = inputData.wiredStateTotalChanged or false + local wired = inputData.wiredStateTotal or false + local hammerOutputName = inputData.hammerOutputName + local hammerResetOutputName = inputData.hammerResetOutputName + + -- wireEnt gets the ownership of connected device + local wireDevice = wireEnt:_WMI_GetInputDevice() or NULL + local owner = IsValid(wireDevice) and WireLib.GetOwner(wireDevice) or NULL + + self._lastWireInputEnt = wireEnt + self._lastWireDeviceEnt = wireDevice + self._lastWireDeviceEntOwner = owner + + local convertFunc, isToggle = self:GetWireToMapConverter(inputData.type) + if not convertFunc then + return + end + + if isToggle then + wireValue = tobool(wireValue) and 1 or 0 + end + + local hammerValue = convertFunc(self, wireValue) + + local debounce = inputData.debounce or {} + inputData.debounce = debounce + + if not wireConnectionChange then + local oldHammerValue = debounce.oldHammerValue + if oldHammerValue ~= nil and hammerValue == oldHammerValue then + return + end + end + + self:PrepairOutputGlobals(inputData, wireValue, wireEnt, wireDevice, owner) + + if not wired then + if wireConnectionChange then + if not isToggle then + -- Do not trigger with "zero" on reset if we are in toggle mode + self:TriggerHammerOutputSafe(hammerOutputName, wireEnt, hammerValue) + end + + self:TriggerHammerOutputSafe(hammerResetOutputName, wireEnt) + end + else + if isToggle then + if hammerValue then + -- Will only trigger if this value is true and if it was false before + self:TriggerHammerOutputSafe(hammerOutputName, wireEnt) + end + else + self:TriggerHammerOutputSafe(hammerOutputName, wireEnt, hammerValue) + end + end + + self.WireInputTriggerBuffer[uid] = nil + + debounce.oldWireValue = wireValue + debounce.oldHammerValue = hammerValue + debounce.nextTime = CurTime() + self:GetMinTriggerTime() + + inputData.wiredStateTotalChanged = false +end + +-- Hammer keyvalues +function ENT:StoreHammerOutputs(key, value) + local portId = tonumber(string.match(key, "onwireinput(%d+)")) or 0 + if self:CheckPortIdLimit(portId, true) then + self:StoreOutput(key, value) + return true + end + + local portId = tonumber(string.match(key, "onresetwireinput(%d+)")) or 0 + if self:CheckPortIdLimit(portId, true) then + self:StoreOutput(key, value) + return true + end + + if key == "onwireentscreated" then + self:StoreOutput(key, value) + return true + end + + if key == "onwireentsremoved" then + self:StoreOutput(key, value) + return true + end + + if key == "onwireentsready" then + self:StoreOutput(key, value) + return true + end + + if key == "onwireentsstartchanging" then + self:StoreOutput(key, value) + return true + end + + return false +end + +local g_blacklistedPortNames = { + wirelink = true, + link = true, + entity = true, +} + +function ENT:RegisterWireInputs(key, value) + local portId, name = string.match(key, "input(%d+)_(%w+)") + + if not portId then + return + end + + portId = tonumber(portId or 0) or 0 + name = tostring(name or "") + + if not self:CheckPortIdLimit(portId, true) then + return false + end + + if name == "" then + return false + end + + local wireInputRegisterTmp = self.WireInputRegisterTmp or {} + self.WireInputRegisterTmp = wireInputRegisterTmp + + local inputData = wireInputRegisterTmp[portId] or {} + wireInputRegisterTmp[portId] = inputData + + if name == "lua" then + -- Used to run given Lua codes. + -- It is no longer supported as it is considered as unsafe. + + if value ~= "" then + inputData.warnAboutLua = true + end + elseif name == "type" then + inputData.type = tonumber(value) or 0 + inputData.typeName = self:GetWireTypenameByTypeId(inputData.type) + inputData.zeroWireValue = WireLib.GetDefaultForType(inputData.typeName) + elseif name == "desc" then + inputData.desc = value + elseif name == "name" then + if value ~= "" then + if not g_blacklistedPortNames[value] then + inputData.name = value + inputData.portId = portId + + inputData.hammerOutputName = "OnWireInput" .. portId + inputData.hammerResetOutputName = "OnResetWireInput" .. portId + + if not inputData.type then + inputData.type = 0 + inputData.typeName = self:GetWireTypenameByTypeId(inputData.type) + inputData.zeroWireValue = WireLib.GetDefaultForType(inputData.typeName) + end + else + table.Empty(inputData) + self:PrintWarning(": Can not add input '%s', as the name is reserved.", value) + end + else + table.Empty(inputData) + end + end + + if inputData.name then + if inputData.warnAboutLua then + self:PrintWarning(", input '%s [%s]': Running Lua code is no longer supported! Trigger an lua_run entity instead.", inputData.name, inputData.typeName) + inputData.warnAboutLua = nil + end + + inputData.uid = self:GetPortUid(inputData) + self.PortsUpdated = true + end + + return true +end + +function ENT:RegisterWireOutputs(key, value) + local portId, name = string.match(key, "output(%d+)_(%w+)") + + if not portId then + return + end + + portId = tonumber(portId or 0) or 0 + name = tostring(name or "") + + if not self:CheckPortIdLimit(portId, true) then + return false + end + + if name == "" then + return false + end + + local wireOutputRegisterTmp = self.WireOutputRegisterTmp or {} + self.WireOutputRegisterTmp = wireOutputRegisterTmp + + local outputData = wireOutputRegisterTmp[portId] or {} + wireOutputRegisterTmp[portId] = outputData + + if name == "type" then + outputData.type = tonumber(value) + outputData.typeName = self:GetWireTypenameByTypeId(outputData.type) + outputData.zeroWireValue = WireLib.GetDefaultForType(outputData.typeName) + elseif name == "desc" then + outputData.desc = value + elseif name == "name" then + if value ~= "" then + if not g_blacklistedPortNames[value] then + outputData.name = value + outputData.portId = portId + + if not outputData.type then + outputData.type = 0 + outputData.typeName = self:GetWireTypenameByTypeId(outputData.type) + outputData.zeroWireValue = WireLib.GetDefaultForType(outputData.typeName) + end + else + table.Empty(outputData) + self:PrintWarning(": Can not add output '%s', as the name is reserved.", value) + end + else + table.Empty(outputData) + end + end + + if outputData.name then + outputData.uid = self:GetPortUid(outputData) + self.PortsUpdated = true + end + + return true +end + +function ENT:RegisterWireIO(key, value) + if self:RegisterWireInputs(key, value) then + return true + end + + if self:RegisterWireOutputs(key, value) then + return true + end + + return false +end + +function ENT:IsConnectedWirelink() + local wireEnts = self:GetWiredEntities() + + for _, wireEnt in ipairs(wireEnts) do + local IsConnectedWirelink = wireEnt._WMI_IsConnectedWirelink + + if IsConnectedWirelink and IsConnectedWirelink(wireEnt) then + return true + end + end + + return false +end + +function ENT:PollWirelinkStatus() + if not self:IsActive() then + return + end + + local now = CurTime() + local nextWirelinkPoll = self.NextWirelinkPoll or 0 + + if nextWirelinkPoll > now then + return + end + + local isWirelinked = self:IsConnectedWirelink() + + local oldIsWirelinked = self.oldIsWirelinked or false + local wirelinkChanged = oldIsWirelinked ~= isWirelinked + self.oldIsWirelinked = isWirelinked + + if wirelinkChanged and not isWirelinked then + self:UnWirelinkAllWireInputs() + end + + self.NextWirelinkPoll = now + self.MIN_THINK_TIME * 8 +end + +function ENT:UnWirelinkAllWireInputs() + local wireEnts = self:GetWiredEntities() + local wireInputRegister = self.WireInputRegister + + if not wireInputRegister then + return + end + + for _, wireEnt in ipairs(wireEnts) do + for _, inputData in ipairs(wireInputRegister.sequence) do + local name = inputData.name + + local wireValue = wireEnt:_WMI_GetDirectLinkedInputValue(name) + + if wireValue == nil then + wireValue = inputData.zeroWireValue + end + + wireEnt:TriggerInput(name, wireValue) + end + end +end + diff --git a/lua/entities/info_wiremapinterface/networking.lua b/lua/entities/info_wiremapinterface/networking.lua new file mode 100644 index 0000000000..9b7a71c584 --- /dev/null +++ b/lua/entities/info_wiremapinterface/networking.lua @@ -0,0 +1,96 @@ +-- Networking functions + +util.AddNetworkString("WireMapInterfaceEntities") + +local function resetNetworking(ply) + local wireMapInterfaceEntities = ents.FindByClass("info_wiremapinterface") + for i, ent in ipairs(wireMapInterfaceEntities) do + local delay = 1 + i / 2 + + -- Trigger a networking call in a staggered + debounced matter. + local nextNetworkTime = math.max(ent.NextNetworkTime, CurTime() + delay) + ent:RequestNetworkEntities(ply, nextNetworkTime) + end +end + +gameevent.Listen("player_activate") +hook.Add("player_activate", "WireMapInterface_PlayerActivate", function(data) + -- Make sure the newly spawned player is ready for networking. + -- Networking in PlayerInitialSpawn is said to be unreliable. + + local ply = Player(data.userid) + resetNetworking(ply) +end) + +hook.Add("PostCleanupMap", "WireMapInterface_PostCleanupMap_SV", function() + -- Reset networking on map clear. It repairs it in case it got desynced. + resetNetworking() +end) + +function ENT:HandleShouldNetworkEntities() + if not self.ShouldNetworkEntities then + return + end + + local now = CurTime() + local nextNetworkTime = self.NextNetworkTime + + if not nextNetworkTime or nextNetworkTime > now then + -- Debounce the network calls. + -- The client does not need the changed data that often. + return + end + + self:NetworkWireEntities() + self:AttachToSaveStateEntity() + + self.ShouldNetworkEntities = false + self.NextNetworkTime = now + self.MIN_THINK_TIME * 4 +end + +function ENT:RequestNetworkEntities(ply, networkAtTime) + self.ShouldNetworkEntities = true + + if networkAtTime then + self.NextNetworkTime = networkAtTime + end + + local recipientFilter = self.NetworkRecipientFilter + + if not IsValid(ply) then + recipientFilter:AddAllPlayers() + return + end + + recipientFilter:AddPlayer(ply) +end + +function ENT:NetworkWireEntities() + -- Network the list and properties of the wire entities. + -- We need we know about them on the client. For cable rendering, tools etc. + + local recipientFilter = self.NetworkRecipientFilter + + if recipientFilter:GetCount() <= 0 then + return + end + + net.Start("WireMapInterfaceEntities") + + net.WriteUInt(self:EntIndex(), MAX_EDICT_BITS) + net.WriteBool(self:FlagGetProtectFromTools()) + net.WriteBool(self:FlagGetProtectFromPhysgun()) + net.WriteBool(self:FlagGetRenderWires()) + net.WriteUInt(self:GetWiredEntityCount(), 6) + + local wireEnts = self:GetWiredEntities() + + for _, wireEnt in ipairs(wireEnts) do + net.WriteUInt(wireEnt:EntIndex(), MAX_EDICT_BITS) + end + + net.Send(recipientFilter) + + recipientFilter:RemoveAllPlayers() +end + diff --git a/lua/entities/info_wiremapinterface/savestate.lua b/lua/entities/info_wiremapinterface/savestate.lua new file mode 100644 index 0000000000..69d1a0f98e --- /dev/null +++ b/lua/entities/info_wiremapinterface/savestate.lua @@ -0,0 +1,308 @@ +-- Dupe and save support and validation + +local WireLib = WireLib +local WireMapInterfaceLookup = WireLib.WireMapInterfaceLookup + +local g_saveStateEntity = nil +local g_mapName = nil + +function ENT:GetSaveStateEntity() + if IsValid(g_saveStateEntity) and not g_saveStateEntity:IsMarkedForDeletion() then + return g_saveStateEntity + end + + g_saveStateEntity = nil + + local entities = ents.FindByClass("info_wiremapinterface_savestate") + for _, ent in ipairs(entities) do + if IsValid(ent) and not ent:IsMarkedForDeletion() then + g_saveStateEntity = ent + return g_saveStateEntity + end + end + + local ent = ents.Create("info_wiremapinterface_savestate") + if not IsValid(ent) then + return nil + end + + ent:SetPos(self:GetPos()) + ent:Spawn() + ent:Activate() + + g_saveStateEntity = ent + return ent +end + +function ENT:AttachToSaveStateEntity() + local saveState = self:GetSaveStateEntity() + if IsValid(saveState) then + saveState:AddInterface(self) + end +end + +function ENT:GetSubEntityByIdCombo(entIdx, mapId, spawnId, createdEntities) + if not spawnId then + return nil + end + + entIdx = tonumber(entIdx) + mapId = tonumber(mapId) + + if not WireLib.WireMapInterfaceValidateId(entIdx) then + return nil + end + + if not WireLib.WireMapInterfaceValidateId(mapId) then + return nil + end + + if self:HashSubEntityMapId(entIdx, mapId) ~= spawnId then + return nil + end + + local wireEnt = nil + + if createdEntities then + wireEnt = createdEntities[entIdx] + end + + if not createdEntities or not IsValid(wireEnt) then + wireEnt = WireMapInterfaceLookup:getBySpawnIDDuped(spawnId) + + if not IsValid(wireEnt) then + wireEnt = WireMapInterfaceLookup:getByMapID(mapId) + + if not IsValid(wireEnt) then + wireEnt = ents.GetMapCreatedEntity(mapId) + + if not IsValid(wireEnt) then + wireEnt = WireMapInterfaceLookup:getBySpawnID(spawnId) + + if not IsValid(wireEnt) then + return nil + end + end + end + end + end + + return wireEnt +end + +function ENT:OnSaveCopy() + if not self:CreatedByMap() then + return nil + end + + local entries = {} + local wireEnts = self:GetWiredEntities() + + for _, wireEnt in ipairs(wireEnts) do + local wireEntSpawnId = self:GetSubEntitySpawnId(wireEnt) + + if wireEntSpawnId then + local entList = entries[wireEntSpawnId] or {} + entries[wireEntSpawnId] = entList + + entList[wireEnt:EntIndex()] = wireEnt:_WMI_BuildDupeData(self) + end + end + + local saveData = { + spawnId = self:GetSpawnId(), + mapId = self:MapCreationID(), + entries = entries, + } + + return saveData +end + +function ENT:OnDuplicatedSave(saveData) + if not self:CreatedByMap() then + return + end + + if not saveData.entries then + return + end + + for wireEntSpawnId, entList in pairs(saveData.entries) do + for entIdx, dupeData in pairs(entList) do + local wireMapInterfaceEntDupeInfo = dupeData.wireMapInterfaceEntDupeInfo + + if wireMapInterfaceEntDupeInfo then + local mapId = wireMapInterfaceEntDupeInfo.mapId + local wireEnt = self:GetSubEntityByIdCombo(entIdx, mapId, wireEntSpawnId) + + if IsValid(wireEnt) and not wireEnt._WMI_ApplyDupeData then + self:OverrideEntFromDupe(wireEnt, wireMapInterfaceEntDupeInfo) + end + end + end + end +end + +function ENT:OnSavePaste(ply, saveData, createdEntities) + if not self:CreatedByMap() then + return + end + + if not saveData.entries then + return + end + + for wireEntSpawnId, entList in pairs(saveData.entries) do + for entIdx, dupeData in pairs(entList) do + local wireMapInterfaceEntDupeInfo = dupeData.wireMapInterfaceEntDupeInfo + + if wireMapInterfaceEntDupeInfo then + local mapId = wireMapInterfaceEntDupeInfo.mapId + local wireEnt = self:GetSubEntityByIdCombo(entIdx, mapId, wireEntSpawnId, createdEntities) + + if IsValid(wireEnt) and wireEnt._WMI_ApplyDupeData then + wireEnt:_WMI_ApplyDupeData(ply, dupeData, createdEntities, self) + end + end + end + end +end + +local function buildSpawnId(...) + if not g_mapName then + g_mapName = game.GetMap() + end + + local tmp = {...} + + tmp = table.concat(tmp, "_") + + local id = string.format( + "WMI_%s_%s_buildSpawnId", + g_mapName, + tmp + ) + + -- This not fire proof security, but this is more of a user convenience thing than anything else. + -- It just prevents unexpected issues from map foreign dupes made by user error. + -- This is used to check if the dupe/save belongs to the map and the particular Wire Map Interface instance. + -- Entities not passing the validation will still be pasted and spawned, but they will not get any addional Wiremod functionalities whatsoever. + -- The validation might not pass across map recompiles, especially if MapCreationIDs change. + + -- In theroy it would be quite possible to crack or bypass this validation with a forged dupe/save file. + -- For that you just would need to know how was hashed and how the targeted map has been built. + + -- However in practice it would be quite tedious to do so and it would also only be useful if the map has a vulnerability. + -- In this context the risks a rigged dupe file can pose are quite minimal. + + id = util.SHA1(id) + return id +end + +function ENT:HashMapId(interfaceMapId) + interfaceMapId = tonumber(interfaceMapId) + + if not WireLib.WireMapInterfaceValidateId(interfaceMapId) then + return nil + end + + -- Get a compact and unique spawn identifier per map and interface. + local id = buildSpawnId( + interfaceMapId, + "HashMapId" + ) + + return id +end + +function ENT:HashSubEntityMapId(subEntIdx, subEntMapId) + if not self:CreatedByMap() then + return nil + end + + subEntIdx = tonumber(subEntIdx) + subEntMapId = tonumber(subEntMapId) + + if not WireLib.WireMapInterfaceValidateId(subEntIdx) then + return nil + end + + if not WireLib.WireMapInterfaceValidateId(subEntMapId) then + return nil + end + + -- Get a compact and unique spawn identifier per map, interface and sub entity. + local id = buildSpawnId( + self:MapCreationID(), + subEntIdx, + subEntMapId, + "HashSubEntityMapId" + ) + + return id +end + +function ENT:GetSpawnId() + if not self:CreatedByMap() then + return nil + end + + if self.SpawnId then + return self.SpawnId + end + + local id = self:HashMapId(self:MapCreationID()) + + self.SpawnId = id + return id +end + +function ENT:GetSubEntitySpawnId(subEnt) + if not IsValid(subEnt) then + return nil + end + + if not subEnt._WMI_GetSpawnId then + return false + end + + local spawnId = subEnt:_WMI_GetSpawnId(self) + if not spawnId then + return nil + end + + return spawnId +end + +function ENT:ValidateDupedMapId(mapCreationID, spawnIdC) + if not mapCreationID then + return false + end + + local spawnIdA = self:GetSpawnId() + if not spawnIdA then + return false + end + + local spawnIdB = self:HashMapId(mapCreationID) + if not spawnIdB then + return false + end + + if spawnIdA ~= spawnIdB then + return false + end + + if spawnIdC ~= nil then + if spawnIdA ~= spawnIdC then + return false + end + + if spawnIdB ~= spawnIdC then + return false + end + end + + return true +end + diff --git a/lua/entities/info_wiremapinterface_savestate/init.lua b/lua/entities/info_wiremapinterface_savestate/init.lua new file mode 100644 index 0000000000..309584118a --- /dev/null +++ b/lua/entities/info_wiremapinterface_savestate/init.lua @@ -0,0 +1,188 @@ +-- This is a helper entity to store save state data wire map interface entities. (info_wiremapinterface_savestate) +-- Only one per map will be spawned during run time. This is needed because info_wiremapinterface can not and also must not be duplicated/saved. +-- When a game is saved, this entity will be save along with it. +-- When the entity is restored, it deploys its saved data to all interface entities it knows about. + +ENT.Base = "base_point" +ENT.Type = "point" + +ENT.Spawnable = false +ENT.AdminOnly = true + +-- Needed for save game support +ENT.DisableDuplicator = false + +-- This entity is for saves only. +-- So block all tools, especially dublicator tools and its area copy feature. +-- This entity not traceable nor visible, so other tools would not matter. +ENT.m_tblToolsAllowed = {} + +local g_SaveStateEntity = nil +local g_interfaceEntities = {} +local g_saveState = {} + +function ENT:Initialize() + if self:CreatedByMap() then + self:Remove() + g_SaveStateEntity = nil + return + end + + if IsValid(g_SaveStateEntity) and g_SaveStateEntity ~= self then + -- Never allow more than one instance of this entity. + g_SaveStateEntity:Remove() + end + + g_SaveStateEntity = self +end + +function ENT:OnReloaded() + -- Easier for debugging. + self:Remove() + g_SaveStateEntity = nil + + local wireMapInterfaceEntities = ents.FindByClass("info_wiremapinterface") + for _, ent in ipairs(wireMapInterfaceEntities) do + if IsValid(ent) then + ent:OnReloaded() + end + end +end + +function ENT:AddInterface(interfaceEnt) + if not self:IsValidInterface(interfaceEnt) then + return + end + + local spawnId = interfaceEnt:GetSpawnId() + if not spawnId then + return + end + + if IsValid(g_interfaceEntities[spawnId]) then + return + end + + g_interfaceEntities[spawnId] = interfaceEnt +end + +function ENT:IsValidInterface(interfaceEnt) + if not IsValid(interfaceEnt) then + return false + end + + if not interfaceEnt.IsWireMapInterface then + return false + end + + if not interfaceEnt:CreatedByMap() then + return false + end + + return true +end + +function ENT:GetInterfaceByIdCombo(mapId, spawnId) + if not spawnId then + return nil + end + + mapId = tonumber(mapId or 0) or 0 + + if not WireLib.WireMapInterfaceValidateId(mapId) then + return nil + end + + local interfaceEnt = g_interfaceEntities[spawnId] + + if not self:IsValidInterface(interfaceEnt) then + interfaceEnt = ents.GetMapCreatedEntity(mapId) + + if not self:IsValidInterface(interfaceEnt) then + return nil + end + end + + if not interfaceEnt:ValidateDupedMapId(mapId, spawnId) then + return nil + end + + return interfaceEnt +end + +function ENT:PreEntityCopy() + if self:IsMarkedForDeletion() then + return + end + + duplicator.ClearEntityModifier(self, "WireMapInterfaceSaveStateInfo") + + local entries = {} + + for spawnId, interfaceEnt in pairs(g_interfaceEntities) do + if self:IsValidInterface(interfaceEnt) then + local saveData = interfaceEnt:OnSaveCopy() + + if saveData then + entries[spawnId] = saveData + + if not g_saveState[spawnId] then + g_saveState[spawnId] = saveData + end + end + else + g_interfaceEntities[spawnId] = nil + end + end + + duplicator.StoreEntityModifier(self, "WireMapInterfaceSaveStateInfo", { + interfaceEntries = entries + }) +end + +function ENT:OnDuplicated() + if self:IsMarkedForDeletion() then + return + end + + local entityMods = self.EntityMods + if not entityMods then + return + end + + local wireMapInterfaceSaveStateInfo = entityMods.WireMapInterfaceSaveStateInfo + if not wireMapInterfaceSaveStateInfo then + return + end + + local entries = wireMapInterfaceSaveStateInfo.interfaceEntries + if not entries then + return + end + + for spawnId, saveData in pairs(entries) do + local interfaceEnt = self:GetInterfaceByIdCombo(saveData.mapId, spawnId) + if interfaceEnt then + g_interfaceEntities[spawnId] = interfaceEnt + g_saveState[spawnId] = saveData + + interfaceEnt:OnDuplicatedSave(saveData) + end + end +end + +function ENT:PostEntityPaste(ply, ent, createdEntities) + if self:IsMarkedForDeletion() then + return + end + + for spawnId, saveData in pairs(g_saveState) do + local interfaceEnt = self:GetInterfaceByIdCombo(saveData.mapId, spawnId) + + if interfaceEnt then + g_interfaceEntities[spawnId] = interfaceEnt + interfaceEnt:OnSavePaste(ply, saveData, createdEntities) + end + end +end + diff --git a/lua/wire/client/cl_wire_map_interface.lua b/lua/wire/client/cl_wire_map_interface.lua index 6d8a7153ae..141a2f1bd7 100644 --- a/lua/wire/client/cl_wire_map_interface.lua +++ b/lua/wire/client/cl_wire_map_interface.lua @@ -1,89 +1,235 @@ --- The client part of the wire map interface. --- It's for the clientside wire ports adding and removing, also for the rendering stuff. --- It's in in this folder, because point entities are serverside only. - --- Removing wire stuff and other changes that were done. -local OverRiddenEnts = {} -local function RemoveWire(Entity) - if (not IsValid(Entity)) then return end +-- Clientside functionalities of Wire Map Interface +-- This is mostly for predection and rendering + +local g_wireTools = { + "wire", + "wire_adv", + "wire_debugger", + "wire_wirelink", + "multi_wire", +} + +local g_wiredEntities = {} +local g_wiredEntitiesRemove = {} +local g_nextThink = 0 +local g_hooksAdded = false + +-- Remove wire stuff and other changes that were done. +local function RemoveWire(item) + local wmiId = item.wmiId + local entId = item.entId + + local ent = item.ent + + item.init = nil + item.ent = nil + item.renderWires = nil + + if not IsValid(ent) then + ent = ents.GetByIndex(entId) + end - local ID = Entity:EntIndex() + if IsValid(ent) then + local oldSettings = item.oldSettings or {} - Entity._NextRBUpdate = nil - Entity.ppp = nil - OverRiddenEnts[ID] = nil - WireLib._RemoveWire(ID) -- Remove entity, so it doesn't count as a wire able entity anymore. + if not oldSettings.m_tblToolsAllowed then + ent.m_tblToolsAllowed = false + else + ent.m_tblToolsAllowed = oldSettings.m_tblToolsAllowed + end - for key, value in pairs(Entity._Settings_WireMapInterfaceEnt or {}) do - if (not value or (value == 0) or (value == "")) then - Entity[key] = nil + if not oldSettings.PhysgunDisabled then + ent.PhysgunDisabled = nil else - Entity[key] = value + ent.PhysgunDisabled = oldSettings.PhysgunDisabled + end + end + + item.oldSettings = nil + item.nextRenderBoundsUpdate = nil + + local wmiWiredEntitiesRemove = g_wiredEntitiesRemove[wmiId] + if wmiWiredEntitiesRemove then + wmiWiredEntitiesRemove[entId] = nil + + if table.IsEmpty(wmiWiredEntitiesRemove) then + g_wiredEntitiesRemove[wmiId] = nil + end + end + + local wmiWiredEntities = g_wiredEntities[wmiId] + if wmiWiredEntities then + wmiWiredEntities[entId] = nil + + if table.IsEmpty(wmiWiredEntities) then + g_wiredEntities[wmiId] = nil end end - Entity._Settings_WireMapInterfaceEnt = nil end --- Adding wire stuff and changes. -usermessage.Hook("WireMapInterfaceEnt", function(data) - local Entity = data:ReadEntity() - local Flags = data:ReadChar() - local Remove = (Flags == -1) - if (not WIRE_CLIENT_INSTALLED) then return end - if (not IsValid(Entity)) then return end +-- Add wire stuff for rendering and prediction. +local function AddWire(item) + local wmiId = item.wmiId + local entId = item.entId + local protectFromTools = item.protectFromTools + local protectFromPhysgun = item.protectFromPhysgun - if (Remove) then - RemoveWire(Entity) + local ent = ents.GetByIndex(entId) + if not IsValid(ent) then return end - Entity._Settings_WireMapInterfaceEnt = {} + item.ent = ent + item.init = true + + local oldSettings = item.oldSettings or {} + item.oldSettings = oldSettings + + local isCreatedByMap = ent:CreatedByMap() - if (bit.band(Flags, 1) > 0) then -- Protect in-/output entities from non-wire tools - Entity._Settings_WireMapInterfaceEnt.m_tblToolsAllowed = Entity.m_tblToolsAllowed or false - Entity.m_tblToolsAllowed = {"wire", "wire_adv", "wire_debugger", "wire_wirelink", "gui_wiring", "multi_wire"} + -- Protect in-/output entities from non-wire tools + if not ent.m_tblToolsAllowed then + oldSettings.m_tblToolsAllowed = false + else + oldSettings.m_tblToolsAllowed = table.Copy(ent.m_tblToolsAllowed) end - if (bit.band(Flags, 2) > 0) then -- Protect in-/output entities from the physgun - Entity._Settings_WireMapInterfaceEnt.PhysgunDisabled = Entity.PhysgunDisabled or false - Entity.PhysgunDisabled = true + if protectFromTools and isCreatedByMap then + ent.m_tblToolsAllowed = ent.m_tblToolsAllowed or {} + table.Add(ent.m_tblToolsAllowed, g_wireTools) end - local ID = Entity:EntIndex() - if (bit.band(Flags, 32) > 0) then -- Render Wires - OverRiddenEnts[ID] = true - else - OverRiddenEnts[ID] = nil + -- Protect in-/output entities from the physgun + oldSettings.PhysgunDisabled = ent.PhysgunDisabled or false + + if protectFromPhysgun and isCreatedByMap then + ent.PhysgunDisabled = true end -end) --- Render bounds updating -hook.Add("Think", "WireMapInterface_Think", function() - for ID, _ in pairs(OverRiddenEnts) do - local self = Entity(ID) - if (not IsValid(self) or not WIRE_CLIENT_INSTALLED) then - OverRiddenEnts[ID] = nil + item.nextRenderBoundsUpdate = 0 - return + local wmiWiredEntitiesRemove = g_wiredEntitiesRemove[wmiId] + if wmiWiredEntitiesRemove then + wmiWiredEntitiesRemove[entId] = nil + + if table.IsEmpty(wmiWiredEntitiesRemove) then + g_wiredEntitiesRemove[wmiId] = nil end + end +end - if (CurTime() >= (self._NextRBUpdate or 0)) then - self._NextRBUpdate = CurTime() + math.random(30,100) / 10 - Wire_UpdateRenderBounds(self) +local function pollWireItems() + for _, wmiWiredEntitiesRemove in pairs(g_wiredEntitiesRemove) do + for _, item in pairs(wmiWiredEntitiesRemove) do + RemoveWire(item) end end -end) --- Rendering -hook.Add("PostDrawOpaqueRenderables", "WireMapInterface_Draw", function() - for ID, _ in pairs(OverRiddenEnts) do - local self = Entity(ID) - if (not IsValid(self) or not WIRE_CLIENT_INSTALLED) then - OverRiddenEnts[ID] = nil + for wmiId, wmiWiredEntities in pairs(g_wiredEntities) do + for entId, item in pairs(wmiWiredEntities) do + if not IsValid(item.ent) then + if item.init then + -- Entity disappeared unexpectedly, so unregister it. + local wmiWiredEntitiesRemove = g_wiredEntitiesRemove[wmiId] or {} + g_wiredEntitiesRemove[wmiId] = wmiWiredEntitiesRemove + + wmiWiredEntitiesRemove[entId] = item + else + AddWire(item) + end + end + end + end +end +local function AddHooks() + hook.Add("PostCleanupMap", "WireMapInterface_PostCleanupMap_CL", function() + table.Empty(g_wiredEntities) + table.Empty(g_wiredEntitiesRemove) + g_nextThink = 0 + end) + + hook.Add("Think", "WireMapInterface_Think", function() + local now = CurTime() + + if now < g_nextThink then return end - Wire_Render(self) + g_nextThink = now + 1 + + pollWireItems() + + -- Render bounds updating + for _, wmiWiredEntities in pairs(g_wiredEntities) do + for _, item in pairs(wmiWiredEntities) do + if item.init and item.renderWires and now >= item.nextRenderBoundsUpdate then + local ent = item.ent + + if IsValid(ent) and not ent:IsDormant() then + Wire_UpdateRenderBounds(ent) + item.nextRenderBoundsUpdate = now + math.random(30, 100) / 10 + end + end + end + end + end) + + -- Rendering + hook.Add("PostDrawOpaqueRenderables", "WireMapInterface_Draw", function() + for _, wmiWiredEntities in pairs(g_wiredEntities) do + for _, item in pairs(wmiWiredEntities) do + if item.init and item.renderWires then + local ent = item.ent + + if IsValid(ent) and not ent:IsDormant() then + Wire_Render(ent) + end + end + end + end + end) + + g_hooksAdded = true +end + +net.Receive("WireMapInterfaceEntities", function() + local wmiId = net.ReadUInt(MAX_EDICT_BITS) + local protectFromTools = net.ReadBool() + local protectFromPhysgun = net.ReadBool() + local renderWires = net.ReadBool() + local count = net.ReadUInt(6) + + local wmiWiredEntities = g_wiredEntities[wmiId] or {} + g_wiredEntities[wmiId] = wmiWiredEntities + + local wmiWiredEntitiesRemove = g_wiredEntitiesRemove[wmiId] or {} + g_wiredEntitiesRemove[wmiId] = wmiWiredEntitiesRemove + + for entId, item in pairs(wmiWiredEntities) do + -- Clear all that belongs to the current WMI. + wmiWiredEntitiesRemove[entId] = item + end + + for i = 1, count do + local entId = net.ReadUInt(MAX_EDICT_BITS) + + -- Unclear listed items. + wmiWiredEntitiesRemove[entId] = nil + + -- Add listed items. + local item = wmiWiredEntities[entId] or {} + wmiWiredEntities[entId] = item + + item.entId = entId + item.wmiId = wmiId + item.protectFromTools = protectFromTools + item.protectFromPhysgun = protectFromPhysgun + item.renderWires = renderWires + end + + if not g_hooksAdded and not table.IsEmpty(wmiWiredEntities) then + AddHooks() end end) + diff --git a/lua/wire/server/wire_map_interface.lua b/lua/wire/server/wire_map_interface.lua new file mode 100644 index 0000000000..e3b496d4e8 --- /dev/null +++ b/lua/wire/server/wire_map_interface.lua @@ -0,0 +1,63 @@ +-- Serverside functionalities of Wire Map Interface + +local function validateId(id) + id = tonumber(id) + return id ~= nil and id >= -1 and id <= 0xFFFF +end + +WireLib.WireMapInterfaceValidateId = validateId + +WireLib.WireMapInterfaceLookup = { + entsBySpawnID = {}, + entsBySpawnIDDuped = {}, + entsByMapID = {}, + + add = function(self, wireEntSpawnId, wireEntSpawnIdDuped, wireEntMapId, wireEnt) + if not wireEntSpawnId then + -- Never add entities without spawn id, e.g. when not assignable. + return + end + + self.entsBySpawnID[wireEntSpawnId] = wireEnt + + if wireEntSpawnIdDuped then + self.entsBySpawnIDDuped[wireEntSpawnIdDuped] = wireEnt + end + + if validateId(wireEntMapId) then + self.entsByMapID[wireEntMapId] = wireEnt + end + + wireEnt:CallOnRemove("WireMapInterfaceLookupRemove", function() + self:remove(wireEntSpawnId, wireEntSpawnIdDuped, wireEntMapId, wireEnt) + end) + end, + + remove = function(self, wireEntSpawnId, wireEntSpawnIdDuped, wireEntMapId, wireEnt) + if not wireEntSpawnId then + self.entsBySpawnID[wireEntSpawnId] = nil + end + + if wireEntSpawnIdDuped then + self.entsBySpawnIDDuped[wireEntSpawnIdDuped] = nil + end + + if validateId(wireEntMapId) then + self.entsByMapID[wireEntMapId] = nil + end + + wireEnt:RemoveCallOnRemove("WireMapInterfaceLookupRemove") + end, + + getBySpawnID = function(self, wireEntSpawnId) + return self.entsBySpawnID[wireEntSpawnId] + end, + + getBySpawnIDDuped = function(self, wireEntSpawnIdDuped) + return self.entsBySpawnIDDuped[wireEntSpawnIdDuped] + end, + + getByMapID = function(self, wireEntMapId) + return self.entsByMapID[wireEntMapId] + end, +} diff --git a/lua/wire/server/wirelib.lua b/lua/wire/server/wirelib.lua index f1e8c846e3..99f2f4811d 100644 --- a/lua/wire/server/wirelib.lua +++ b/lua/wire/server/wirelib.lua @@ -841,29 +841,67 @@ function WireLib.Weld(ent, traceEntity, tracePhysicsBone, DOR, collision, AllowW end end +local function LookupEntityByIdOrWmiId(entIdx, spawnId, GetEntByID) + local ent = GetEntByID(entIdx) + + if IsValid(ent) then + return ent + end + + -- Used for the Wire Map Interface. + -- Linked entities might be part of the map but not of the dupe/save file ("createdEntities"), + -- because the linked entity might be not duplicatable, but still belongs to the contraption. + -- In this case lookup the entity in an additional list aswell. This fixes wire entities not connecting to Wire Map Interface entities on paste/startup. + + if not spawnId then + return nil + end + + ent = WireLib.WireMapInterfaceLookup:getBySpawnIDDuped(spawnId) + if ent then + -- The spawnId is a custom id similar to ent:MapCreationID(), but it mostly survives duping. + return ent + end + + ent = WireLib.WireMapInterfaceLookup:getBySpawnID(spawnId) + if ent then + return ent + end + + return nil +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 + local SrcEntity = input.Src + + if IsValid(SrcEntity) then info.Wires[portname] = { StartPos = input.StartPos, Material = input.Material, Color = input.Color, Width = input.Width, - Src = input.Src:EntIndex(), + Src = SrcEntity:EntIndex(), + SrcWmiSpawnId = SrcEntity._IsWireMapInterfaceSubEntity and SrcEntity:_WMI_GetSpawnId() or nil, SrcId = input.SrcId, SrcPos = Vector(0, 0, 0), } - if (input.Path) then + 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 }) + local vEntity = v.Entity + + if IsValid(vEntity) then + table.insert(info.Wires[portname].Path, { + Entity = vEntity:EntIndex(), + EntityWmiSpawnId = vEntity._IsWireMapInterfaceSubEntity and vEntity:_WMI_GetSpawnId() or nil, + Pos = v.Pos + }) end end @@ -879,72 +917,84 @@ function WireLib.BuildDupeInfo( Ent ) 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 +hook.Add("Wire_ApplyDupeInfo", "Wire_AddWirelink", function(ply, inputEnt, outputEnt, inputData) + -- 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 inputData.SrcId == "link" or inputData.SrcId == "wirelink" then -- If the target entity has no wirelink output, create one (& more old dupe compatibility) + inputData.SrcId = "wirelink" + if not outputEnt.extended then + WireLib.CreateWirelinkOutput( ply, outputEnt, {true} ) + end + elseif inputData.SrcId == "entity" and ((outputEnt.Outputs and not outputEnt.Outputs.entity) or not outputEnt.Outputs) then -- if the input name is 'entity', and the target entity doesn't have that output... + WireLib.CreateEntityOutput( ply, outputEnt, {true} ) + end +end) + +function WireLib.ApplyDupeInfo( ply, inputEnt, info, GetEntByID ) + if info.extended and not inputEnt.extended then + WireLib.CreateWirelinkOutput( ply, inputEnt, {true} ) -- old dupe compatibility; use the new function + end + + if not istable(info.Wires) then + return 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 - k=tostring(k) -- For some reason duplicator will parse strings containing numbers as numbers? - 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(ply) then + -- Map Save loading does not have a ply + idx = ply:UniqueID() + end - if IsValid( ent2 ) then - -- Wirelink and entity outputs + for k, inputData in pairs(info.Wires) do + k = tostring(k) -- For some reason duplicator will parse strings containing numbers as numbers? + local outputEnt = LookupEntityByIdOrWmiId(inputData.Src, inputData.SrcWmiSpawnId, GetEntByID) - -- 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 + -- Input alias + if inputEnt.Inputs and not inputEnt.Inputs[k] then -- if the entity has any inputs and the input 'k' is not one of them... + if inputEnt.InputAliases and inputEnt.InputAliases[k] then + k = inputEnt.InputAliases[k] + else + Msg("ApplyDupeInfo: Error, Could not find input '" .. k .. "' on entity type: '" .. inputEnt:GetClass() .. "'\n") + continue + end + end - 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 + if IsValid( outputEnt ) then + -- Sometimes you have to prepair the connection entities, before actually linking them during duplication. + -- Such cases are the Wire Map Interface and Wirelink support. + hook.Run("Wire_ApplyDupeInfo", ply, inputEnt, outputEnt, inputData) - -- 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 + -- Output alias + if outputEnt.Outputs and not outputEnt.Outputs[inputData.SrcId] then -- if the target entity has any outputs and the output 'inputData.SrcId' is not one of them... + if outputEnt.OutputAliases and outputEnt.OutputAliases[inputData.SrcId] then + inputData.SrcId = outputEnt.OutputAliases[inputData.SrcId] + else + Msg("ApplyDupeInfo: Error, Could not find output '" .. inputData.SrcId .. "' on entity type: '" .. outputEnt:GetClass() .. "'\n") + continue end end + end - WireLib.Link_Start(idx, ent, input.StartPos, k, input.Material, input.Color, input.Width) + WireLib.Link_Start(idx, inputEnt, inputData.StartPos, k, inputData.Material, inputData.Color, inputData.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 + if inputData.Path then + for _,v in ipairs(inputData.Path) do + local outputEnt = LookupEntityByIdOrWmiId(v.Entity, v.EntityWmiSpawnId, GetEntByID) + if IsValid(outputEnt) then + WireLib.Link_Node(idx, outputEnt, 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 + if IsValid(outputEnt) then + WireLib.Link_End(idx, outputEnt, inputData.SrcPos, inputData.SrcId) + else + Msg("ApplyDupeInfo: Error, Could not find the output entity\n") end end end diff --git a/wiremod.fgd b/wiremod.fgd index 42d45d357c..6fa4e18a63 100644 --- a/wiremod.fgd +++ b/wiremod.fgd @@ -2,7 +2,7 @@ : "Creates an interface between the map and Wiremod." [ wire_entity_name(target_destination) : "Wire Entity" : "" : "The name of an entity or of the entities that will get wire ports." - min_trigger_time(float) : "Minimum Trigger Time" : "0.01" : "Set the minimum time in seconds between two in-/output triggers. It's usefull to prevent lags." + min_trigger_time(float) : "Minimum Trigger Time" : "0.01" : "Set the minimum time in seconds between two Wiremod input triggers. It's usefull to prevent lags." // Wire Inputs input1_name(string) : "Wire Input 1 Name" : "" : "Sets the name of Wire Input 1." @@ -19,9 +19,6 @@ 8 : "Array" ] input1_desc(string) : "Wire Input 1 Description" : "" : "Sets the description of Wire Input 1." - input1_lua(string) : "Wire Input 1 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 1 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input2_name(string) : "Wire Input 2 Name" : "" : "Sets the name of Wire Input 2." input2_type(choices) : "Wire Input 2 Type" : 0 : "Sets the type of Wire Input 2." = @@ -37,9 +34,6 @@ 8 : "Array" ] input2_desc(string) : "Wire Input 2 Description" : "" : "Sets the description of Wire Input 2." - input2_lua(string) : "Wire Input 2 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 2 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input3_name(string) : "Wire Input 3 Name" : "" : "Sets the name of Wire Input 3." input3_type(choices) : "Wire Input 3 Type" : 0 : "Sets the type of Wire Input 3." = @@ -55,9 +49,6 @@ 8 : "Array" ] input3_desc(string) : "Wire Input 3 Description" : "" : "Sets the description of Wire Input 3." - input3_lua(string) : "Wire Input 3 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 3 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input4_name(string) : "Wire Input 4 Name" : "" : "Sets the name of Wire Input 4." input4_type(choices) : "Wire Input 4 Type" : 0 : "Sets the type of Wire Input 4." = @@ -73,9 +64,6 @@ 8 : "Array" ] input4_desc(string) : "Wire Input 4 Description" : "" : "Sets the description of Wire Input 4." - input4_lua(string) : "Wire Input 4 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 4 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input5_name(string) : "Wire Input 5 Name" : "" : "Sets the name of Wire Input 5." input5_type(choices) : "Wire Input 5 Type" : 0 : "Sets the type of Wire Input 5." = @@ -91,9 +79,6 @@ 8 : "Array" ] input5_desc(string) : "Wire Input 5 Description" : "" : "Sets the description of Wire Input 5." - input5_lua(string) : "Wire Input 5 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 5 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input6_name(string) : "Wire Input 6 Name" : "" : "Sets the name of Wire Input 6." input6_type(choices) : "Wire Input 6 Type" : 0 : "Sets the type of Wire Input 6." = @@ -109,9 +94,6 @@ 8 : "Array" ] input6_desc(string) : "Wire Input 6 Description" : "" : "Sets the description of Wire Input 6." - input6_lua(string) : "Wire Input 6 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 6 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input7_name(string) : "Wire Input 7 Name" : "" : "Sets the name of Wire Input 7." input7_type(choices) : "Wire Input 7 Type" : 0 : "Sets the type of Wire Input 7." = @@ -127,9 +109,6 @@ 8 : "Array" ] input7_desc(string) : "Wire Input 7 Description" : "" : "Sets the description of Wire Input 7." - input7_lua(string) : "Wire Input 7 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 7 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - input8_name(string) : "Wire Input 8 Name" : "" : "Sets the name of Wire Input 8." input8_type(choices) : "Wire Input 8 Type" : 0 : "Sets the type of Wire Input 8." = @@ -145,9 +124,6 @@ 8 : "Array" ] input8_desc(string) : "Wire Input 8 Description" : "" : "Sets the description of Wire Input 8." - input8_lua(string) : "Wire Input 8 Code" : "" // Hammer doens't support new lines, so I have to use spaces. - : "For advanced users! Sets the Lua code that gets called when Wire Input 8 gets triggered. The globals are: WIRE_NAME = input name, WIRE_VALUE = input value, WIRE_WIRED = true if input is wired, WIRE_CALLER = this entity, WIRE_ACTIVATOR = the input entity" - // Wire Outputs @@ -299,11 +275,11 @@ output OnResetWireInput7(void) : "Fires when Wire Input 7 gets reset." output OnResetWireInput8(void) : "Fires when Wire Input 8 gets reset." - output OnWireEntsCreated(void) : "Fires when Wire In-/Outputs entities have been created. The entity is ready to use." - output OnWireEntsRemoved(void) : "Fires when Wire In-/Outputs entities have been removed. The entity is ready to use." - output OnWireEntsReady(void) : "Fires when Wire In-/Outputs entities have been created or removed. The entity is ready to use." + output OnWireEntsCreated(void) : "Fires when Wire In-/Outputs entities have been created." + output OnWireEntsRemoved(void) : "Fires when Wire In-/Outputs entities have been removed." + output OnWireEntsReady(void) : "Fires when Wire In-/Outputs entities have been created or removed." - output OnWireEntsStartChanging(void) : "Fires when the wire map interface entity is adding or removing Wire In-/Outputs entities. The entity can't be used yet." + output OnWireEntsStartChanging(void) : "Fires when the wire map interface entity starts adding or removing Wire In-/Outputs entities." // Entity Inputs @@ -331,10 +307,9 @@ // Spawnflags spawnflags(flags) = [ - 1 : "Protect in-/output entities from non-wire tools (Useful to allow wire tools on already protected stuff)" : 0 + 1 : "Protect in-/output entities from non-wire and duplicator tools" : 0 2 : "Protect in-/output entities from the physgun" : 0 4 : "Remove in-/output entities on remove" : 1 - 8 : "Run given Lua codes (For advanced users!)" : 0 16 : "Start Active" : 1 32 : "Render Wires" : 1 ]