--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] local gmod = gmod local pairs = pairs local setmetatable = setmetatable local isstring = isstring local isnumber = isnumber local isbool = isbool local isfunction = isfunction local insert = table.insert local IsValid = IsValid local type = type local ErrorNoHaltWithStack = ErrorNoHaltWithStack -- I just do this so glua-lint doesn't rage at me do _G.HOOK_MONITOR_HIGH = -2 _G.HOOK_HIGH = -1 _G.HOOK_NORMAL = 0 _G.HOOK_LOW = 1 _G.HOOK_MONITOR_LOW = 2 end local HOOK_MONITOR_HIGH = HOOK_MONITOR_HIGH local HOOK_HIGH = HOOK_HIGH local HOOK_NORMAL = HOOK_NORMAL local HOOK_LOW = HOOK_LOW local HOOK_MONITOR_LOW = HOOK_MONITOR_LOW module("hook") local events = {} local function find_hook(event, name) for i = 1, event.n, 4 do local _name = event[i] if _name and _name == name then return i end end end --[[ we are making a new event table so we don't mess up anything when adding/removing hooks while hook.Call is running, this is how it works: 1- When (adding/removing a hook)/(editing a hook priority), we create a new event table to avoid messing up hook.Call call order if it's running, and the old event table will be shadowed and can only be accessed from hook.Call if it's running 2- We make old event table have __index method to make sure if any hook got removed/edited we (stop it from running)/(run the new function) ]] local function copy_event(event, event_name) local new_event = {} do for i = 1, event.n do local v = event[i] if v then insert(new_event, v) end end new_event.n = #new_event end -- we use proxies here just to make __index work -- https://stackoverflow.com/a/3122136 local proxy = {} do for i = 1, event.n do proxy[i] = event[i] event[i] = nil end proxy.n = event.n event.n = nil end setmetatable(event, { __index = function(_, key) -- make event.n work if isstring(key) then return proxy[key] end local name = proxy[key - 1] if not name then return end local parent = events[event_name] -- if hook got removed then don't run it local pos = find_hook(parent, name) if not pos then return end -- if hook priority changed then it should be treated as a new hook, don't run it if parent[pos + 3 --[[priority]]] ~= proxy[key + 2 --[[priority]]] then return end return parent[pos + 1] end }) return new_event end --[[--------------------------------------------------------- Name: Add Args: string hookName, any identifier, function func Desc: Add a hook to listen to the specified event. -----------------------------------------------------------]] function Add(event_name, name, func, priority) if not isstring(event_name) then ErrorNoHaltWithStack("bad argument #1 to 'Add' (string expected, got " .. type(event_name) .. ")") return end if not isfunction(func) then ErrorNoHaltWithStack("bad argument #3 to 'Add' (function expected, got " .. type(func) .. ")") return end local notValid = name == nil or isnumber(name) or isbool(name) or isfunction(name) or not name.IsValid or not IsValid(name) if not isstring(name) and notValid then ErrorNoHaltWithStack("bad argument #2 to 'Add' (string expected, got " .. type(name) .. ")") return end local real_func = func if not isstring(name) then func = function(...) local isvalid = name.IsValid if isvalid and isvalid(name) then return real_func(name, ...) end Remove(event_name, name) end end if not isnumber(priority) then priority = HOOK_NORMAL elseif priority < HOOK_MONITOR_HIGH then priority = HOOK_MONITOR_HIGH elseif priority > HOOK_MONITOR_LOW then priority = HOOK_MONITOR_LOW end -- disallow returning in monitor hooks if priority == HOOK_MONITOR_HIGH or priority == HOOK_MONITOR_LOW then local _func = func func = function(...) _func(...) end end local event = events[event_name] if not event then event = { n = 0, } events[event_name] = event end local pos if event then local _pos = find_hook(event, name) -- if hook exists and priority changed then remove the old one because it has to be treated as a new hook if _pos and event[_pos + 3] ~= priority then Remove(event_name, name) else -- just update the hook here because nothing changed but the function pos = _pos end end event = events[event_name] if pos then event[pos + 1] = func event[pos + 2] = real_func return end if priority == HOOK_MONITOR_LOW then local n = event.n event[n + 1] = name event[n + 2] = func event[n + 3] = real_func event[n + 4] = priority else local event_pos = 4 for i = 4, event.n, 4 do local _priority = event[i] if priority < _priority then if i < event_pos then event_pos = i end elseif priority >= _priority then event_pos = i + 4 end end insert(event, event_pos - 3, name) insert(event, event_pos - 2, func) insert(event, event_pos - 1, real_func) insert(event, event_pos, priority) end event.n = event.n + 4 end --[[--------------------------------------------------------- Name: Remove Args: string hookName, identifier Desc: Removes the hook with the given indentifier. -----------------------------------------------------------]] function Remove(event_name, name) local event = events[event_name] if not event then return end local pos = find_hook(event, name) if pos then event[pos] = nil --[[name]] event[pos + 1] = nil --[[func]] event[pos + 2] = nil --[[real_func]] event[pos + 3] = nil --[[priority]] end events[event_name] = copy_event(event, event_name) end --[[--------------------------------------------------------- Name: GetTable Desc: Returns a table of all hooks. -----------------------------------------------------------]] function GetTable() local new_events = {} for event_name, event in pairs(events) do local hooks = {} for i = 1, event.n, 4 do local name = event[i] if name then hooks[name] = event[i + 2] --[[real_func]] end end new_events[event_name] = hooks end return new_events end --[[--------------------------------------------------------- Name: Call Args: string hookName, table gamemodeTable, vararg args Desc: Calls hooks associated with the hook name. -----------------------------------------------------------]] function Call(event_name, gm, ...) local event = events[event_name] if event then local i, n = 2, event.n ::loop:: local func = event[i] if func then local a, b, c, d, e, f = func(...) if a ~= nil then return a, b, c, d, e, f end end i = i + 4 if i <= n then goto loop end end -- -- Call the gamemode function -- if not gm then return end local GamemodeFunction = gm[event_name] if not GamemodeFunction then return end return GamemodeFunction(gm, ...) end --[[--------------------------------------------------------- Name: Run Args: string hookName, vararg args Desc: Calls hooks associated with the hook name. -----------------------------------------------------------]] function Run(name, ...) return Call(name, gmod and gmod.GetGamemode() or nil, ...) end