diff --git a/lua/entities/gmod_wire_expression2/init.lua b/lua/entities/gmod_wire_expression2/init.lua index 3719681b4b..08cdd67876 100644 --- a/lua/entities/gmod_wire_expression2/init.lua +++ b/lua/entities/gmod_wire_expression2/init.lua @@ -75,6 +75,13 @@ function ENT:Initialize() self.error = true self:UpdateOverlay(true) self:SetColor(Color(255, 0, 0, self:GetColor().a)) + + local owner = self.player + + if IsValid(owner) then + E2Lib.PlayerChips[owner] = E2Lib.PlayerChips[owner] or {} + table.insert(E2Lib.PlayerChips[owner], self) + end end function ENT:OnRestore() @@ -287,14 +294,56 @@ function ENT:Think() context.prf = 0 context.time = 0 - if e2_timequota > 0 and context.timebench > e2_timequota then - self:Error("Expression 2 (" .. selfTbl.name .. "): time quota exceeded", "time quota exceeded") - self:PCallHook("destruct") - end - return true end +E2Lib.PlayerChips = E2Lib.PlayerChips or {} + +hook.Add("Think", "E2_Think", function() + if e2_timequota < 0 then return end + + for ply, chips in pairs(E2Lib.PlayerChips) do + local total_time = 0 + + for _, chip in ipairs(chips) do + local tab = chip:GetTable() + if tab.error then continue end + + local context = tab.context + if not context then continue end + + total_time = total_time + context.timebench + end + + while total_time > e2_timequota do + local max_time = 0 + local max_chip + + for _, chip in ipairs(chips) do + local tab = chip:GetTable() + if tab.error then continue end + + local context = tab.context + if not context then continue end + + if context.timebench > max_time then + max_time = context.timebench + max_chip = chip + end + end + + if max_chip then + total_time = total_time - max_time + max_chip:Error("Expression 2 (" .. max_chip.name .. "): Per-player time quota exceeded", "per-player time quota exceeded") + max_chip:Destruct() + else + -- It shouldn't happen, but if something breaks, it will prevent an infinity loop + break + end + end + end +end) + local CallHook = wire_expression2_CallHook function ENT:CallHook(hookname, ...) local context = self.context @@ -308,6 +357,22 @@ function ENT:OnRemove() self:Destruct() end + local owner = self.player + if not IsValid(owner) then return end + + local chips = E2Lib.PlayerChips[owner] + + for index, chip in ipairs(chips) do + if chip == self then + table.remove(chips, index) + break + end + end + + if #chips == 0 then + E2Lib.PlayerChips[owner] = nil + end + BaseClass.OnRemove(self) end @@ -718,52 +783,24 @@ end --[[ Player Disconnection Magic --]] -local cvar = CreateConVar("wire_expression2_pause_on_disconnect", 0, 0, "Decides if chips should pause execution on their owner's disconnect.\n0 = no, 1 = yes, 2 = non-admins only.") --- This is a global function so it can be overwritten for greater control over whose chips are frozenated -function wire_expression2_ShouldFreezeChip(ply) - return not ply:IsAdmin() -end +hook.Add("PlayerDisconnected", "Wire_Expression2_Player_Disconnected", function(ply) + E2Lib.PlayerChips[ply] = nil --- It uses EntityRemoved because PlayerDisconnected doesn't catch all disconnects. -hook.Add("EntityRemoved", "Wire_Expression2_Player_Disconnected", function(ent) - if (not (ent and ent:IsPlayer())) then - return - end - local ret = cvar:GetInt() - if (ret == 0 or (ret == 2 and not wire_expression2_ShouldFreezeChip(ent))) then - return - end for _, v in ipairs(ents.FindByClass("gmod_wire_expression2")) do - if (v.player == ent) then - v:SetOverlayText(v.name .. "\n(Owner disconnected.)") - local oldColor = v:GetColor() - v:SetColor(Color(255, 0, 0, v:GetColor().a)) - v.disconnectPaused = oldColor - v.error = true + if v.player == ply and not v.error then + v:Error("Owner disconnected") + v:Destruct() end end end) hook.Add("PlayerAuthed", "Wire_Expression2_Player_Authed", function(ply, sid, uid) for _, ent in ipairs(ents.FindByClass("gmod_wire_expression2")) do - if ent.uid == uid and ent.context then - ent.context.player = ply - ent.player = ply + if ent.uid == uid then + E2Lib.PlayerChips[ply] = E2Lib.PlayerChips[ply] or {} + table.insert(E2Lib.PlayerChips[ply], ent) ent:SetNWEntity("player", ply) - ent:SetPlayer(ply) - - if ent.disconnectPaused then - ent:SetColor(ent.disconnectPaused) - ent:SetRenderMode(ent:GetColor().a == 255 and RENDERMODE_NORMAL or RENDERMODE_TRANSALPHA) - ent.error = false - ent.disconnectPaused = nil - ent:SetOverlayText(ent.name) - end - end - end - for _, ent in ipairs(ents.FindByClass("gmod_wire_hologram")) do - if ent.steamid == sid then - ent:SetPlayer(ply) + ent.player = ply end end end) @@ -781,10 +818,10 @@ function MakeWireExpression2(player, Pos, Ang, model, buffer, name, inputs, outp self:SetModel(model) self:SetAngles(Ang) self:SetPos(Pos) - self:Spawn() self:SetPlayer(player) - self.player = player self:SetNWEntity("player", player) + self.player = player + self:Spawn() if isstring( buffer ) then -- if someone dupes an E2 with compile errors, then all these values will be invalid buffer = string.Replace(string.Replace(buffer, string.char(163), "\""), string.char(128), "\n") diff --git a/lua/entities/gmod_wire_expression2/shared.lua b/lua/entities/gmod_wire_expression2/shared.lua index fd53d42fc4..58358a08fd 100644 --- a/lua/entities/gmod_wire_expression2/shared.lua +++ b/lua/entities/gmod_wire_expression2/shared.lua @@ -12,7 +12,7 @@ CreateConVar("wire_expression2_unlimited", "0", {FCVAR_REPLICATED}) CreateConVar("wire_expression2_quotasoft", "10000", {FCVAR_REPLICATED}) CreateConVar("wire_expression2_quotahard", "100000", {FCVAR_REPLICATED}) CreateConVar("wire_expression2_quotatick", "25000", {FCVAR_REPLICATED}) -CreateConVar("wire_expression2_quotatime", "-1", {FCVAR_REPLICATED}, "Time in (ms) the e2 can consume before killing (-1 is infinite)") +CreateConVar("wire_expression2_quotatime", "-1", {FCVAR_REPLICATED}, "Time in (ms) that all E2s of one player can consume before killing (-1 is infinite)") include("core/e2lib.lua") include("base/debug.lua")