diff --git a/CHANGELOG.md b/CHANGELOG.md index e76b281..f52056e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Moved ModUtil.Mod.Register and ModData to ModUtil.lua so it will be available earlier in the load order +- Moved ModUtil.Hades.PrintStack config to dedicated module wide config +- Moved to ReturnOfModding layout (still works on ModImporter) + +### Removed + +- Removed all menu handling capabilities from ModUtil.Hades +- Removed ModUtil.Compat, which includes the old names, such as WrapBaseFunction + ## [2.10.1] - 2024-04-24 ### Added diff --git a/README.md b/README.md index 39da4b7..126d3f1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,2 @@ # Mod Utility / ModUtil -Utility mod for mod interactions within lua for SGG's games -see the [Wiki](https://github.com/SGG-Modding/ModUtil/wiki) for more details +Utility mod for mod interactions within lua for SGG's games diff --git a/src/ModUtil.Compat.lua b/src/ModUtil.Compat.lua deleted file mode 100644 index 90edc6c..0000000 --- a/src/ModUtil.Compat.lua +++ /dev/null @@ -1,116 +0,0 @@ - -ModUtil.Mod.Register( "Compat", ModUtil ) - -setmetatable( ModUtil, { - __index = ModUtil.Compat -} ) - -ModUtil.Compat.RegisterMod = ModUtil.Mod.Register - -ModUtil.Compat.ValueString = ModUtil.ToString.Value - -ModUtil.Compat.KeyString = ModUtil.ToString.Key - -ModUtil.Compat.TableKeysString = ModUtil.ToString.TableKeys - -ModUtil.Compat.ToShallowString = ModUtil.ToString.Shallow - -ModUtil.Compat.ToDeepString = ModUtil.ToString.Deep - -ModUtil.Compat.ToDeepNoNamespacesString = ModUtil.ToString.Deep.NoNamespaces - -ModUtil.Compat.ToDeepNamespacesString = ModUtil.ToString.Deep.Namespaces - -ModUtil.Compat.JoinStrings = ModUtil.String.Join - -ModUtil.Compat.ChunkText = ModUtil.String.Chunk - -ModUtil.Compat.ReplaceTable = ModUtil.Table.Replace - -ModUtil.Compat.IsUnKeyed = ModUtil.Table.UnKeyed - -ModUtil.Compat.PrintToFile = ModUtil.Print.ToFile - -ModUtil.Compat.DebugPrint = ModUtil.Print.Debug - -ModUtil.Compat.PrintTraceback = ModUtil.Print.Traceback - -ModUtil.Compat.PrintNamespaces = ModUtil.Print.Namespaces - -ModUtil.Compat.PrintVariables = ModUtil.Print.Variables - -ModUtil.Compat.Slice = ModUtil.Array.Slice - -ModUtil.Compat.NewTable = ModUtil.Node.New - -ModUtil.Compat.SafeGet = ModUtil.IndexArray.Get - -ModUtil.Compat.SafeSet = ModUtil.IndexArray.Set - -ModUtil.Compat.MapNilTable = ModUtil.Table.NilMerge - -ModUtil.Compat.MapSetTable = ModUtil.Table.Merge - -ModUtil.Compat.JoinIndexArrays = ModUtil.Array.Join - -ModUtil.Compat.PathToIndexArray = ModUtil.Path.IndexArray - -ModUtil.Compat.PathGet = ModUtil.Path.Get - -ModUtil.Compat.PathSet = ModUtil.Path.Set - -ModUtil.Compat.WrapFunction = ModUtil.IndexArray.Wrap - -ModUtil.Compat.RewrapFunction = ModUtil.IndexArray.Decorate.Refresh - -ModUtil.Compat.UnwrapFunction = ModUtil.IndexArray.Decorate.Pop - -ModUtil.Compat.WrapBaseFunction = ModUtil.Path.Wrap - -ModUtil.Compat.RewrapBaseFunction = ModUtil.Path.Decorate.Refresh - -ModUtil.Compat.UnwrapBaseFunction = ModUtil.Path.Decorate.Pop - -ModUtil.Compat.BaseOverride = ModUtil.Path.Override - -ModUtil.Compat.GetOriginalValue = ModUtil.IndexArray.Original - -ModUtil.Compat.GetOriginalBaseValue = ModUtil.Path.Original - -ModUtil.Compat.RawInterface = ModUtil.Raw - -ModUtil.Compat.MapVars = ModUtil.Args.Map - -ModUtil.Compat.StackedUpValues = ModUtil.UpValues.Stacked - -ModUtil.Compat.StackedLocals = ModUtil.Locals.Stacked - -ModUtil.Compat.LocalValues = ModUtil.Locals.Values - -ModUtil.Compat.LocalNames = ModUtil.Locals.Names - -function ModUtil.Compat.GetBaseBottomUpValues( funcPath ) - return ModUtil.UpValues( ModUtil.Path.Original( funcPath ) ) -end - -function ModUtil.Compat.MapTable( mapFunc, tableArg ) - return ModUtil.Table.Map( tableArg, mapFunc ) -end - -function ModUtil.Compat.WrapWithinFunction( baseTable, indexArray, envIndexArray, wrapFunc, mod ) - ModUtil.IndexArray.Context.Wrap( baseTable, indexArray, function( ) - ModUtil.IndexArray.Wrap( _G, envIndexArray, wrapFunc, mod ) - end ) -end - -function ModUtil.Compat.WrapBaseWithinFunction( funcPath, baseFuncPath, wrapFunc, mod ) - ModUtil.Path.Context.Wrap( baseFuncPath, function( ) - ModUtil.Path.Wrap( funcPath, wrapFunc, mod ) - end ) -end - -function ModUtil.BaseOverrideWithinFunction( funcPath, basePath, value, mod ) - ModUtil.Path.Context.Wrap( funcPath, function( ) - ModUtil.Path.Override( basePath, value, mod ) - end ) -end diff --git a/src/ModUtil.Extra.lua b/src/ModUtil.Extra.lua index 8af053c..a2fd6f3 100644 --- a/src/ModUtil.Extra.lua +++ b/src/ModUtil.Extra.lua @@ -1,4 +1,5 @@ ---- +---@meta _ +---@diagnostic disable ModUtil.IndexArray.Context = { } diff --git a/src/ModUtil.Hades.lua b/src/ModUtil.Hades.lua index 3f8a1ec..932b2f1 100644 --- a/src/ModUtil.Hades.lua +++ b/src/ModUtil.Hades.lua @@ -1,93 +1,7 @@ -ModUtil.Mod.Register( "Hades", ModUtil ) - -ModUtil.Table.Merge( ModUtil.Hades, { - PrintStackHeight = 10, - PrintStackCapacity = 80 -} ) - --- Menu Handling - -local menuScreens = { } -local closeFuncs = { } - ---[[ - Tell each screen anchor that they have been forced closed by the game ---]] -local function forceClosed( triggerArgs ) - for _, v in pairs( closeFuncs ) do - v( nil, nil, triggerArgs ) - end - closeFuncs = { } - menuScreens = { } -end -OnAnyLoad{ function( triggerArgs ) forceClosed( triggerArgs ) end } +---@meta _ +---@diagnostic disable -function ModUtil.Hades.CloseMenu( screen, button ) - CloseScreen(GetAllIds(screen.Components), 0.1) - menuScreens[screen.Name] = nil - screen.KeepOpen = false - OnScreenClosed({ Flag = screen.Name }) - if TableLength(menuScreens) == 0 then - SetConfigOption({ Name = "FreeFormSelectWrapY", Value = false }) - SetConfigOption({ Name = "UseOcclusion", Value = true }) - UnfreezePlayerUnit() - DisableShopGamepadCursor() - end - if closeFuncs[screen.Name] then - closeFuncs[screen.Name]( screen, button ) - closeFuncs[screen.Name]=nil - end -end - -function ModUtil.Hades.OpenMenu( group, closeFunc, openFunc ) - if menuScreens[group] then - ModUtil.Hades.CloseMenu(menuScreens[group]) - end - if closeFunc then closeFuncs[group]=closeFunc end - - local screen = { Name = group, Components = {} } - local components = screen.Components - menuScreens[group] = screen - - OnScreenOpened({ Flag = screen.Name, PersistCombatUI = true }) - - components.Background = CreateScreenComponent({ Name = "BlankObstacle", Group = group }) - - if openFunc then openFunc(screen) end - - return screen -end - -function ModUtil.Hades.DimMenu( screen ) - if not screen then return end - if not screen.Components.BackgroundDim then - screen.Components.BackgroundDim = CreateScreenComponent({ Name = "rectangle01", Group = screen.Name }) - SetScale({ Id = screen.Components.BackgroundDim.Id, Fraction = 4 }) - end - SetColor({ Id = screen.Components.BackgroundDim.Id, Color = {0.090, 0.090, 0.090, 0.8} }) -end - -function ModUtil.Hades.UndimMenu( screen ) - if not screen then return end - if not screen.Components.BackgroundDim then return end - SetColor({ Id = screen.Components.BackgroundDim.Id, Color = {0.090, 0.090, 0.090, 0} }) -end - -function ModUtil.Hades.PostOpenMenu( screen ) - if TableLength(menuScreens) == 1 then - SetConfigOption({ Name = "FreeFormSelectWrapY", Value = true }) - SetConfigOption({ Name = "UseOcclusion", Value = false }) - FreezePlayerUnit() - EnableShopGamepadCursor() - end - thread(HandleWASDInput, screen) - HandleScreenInput(screen) - return screen -end - -function ModUtil.Hades.GetMenuScreen( group ) - return menuScreens[group] -end +ModUtil.Mod.Register( "Hades", ModUtil ) -- Debug Printing @@ -166,9 +80,9 @@ local function closePrintStack() end local function orderPrintStack(screen,components) - - if screen.CullPrintStack then - local v = screen.TextStack[1] + local v + if screen.CullPrintStack then + v = screen.TextStack[1] if v.obj then Destroy({Ids = {v.obj.Id}}) components["TextStack_" .. v.tid] = nil @@ -310,86 +224,6 @@ function ModUtil.Hades.PrintStackChunks( text, linespan, ... ) end end --- Custom Menus - -function ModUtil.Hades.NewMenuYesNo( group, closeFunc, openFunc, yesFunc, noFunc, title, body, yesText, noText, icon, iconScale) - - if not group or group == "" then group = "MenuYesNo" end - if not yesFunc then yesFunc = function( ) end end - if not noFunc then noFunc = function( ) end end - if not icon then icon = "AmmoPack" end - if not iconScale then iconScale = 1 end - if not yesText then yesText = "Yes" end - if not noText then noText = "No" end - if not body then body = "Make a choice..." end - if not title then title = group end - - local screen = ModUtil.Hades.OpenMenu( group, closeFunc, openFunc ) - local components = screen.Components - - PlaySound({ Name = "/SFX/Menu Sounds/GodBoonInteract" }) - - components.LeftPart = CreateScreenComponent({ Name = "TraitTrayBackground", Group = group, X = 1030, Y = 424}) - components.MiddlePart = CreateScreenComponent({ Name = "TraitTray_Center", Group = group, X = 660, Y = 464 }) - components.RightPart = CreateScreenComponent({ Name = "TraitTray_Right", Group = group, X = 1270, Y = 438 }) - SetScaleY({Id = components.LeftPart.Id, Fraction = 0.8}) - SetScaleY({Id = components.MiddlePart.Id, Fraction = 0.8}) - SetScaleY({Id = components.RightPart.Id, Fraction = 0.8}) - SetScaleX({Id = components.MiddlePart.Id, Fraction = 5}) - - - CreateTextBox({ Id = components.Background.Id, Text = " "..title.." ", FontSize = 34, - OffsetX = 0, OffsetY = -225, Color = Color.White, Font = "SpectralSCLight", - ShadowBlur = 0, ShadowColor = {0,0,0,1}, ShadowOffset={0, 1}, Justification = "Center" }) - CreateTextBox({ Id = components.Background.Id, Text = " "..body.." ", FontSize = 19, - OffsetX = 0, OffsetY = -175, Width = 840, Color = Color.SubTitle, Font = "CrimsonTextItalic", - ShadowBlur = 0, ShadowColor = {0,0,0,1}, ShadowOffset={0, 1}, Justification = "Center" }) - - components.Icon = CreateScreenComponent({ Name = "BlankObstacle", Group = group }) - Attach({ Id = components.Icon.Id, DestinationId = components.Background.Id, OffsetX = 0, OffsetY = -50}) - SetAnimation({ Name = icon, DestinationId = components.Icon.Id, Scale = iconScale }) - - components.CloseButton = CreateScreenComponent({ Name = "ButtonClose", Scale = 0.7, Group = group }) - Attach({ Id = components.CloseButton.Id, DestinationId = components.Background.Id, OffsetX = 0, OffsetY = ScreenCenterY - 315 }) - components.CloseButton.OnPressedFunctionName = ModUtil.Hades.CloseMenuYesNo - components.CloseButton.ControlHotkey = "Cancel" - - components.YesButton = CreateScreenComponent({ Name = "BoonSlot1", Group = group, Scale = 0.35, }) - components.YesButton.OnPressedFunctionName = function(screen, button) - if not yesFunc(screen,button) then - ModUtil.Hades.CloseMenuYesNo(screen,button) - end - end - SetScaleX({Id = components.YesButton.Id, Fraction = 0.75}) - SetScaleY({Id = components.YesButton.Id, Fraction = 1.15}) - Attach({ Id = components.YesButton.Id, DestinationId = components.Background.Id, OffsetX = -150, OffsetY = 75 }) - CreateTextBox({ Id = components.YesButton.Id, Text = " "..yesText.." ", - FontSize = 28, OffsetX = 0, OffsetY = 0, Width = 720, Color = Color.LimeGreen, Font = "AlegreyaSansSCLight", - ShadowBlur = 0, ShadowColor = {0,0,0,1}, ShadowOffset={0, 2}, Justification = "Center" - }) - - components.NoButton = CreateScreenComponent({ Name = "BoonSlot1", Group = group, Scale = 0.35, }) - components.NoButton.OnPressedFunctionName = function(screen, button) - if not noFunc( screen, button ) then - ModUtil.Hades.CloseMenuYesNo( screen, button ) - end - end - SetScaleX({Id = components.NoButton.Id, Fraction = 0.75}) - SetScaleY({Id = components.NoButton.Id, Fraction = 1.15}) - Attach({ Id = components.NoButton.Id, DestinationId = components.Background.Id, OffsetX = 150, OffsetY = 75 }) - CreateTextBox({ Id = components.NoButton.Id, Text = noText, - FontSize = 26, OffsetX = 0, OffsetY = 0, Width = 720, Color = Color.Red, Font = "AlegreyaSansSCLight", - ShadowBlur = 0, ShadowColor = {0,0,0,1}, ShadowOffset={0, 2}, Justification = "Center" - }) - - return ModUtil.Hades.PostOpenMenu( screen ) -end - -function ModUtil.Hades.CloseMenuYesNo( screen, button ) - PlaySound( { Name = "/SFX/Menu Sounds/GeneralWhooshMENU" } ) - ModUtil.Hades.CloseMenu( screen, button ) -end - -- Trigger Proxy local triggers = { } diff --git a/src/ModUtil.Main.lua b/src/ModUtil.Main.lua index 229d1c4..910382b 100644 --- a/src/ModUtil.Main.lua +++ b/src/ModUtil.Main.lua @@ -1,23 +1,11 @@ +---@meta _ +---@diagnostic disable + --[[ ModUtil Main Components of ModUtil that depend on loading after Main.lua ]] --- Management - -local function setSaveIgnore(key, ignore) - if SaveIgnores then - SaveIgnores[key] = ignore - elseif GlobalSaveWhitelist then - GlobalSaveWhitelist[key] = not ignore - end -end - -setSaveIgnore( "ModUtil", true ) - -rawset( _ENV, "GLOBALS", ModUtil.Internal._G ) -setSaveIgnore( "GLOBALS", true ) - -- Global Interception --[[ @@ -26,6 +14,12 @@ setSaveIgnore( "GLOBALS", true ) --]] local callableCandidateTypes = ModUtil.Internal.callableCandidateTypes +local setSaveIgnore = ModUtil.Internal.setSaveIgnore + +setSaveIgnore( "ModUtil", true ) + +rawset( _ENV, "GLOBALS", ModUtil.Internal._ENV_ORIGINAL ) +setSaveIgnore( "GLOBALS", true ) local function isPath( path ) return path:find("[.]") @@ -44,56 +38,27 @@ local function routeKey( self, key ) end end -do - +local function extendGlobalEnvironment() local meta = getmetatable( _ENV ) or { } - if meta.__index then + local mi = meta.__index + local mit = type(mi) + if mit == "function" then meta.__index = ModUtil.Wrap( meta.__index, function( base, self, key ) local value = base( self, key ) if value ~= nil then return value end return routeKey( self, key ) end, ModUtil ) + elseif mit == "table" then + meta.__index = function( self, key ) + local value = mi[key] + if value ~= nil then return value end + return routeKey( self, key ) + end else meta.__index = routeKey end setmetatable( _ENV, meta ) - -end - ---[[ - Create a namespace that can be used for the mod's functions - and data, and ensure that it doesn't end up in save files. - - modName - the name of the mod - parent - the parent mod, or nil if this mod stands alone ---]] -function ModUtil.Mod.Register( first, second, meta ) - local modName, parent - if type( first ) == "string" then - modName, parent = first, second - else - modName, parent = second, first - end - if not parent then - parent = _G - setSaveIgnore( modName, true ) - end - local mod = parent[ modName ] or { } - parent[ modName ] = mod - local path = ModUtil.Identifiers.Data[ parent ] - if path ~= nil then - path = path .. '.' - else - path = '' - end - path = path .. modName - ModUtil.Mods.Data[ path ] = mod - ModUtil.Identifiers.Inverse[ path ] = mod - if meta == false then - return mod - end - return setmetatable( mod, ModUtil.Metatables.Mod ) end local objectData = ModUtil.Internal.objectData @@ -218,6 +183,7 @@ ModUtil.Mod.Data = setmetatable( { }, { return idx, modDataProxy( ModData[ idx ], 2 ) end end, + ---@type fun( t ): any, any?, any? __pairs = function( self ) return qrawpairs( self ) end, @@ -254,8 +220,11 @@ local function loadFuncs( triggerArgs ) end funcsToLoad = { } end -OnAnyLoad{ function( triggerArgs ) loadFuncs( triggerArgs ) end } - +---@diagnostic disable-next-line: undefined-global +if OnAnyLoad then + ---@diagnostic disable-next-line: undefined-global + OnAnyLoad{ function( triggerArgs ) loadFuncs( triggerArgs ) end } +end --[[ Run the provided function once on the next in-game load. @@ -283,8 +252,10 @@ end do local ups = ModUtil.UpValues( function( ) - return _G, funcsToLoad, loadFuncs, isPath, routeKey, callableCandidateTypes, setSaveIgnore, - objectData, passByValueTypes, modDataKey, modDataProxy, modDataPlain, relativeTable + return _ENV, funcsToLoad, loadFuncs, isPath, routeKey, callableCandidateTypes, setSaveIgnore, + objectData, passByValueTypes, modDataKey, modDataProxy, modDataPlain, relativeTable, extendGlobalEnvironment end ) ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) -end \ No newline at end of file +end + +extendGlobalEnvironment() \ No newline at end of file diff --git a/src/ModUtil.lua b/src/ModUtil.lua index c595ea8..f315dee 100644 --- a/src/ModUtil.lua +++ b/src/ModUtil.lua @@ -1,10 +1,6 @@ ---[[ -Mod: Mod Utility -Author: MagicGonads - - Library to allow mods to be more compatible and expand capabilities. +---@meta _ +---@diagnostic disable ---]] ModUtil = { Mod = { }, Args = { }, @@ -27,6 +23,7 @@ local function getname( ) end -- doesn't invoke __index +---@diagnostic disable-next-line: lowercase-global rawnext = next local rawnext = rawnext @@ -46,18 +43,23 @@ end local next = next -- truly raw pairs, ignores __next and __pairs +---@type fun( t ): any, any +---@diagnostic disable-next-line: lowercase-global function rawpairs( t ) - return rawnext, t, nil + return rawnext, t end -- quasi-raw pairs, invokes __next but ignores __pairs +---@type fun( t ): any, any +---@diagnostic disable-next-line: lowercase-global function qrawpairs( t ) - return next, t, nil + return next, t end local rawget, rawset, rawlen = rawget, rawset, rawlen -- doesn't invoke __index just like rawnext +---@diagnostic disable-next-line: lowercase-global function rawinext( t, i ) if type( t ) ~= "table" then @@ -82,6 +84,7 @@ end local rawinext = rawinext -- invokes __inext +---@diagnostic disable-next-line: lowercase-global function inext( t, i ) local m = debug.getmetatable( t ) local f = m and rawget(m,'__inext') or rawinext @@ -91,6 +94,7 @@ end local inext = inext -- truly raw ipairs, ignores __inext and __ipairs +---@diagnostic disable-next-line: lowercase-global function rawipairs( t ) return function( self, key ) return rawinext( self, key ) @@ -98,6 +102,7 @@ function rawipairs( t ) end -- quasi-raw ipairs, invokes __inext but ignores __ipairs +---@diagnostic disable-next-line: lowercase-global function qrawipairs( t ) return function( self, key ) return inext( self, key ) @@ -105,6 +110,7 @@ function qrawipairs( t ) end -- ignore __tostring (not thread safe?) +---@diagnostic disable-next-line: lowercase-global function rawtostring(t) -- https://stackoverflow.com/a/43286713 local m = getmetatable( t ) @@ -162,6 +168,8 @@ end table.rawinsert = table.insert local rawinsert = table.rawinsert -- table.insert that respects metamethods +---@type fun( list, pos, value?: any ) +---@diagnostic disable-next-line: duplicate-set-field function table.insert( list, pos, value ) local last = #list if value == nil then @@ -183,6 +191,8 @@ end table.rawremove = table.remove -- table.remove that respects metamethods +---@type fun( list, pos ): any +---@diagnostic disable-next-line: duplicate-set-field function table.remove( list, pos ) local last = #list if pos == nil then @@ -210,6 +220,8 @@ do return _unpack( t, m, i - 1, t[ i ], ... ) end + ---@type fun( list, i?, j? ): any + ---@diagnostic disable-next-line: duplicate-set-field function table.unpack( list, i, j ) return _unpack( list, i or 1, j or list.n or #list or 1 ) end @@ -220,6 +232,8 @@ table.rawconcat = rawconcat -- table.concat that respects metamethods and includes more values do local wt = setmetatable( { }, { __mode = 'v' } ) + ---@type fun( tbl, sep?, i?, j? ): string + ---@diagnostic disable-next-line: duplicate-set-field function table.concat( tbl, sep, i, j ) i = i or 1 j = j or tbl.n or #tbl @@ -271,8 +285,8 @@ end -- Environment Manipulation -local _ENV_ORIGINAL = _ENV -local _ENV_REPLACED = _ENV +local _ENV_ORIGINAL = _G +local _ENV_REPLACED = _G local threadEnvironments = setmetatable( { }, { __mode = "k" } ) @@ -297,6 +311,7 @@ local function replaceGlobalEnvironment( ) __inext = function( _, key ) return inext( getEnv( ), key ) end, + ---@type fun( t? ): any, any __pairs = function( ) return pairs( getEnv( ) ) end, @@ -329,20 +344,20 @@ ModUtil.Metatables.Proxy = { __newindex = function( self, key, value ) objectData[ self ][ key ] = value end, - __len = function( self, ...) + __len = function( self ) return #objectData( self ) end, - __next = function( self, ... ) - return next( objectData[ self ], ... ) + __next = function( self, key ) + return next( objectData[ self ], key ) end, - __inext = function( self, ... ) - return inext( objectData[ self ], ... ) + __inext = function( self, idx ) + return inext( objectData[ self ], idx ) end, - __pairs = function( self, ... ) - return pairs( objectData[ self ], ... ) + __pairs = function( self ) + return pairs( objectData[ self ] ) end, - __ipairs = function( self, ... ) - return ipairs( objectData[ self ], ... ) + __ipairs = function( self ) + return ipairs( objectData[ self ] ) end } @@ -357,20 +372,20 @@ ModUtil.Metatables.Raw = { __newindex = function( self, ... ) return rawset( objectData[ self ][ "data" ], ... ) end, - __len = function( self, ...) - return rawlen( objectData[ self ][ "data" ], ... ) + __len = function( self ) + return rawlen( objectData[ self ][ "data" ] ) end, - __next = function( self, ... ) - return rawnext( objectData[ self ][ "data" ], ... ) + __next = function( self, key ) + return rawnext( objectData[ self ][ "data" ], key ) end, - __inext = function( self, ... ) - return rawinext( objectData[ self ][ "data" ], ... ) + __inext = function( self, idx ) + return rawinext( objectData[ self ][ "data" ], idx ) end, - __pairs = function( self, ... ) - return rawpairs( objectData[ self ][ "data" ], ... ) + __pairs = function( self ) + return rawpairs( objectData[ self ][ "data" ] ) end, __ipairs = function( self, ... ) - return rawipairs( objectData[ self ][ "data" ], ... ) + return rawipairs( objectData[ self ][ "data" ] ) end } @@ -528,6 +543,7 @@ local function literalString( str ) end ModUtil.ToString = ModUtil.Callable.Set( { }, function( _, o ) + ---@type boolean|string local identifier = o ~= nil and ModUtil.Identifiers.Data[ o ] identifier = identifier and identifier .. ": " or "" return identifier .. ModUtil.ToString.Static( o ) @@ -602,17 +618,18 @@ local function deepLoop( o, limit, dlimit, indent, seen, cond, depth ) end local _indent = '' if indent then - _indent = { } + local __indent = { } for i = 1, depth, 1 do _indent[ i ] = indent end - _indent = table.rawconcat( _indent ) + _indent = table.rawconcat( __indent ) end if type( o ) ~= "table" or (seen and seen[ o ]) or (cond and not cond( o )) then return limit, repv( o ) end if seen then seen[ o ] = true end local m = getmetatable( o ) + ---@type boolean|string local h = showTableAddrs or ( m and m.__call ) or isNamespace( o ) h = ( h and repv( o ) or "" ) .. '{' .. ( indent and '\n' .. _indent .. indent or '' ) local out = { } @@ -682,6 +699,7 @@ end ModUtil.Print = ModUtil.Callable.Set( { }, function ( _, ... ) print( ... ) + ---@diagnostic disable-next-line: undefined-global if DebugPrint then ModUtil.Print.Debug( ... ) end if io then if io.stdout ~= io.output( ) then @@ -706,6 +724,7 @@ end function ModUtil.Print.Debug( ... ) local text = ModUtil.String.Join( "\t", ModUtil.Args.Map( tostring, ... ) ):gsub( "\t", " " ) for line in text:gmatch( "([^\n]+)" ) do + ---@diagnostic disable-next-line: undefined-global DebugPrint{ Text = line } end end @@ -1119,6 +1138,7 @@ end -- Metaprogramming Shenanigans +---@type table local stackLevelProperty stackLevelProperty = { here = function( self ) @@ -1146,6 +1166,7 @@ stackLevelProperty = { end } +---@type table local stackLevelFunction = { gethook = function( self, ... ) return pusherror( debug.gethook, self.co, ... ) @@ -1346,6 +1367,7 @@ end local upvaluejoin = debug.upvaluejoin +---@diagnostic disable-next-line: duplicate-set-field function debug.upvaluejoin( f1, n1, f2, n2 ) upvaluejoin( f1, n1, f2, n2 ) setUpValueIdData( debug.upvalueid( f1, n1 ), f2, n2 ) @@ -2125,6 +2147,7 @@ ModUtil.Context.Env = ModUtil.Context( function( info ) end info.env = setmetatable( { }, { __index = function( _, key ) return rawget( env, key ) or info.penv[ key ] end, + ---@diagnostic disable-next-line: redundant-return-value __newindex = function( _, key, val ) return rawset( env, key, val ) end } ) info.final = { env } @@ -2434,13 +2457,62 @@ function ModUtil.ReferTable( obtain, ... ) return ModUtil.Proxy( { obtain = obtain }, ModUtil.Metatables.ReferTable ) end +-- Management + +local function setSaveIgnore(key, ignore) + ---@diagnostic disable-next-line: undefined-global + if SaveIgnores then + ---@diagnostic disable-next-line: undefined-global + SaveIgnores[key] = ignore + ---@diagnostic disable-next-line: undefined-global + elseif GlobalSaveWhitelist then + ---@diagnostic disable-next-line: undefined-global + GlobalSaveWhitelist[key] = not ignore + end +end + +--[[ + Create a namespace that can be used for the mod's functions + and data, and ensure that it doesn't end up in save files. + + modName - the name of the mod + parent - the parent mod, or nil if this mod stands alone +--]] +function ModUtil.Mod.Register( first, second, meta ) + local modName, parent + if type( first ) == "string" then + modName, parent = first, second + else + modName, parent = second, first + end + if not parent then + parent = _G + setSaveIgnore( modName, true ) + end + local mod = parent[ modName ] or { } + parent[ modName ] = mod + local path = ModUtil.Identifiers.Data[ parent ] + if path ~= nil then + path = path .. '.' + else + path = '' + end + path = path .. modName + ModUtil.Mods.Data[ path ] = mod + ModUtil.Identifiers.Inverse[ path ] = mod + if meta == false then + return mod + end + return setmetatable( mod, ModUtil.Metatables.Mod ) +end + -- Internal access ModUtil.Internal = ModUtil.Entangled.Union( ) do local ups = ModUtil.UpValues( function( ) - return _ENV_ORIGINAL, + return _ENV_ORIGINAL, setSaveIgnore, objectData, newObjectData, isInt, deepLoop, repk, repv, showTableAddrs, decorators, overrides, refreshDecorHistory, cloneDecorHistory, cloneDecorNode, threadContexts, threadEnvironments, getEnv, replaceGlobalEnvironment, @@ -2451,6 +2523,6 @@ do ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) end --- Final Actions +-- final actions -replaceGlobalEnvironment( ) \ No newline at end of file +replaceGlobalEnvironment() \ No newline at end of file diff --git a/src/config.lua b/src/config.lua new file mode 100644 index 0000000..c35ed3e --- /dev/null +++ b/src/config.lua @@ -0,0 +1,10 @@ +---@meta _ +---@diagnostic disable +local config = { + Hades = { + PrintStackHeight = 10, + PrintStackCapacity = 80 + } +} +ModUtil.Config = config +return config \ No newline at end of file diff --git a/src/def.lua b/src/def.lua new file mode 100644 index 0000000..fad87c5 --- /dev/null +++ b/src/def.lua @@ -0,0 +1,3007 @@ +---@meta SGG_Modding-ModUtil + +---@alias SGG_Modding-ModUtil*-nil boolean|string|number|integer|function|table|thread|userdata|lightuserdata + +--[[ + DOCUMENTATION FOR MODUTIL IS A WORK IN PROGRESS! +]] +ModUtil = { + Mod = { }, + Args = { }, + String = { }, + Table = { }, + Path = { }, + Array = { }, + IndexArray = { }, + Entangled = { }, + Metatables = { }, + Hades = { } +} + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*getter fun(iter: table, key: K): value: V? + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*setter fun(iter: table, key: K, value: V) + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*length fun(iter: table): length: integer + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*next fun(iter: table, key: K?): key: K?, value: V? + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*inext fun(iter: table, index: integer?): index: integer?, value: V? + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*pairs fun(iter: table): next: SGG_Modding-ModUtil*next, iter: table + +---@generic K : SGG_Modding-ModUtil*-nil +---@generic V : any +---@alias SGG_Modding-ModUtil*ipairs fun(iter: table): inext: SGG_Modding-ModUtil*inext, iter: table + +-- base next, doesn't invoke __index +---@type SGG_Modding-ModUtil*next +function rawnext( iter, key ) end + +-- next that does invoke __index +---@type SGG_Modding-ModUtil*next +function next( iter, key ) end + +-- truly raw pairs, ignores __next and __pairs +---@type SGG_Modding-ModUtil*pairs +function rawpairs( iter ) + return rawnext, iter +end + +-- quasi-raw pairs, invokes __next but ignores __pairs +---@type SGG_Modding-ModUtil*pairs +function qrawpairs( iter ) + return next, iter +end + +-- doesn't invoke __index just like rawnext +---@type SGG_Modding-ModUtil*inext +function rawinext( iter, index ) end + +-- invokes __inext just like the new next +---@type SGG_Modding-ModUtil*inext +function inext( iter, index ) end + +-- truly raw ipairs, ignores __inext and __ipairs +---@type SGG_Modding-ModUtil*ipairs +function rawipairs( t ) end + +-- quasi-raw ipairs, invokes __inext but ignores __ipairs +---@type SGG_Modding-ModUtil*ipairs +function qrawipairs( t ) end + +-- ignore __tostring (not thread safe?) +---@param obj any +---@return string rep +function rawtostring( obj ) end + +---@param fn integer | function? +---@return table? +function getfenv( fn ) end + +--[[ + Replace a function's _ENV with a new environment table. + Global variable lookups (including function calls) in that function + will use the new environment table rather than the normal one. + This is useful for function-specific overrides. The new environment + table should generally have _G as its __index (and usually __newindex), + so that any globals other than those being deliberately overridden operate as usual. +]] +---@param fn integer | function? +---@param env table? +function setfenv( fn, env ) end + +-- base table.insert +---@param list table +---@param value any +function table.rawinsert( list, value ) end + +-- base table.insert +---@param list table +---@param pos integer +---@param value any +function table.rawinsert( list, pos, value ) end + +-- table.insert that respects metamethods +---@param list table +---@param value any +function table.insert( list, value ) end + +-- table.insert that respects metamethods +---@param list table +---@param pos integer +---@param value any +function table.insert( list, pos, value ) end + +-- base table.remove +---@param list table +---@param pos integer? +---@return any value +function table.rawremove( list, pos ) end + +-- table.remove that respects metamethods +---@param list table +---@param pos integer? +---@return any value +function table.remove( list, pos ) end + +-- base table.unpack +---@generic T: any +---@param list T[] +---@param start integer? +---@param stop integer? +---@return ... +function table.rawunpack( list, start, stop ) end + +-- table.unpack that respects metamethods +---@generic T: any +---@param list T[] +---@param start integer? +---@param stop integer? +---@return ... +function table.unpack( list, start, stop ) end + +-- base table.rawconcat +---@param list table +---@param sep string? +---@param start integer? +---@param stop integer? +---@return string concat +function table.rawconcat( list, sep, start, stop ) end + +-- table.concat that respects metamethods and includes more values +---@param list table +---@param sep string? +---@param start integer? +---@param stop integer? +---@return string concat +function table.concat( list, sep, start, stop ) end + +---@class Proxy +ModUtil.Metatables.Proxy = {} +---@type SGG_Modding-ModUtil*getter +function ModUtil.Metatables.Proxy:__index( key ) end +---@type SGG_Modding-ModUtil*setter +function ModUtil.Metatables.Proxy:__newindex( key, value ) end +---@type SGG_Modding-ModUtil*length +function ModUtil.Metatables.Proxy:__len() end +---@type SGG_Modding-ModUtil*next +function ModUtil.Metatables.Proxy:__next( key ) end +---@type SGG_Modding-ModUtil*inext +function ModUtil.Metatables.Proxy:__inext( index ) end +---@type SGG_Modding-ModUtil*pairs +function ModUtil.Metatables.Proxy:__pairs( ) end +---@type SGG_Modding-ModUtil*ipairs +function ModUtil.Metatables.Proxy:__ipairs( ) end + +---@generic K: SGG_Modding-ModUtil*-nil +---@generic V: any +---@param data table +---@param meta table? +---@return Proxy proxy +function ModUtil.Proxy( data, meta ) end + +---@class Raw: Proxy +ModUtil.Metatables.Raw = {} + +---@generic K: SGG_Modding-ModUtil*-nil +---@generic V: any +---@param data table +---@return Raw proxy +function ModUtil.Raw( data ) end + +-- Operations on Callables + +---@overload fun(_: any, obj: table): boolean +ModUtil.Callable = { Func = { } } + +---@param obj table | function +---@return table? parent +---@return table | function? call +function ModUtil.Callable.Get( obj ) end + +---@param obj table | function +---@param call function +---@return table? parent +---@return table | function? call +function ModUtil.Callable.Set( obj, call ) end + +---@generic C: table|function +---@param obj C +---@param mcall fun(call:C, ...): C +---@return table? parent +---@return C? call +function ModUtil.Callable.Map( obj, mcall, ... ) end + +---@param obj table | function +---@return table | function? call +function ModUtil.Callable.Func.Get( obj ) end + +---@param obj table | function +---@param call function +---@return table | function? call +function ModUtil.Callable.Func.Set( obj, call ) end + +---@generic C: table|function +---@param obj C +---@param mcall fun(call:C, ...): C +---@return C? call +function ModUtil.Callable.Func.Map( obj, mcall, ... ) end + +-- Data Misc + +---@generic I: any +---@generic O: any +---@param map fun(input: I): output: O +---@param ... I +---@return O ... +function ModUtil.Args.Map( map, ... ) end + +---@generic A: any +---@param n integer +---@param ... A +---@return A ... +function ModUtil.Args.Take( n, ... ) end + +--- TODO: ... + +--[==[ + +function ModUtil.Args.Drop( n, ... ) + local args = table.pack( ... ) + return table.rawunpack( args, n + 1, args.n ) +end + +function ModUtil.Args.Join( args, ... ) + local args = ModUtil.Array.Join( args, table.pack( ... ) ) + return table.rawunpack( args ) +end + +function ModUtil.Table.Map( tbl, map ) + local out = { } + for k, v in pairs( tbl ) do + out[ k ] = map( v ) + end + return out +end + +function ModUtil.Table.Mutate( tbl, map ) + for k, v in pairs( tbl ) do + tbl[ k ] = map( v ) + end +end + +function ModUtil.Table.Replace( target, data ) + for k in pairs( target ) do + target[ k ] = data[ k ] + end + for k, v in pairs( data ) do + target[ k ] = v + end +end + +function ModUtil.Table.UnKeyed( tbl ) + local n = #tbl + for k in pairs( tbl ) do + if type( k ) ~= "number" or k > n or k < 1 or k ~= math.floor(k) then return false end + end + return true +end + + +function ModUtil.String.Join( sep, ... ) + return table.rawconcat( table.pack( ... ), sep ) +end + +function ModUtil.String.Chunk( text, chunkSize, maxChunks ) + local chunks = { "" } + local cs = 0 + local ncs = 1 + for chr in text:gmatch( "." ) do + cs = cs + 1 + if cs > chunkSize or chr == "\n" then + ncs = ncs + 1 + if maxChunks and ncs > maxChunks then + return chunks + end + chunks[ ncs ] = "" + cs = 0 + end + if chr ~= "\n" then + chunks[ ncs ] = chunks[ ncs ] .. chr + end + end + return chunks +end + +-- String Representations + +local escapeCharacters = { + ['\\'] = '\\\\', ["'"] = "\'", ['"'] = '\"', + ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t', + ['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f' +} + +local function literalString( str ) + for chr, esc in pairs( escapeCharacters ) do + str = str:gsub( chr, esc ) + end + return "'" .. str .. "'" +end + +ModUtil.ToString = ModUtil.Callable.Set( { }, function( _, o ) + ---@type boolean|string + local identifier = o ~= nil and ModUtil.Identifiers.Data[ o ] + identifier = identifier and identifier .. ": " or "" + return identifier .. ModUtil.ToString.Static( o ) +end ) + +function ModUtil.ToString.Address( o ) + local t = type( o ) + if t == "string" or passByValueTypes[ t ] then return nil end + return rawtostring( o ):match( ": 0*([0-9A-F]*)" ) +end + +function ModUtil.ToString.Static( o ) + local t = type( o ) + if t == "string" or passByValueTypes[ t ] then return tostring( o ) end + return tostring( o ):gsub( ": 0*", ": ", 1 ) +end + +function ModUtil.ToString.Value( o ) + local t = type( o ) + if t == 'string' then + return literalString( o ) + end + if passByValueTypes[ t ] then + return tostring( o ) + end + return '<' .. ModUtil.ToString( o ) .. '>' +end + +function ModUtil.ToString.Key( o ) + local t = type( o ) + if t == 'string' then + if not excludedFieldNames[ o ] and o:match( "^[a-zA-Z_][a-zA-Z0-9_]*$" ) then + return o + end + return '[' .. literalString( o ) .. ']' + end + if passByValueTypes[ t ] then + return "[" .. tostring( o ) .. "]" + end + return '<' .. ModUtil.ToString( o ) .. '>' +end + +function ModUtil.ToString.TableKeys( o ) + if type( o ) == 'table' then + local out = { } + for k in pairs( o ) do + rawinsert( out , ModUtil.ToString.Key( k ) ) + end + return rawconcat( out, ',' ) + end +end + +local function isNamespace( obj ) + return obj == _ENV_ORIGINAL or obj == _ENV_REPLACED or obj == objectData or ModUtil.Mods.Inverse[ obj ] + or ( getmetatable( obj ) == ModUtil.Metatables.Raw and isNamespace( objectData[ obj ][ "data" ] ) ) +end + +local function isNotNamespace( obj ) + return not isNamespace( obj ) +end + +local showTableAddrs = false + +local repk, repv = ModUtil.ToString.Key, ModUtil.ToString.Value + +local function deepLoop( o, limit, dlimit, indent, seen, cond, depth ) + depth = depth or 0 + if dlimit then + if dlimit <= depth then + return limit, repv( o ) + end + end + local _indent = '' + if indent then + local __indent = { } + for i = 1, depth, 1 do + _indent[ i ] = indent + end + _indent = table.rawconcat( __indent ) + end + if type( o ) ~= "table" or (seen and seen[ o ]) or (cond and not cond( o )) then + return limit, repv( o ) + end + if seen then seen[ o ] = true end + local m = getmetatable( o ) + ---@type boolean|string + local h = showTableAddrs or ( m and m.__call ) or isNamespace( o ) + h = ( h and repv( o ) or "" ) .. '{' .. ( indent and '\n' .. _indent .. indent or '' ) + local out = { } + local i = 0 + local broken = false + depth = depth + 1 + for j, v in ipairs( o ) do + if cond and not cond( v ) then break end + i = j + if limit and limit <= 0 then + out[ i ] = "..." + broken = true + break + end + if limit then + limit = limit - 1 + end + limit, v = deepLoop( v, limit, dlimit, indent, seen, cond, depth ) + out[ i ] = v + end + if not broken then + local j = i + for k, v in pairs( o ) do + if ( not cond or cond( v ) ) and ( not isInt( k ) or k < 1 or k > j ) then + i = i + 1 + if limit and limit <= 0 then + out[ i ] = '...' + break + end + if limit then + limit = limit - 1 + end + limit, v = deepLoop( v, limit, dlimit, indent, seen, cond, depth ) + out[ i ] = repk( k ) .. ' = ' .. v + end + end + end + local _end = ( indent and '\n' .. _indent or '' ) .. '}' + if i == 0 then return limit, h .. _end end + out[ 1 ] = h .. out[ 1 ] + out[ i ] = out[ i ] .. _end + return limit, rawconcat( out, ',' .. ( indent and '\n' .. _indent .. indent or ' ' ) ) +end + +function ModUtil.ToString.Shallow( object, limit, indent ) + local _, out = deepLoop( object, limit, 1, indent ) + return out +end + +ModUtil.ToString.Deep = ModUtil.Callable.Set( { }, function( _, object, limit, depth, indent ) + local _, out = deepLoop( object, limit, depth, indent, { } ) + return out +end ) + + +function ModUtil.ToString.Deep.NoNamespaces( object, limit, depth, indent ) + local _, out = deepLoop( object, limit, depth, indent, { }, isNotNamespace ) + return out +end + +function ModUtil.ToString.Deep.Namespaces( object, limit, depth, indent ) + local _, out = deepLoop( object, limit, depth, indent, { }, isNamespace ) + return out +end + +-- Print + +ModUtil.Print = ModUtil.Callable.Set( { }, function ( _, ... ) + print( ... ) + ---@diagnostic disable-next-line: undefined-global + if DebugPrint then ModUtil.Print.Debug( ... ) end + if io then + if io.stdout ~= io.output( ) then + ModUtil.Print.ToFile( io.output( ), ... ) + io.flush( ) + end + end +end ) + +function ModUtil.Print.ToFile( file, ... ) + local close = false + if type( file ) == "string" and io then + file = io.open( file, "a" ) + close = true + end + file:write( ModUtil.Args.Map( tostring, ... ) ) + if close then + file:close( ) + end +end + +function ModUtil.Print.Debug( ... ) + local text = ModUtil.String.Join( "\t", ModUtil.Args.Map( tostring, ... ) ):gsub( "\t", " " ) + for line in text:gmatch( "([^\n]+)" ) do + ---@diagnostic disable-next-line: undefined-global + DebugPrint{ Text = line } + end +end + +function ModUtil.Print.Traceback( level ) + level = (level or 1) + 1 + ModUtil.Print("Traceback:") + local cont = true + while cont do + local text = debug.traceback( "", level ):sub( 2 ) + local first = true + local i = 1 + cont = false + for line in text:gmatch( "([^\n]+)" ) do + if first then + first = false + else + if line == "\t" then + break + end + if i > 10 then + cont = true + break + end + ModUtil.Print( line ) + i = i + 1 + end + end + level = level + 10 + end +end + +function ModUtil.Print.DebugInfo( level ) + level = level or 1 + local text + text = ModUtil.ToString.Deep( debug.getinfo( level + 1 ) ) + ModUtil.Print( "Debug Info:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) +end + +function ModUtil.Print.Namespaces( level ) + level = level or 1 + local text + ModUtil.Print("Namespaces:") + text = ModUtil.ToString.Deep.Namespaces( ModUtil.Locals( level + 1 ) ) + ModUtil.Print( "\t" .. "Locals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) + text = ModUtil.ToString.Deep.Namespaces( ModUtil.UpValues( level + 1 ) ) + ModUtil.Print( "\t" .. "UpValues:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) + text = ModUtil.ToString.Deep.Namespaces( _G ) + ModUtil.Print( "\t" .. "Globals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) +end + +function ModUtil.Print.Variables( level ) + level = level or 1 + local text + ModUtil.Print("Variables:") + text = ModUtil.ToString.Deep.NoNamespaces( ModUtil.Locals( level + 1 ) ) + ModUtil.Print( "\t" .. "Locals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) + text = ModUtil.ToString.Deep.NoNamespaces( ModUtil.UpValues( level + 1 ) ) + ModUtil.Print( "\t" .. "UpValues:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) + text = ModUtil.ToString.Deep.NoNamespaces( _G ) + ModUtil.Print( "\t" .. "Globals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) +end + +--[[ + Call a function with the provided arguments + instead of halting when an error occurs it prints the entire error traceback +--]] +function ModUtil.DebugCall( f, ... ) + return xpcall( f, function( err ) + ModUtil.Print( err ) + ModUtil.Print.DebugInfo( 2 ) + --ModUtil.Print.Namespaces( 2 ) + --ModUtil.Print.Variables( 2 ) + ModUtil.Print.Traceback( 2 ) + end, ... ) +end + +-- Data Manipulation + +--[[ + Return a slice of an array table, python style + would be written state[ start : stop : step ] in python + + start and stop are offsets rather than ordinals + meaning 0 corresponds to the start of the array + and -1 corresponds to the end +--]] +function ModUtil.Array.Slice( state, start, stop, step ) + local slice = { } + local n = #state + start = start or 0 + if start < 0 then + start = start + n + end + stop = stop or n + if stop < 0 then + stop = stop + n + end + for i = start, stop - 1, step do + table.insert( slice, state[ i + 1 ] ) + end + return slice +end + +function ModUtil.Array.Copy( a ) + return { table.unpack( a ) } +end + +--[[ + Concatenates arrays, in order. + + a, ... - the arrays +--]] +function ModUtil.Array.Join( a, ... ) + local b = ... + if not b then return ModUtil.Array.Copy( a ) end + local c = { } + local j = 0 + for i, v in ipairs( a ) do + c[ i ] = v + j = i + end + for i, v in ipairs( b ) do + c[ i + j ] = v + end + return ModUtil.Array.Join( c, ModUtil.Args.Drop( 1, ... ) ) +end + +ModUtil.Table.Copy = ModUtil.Callable.Set( { }, function( _, t ) + local c = { } + for k, v in pairs( t ) do + c[ k ] = v + end + return c +end ) + +function ModUtil.Table.Copy.Deep( t ) + local c = { } + for k, v in pairs( t ) do + if type( v ) == "table" then + v = ModUtil.Table.Copy( v ) + end + c[ k ] = v + end + return c +end + +function ModUtil.Table.Clear( t ) + for k in pairs( t ) do + t[ k ] = nil + end + return t +end + +function ModUtil.Table.Transpose( t ) + local i = { } + for k, v in pairs( t ) do + i[ v ] = k + end + return i +end + +function ModUtil.Table.Flip( t ) + local i = ModUtil.Table.Transpose( t ) + ModUtil.Table.Clear( t ) + for k, v in pairs( i ) do + t[ k ] = v + end + return t +end + +--[[ + Set all the values in inTable corresponding to keys + in nilTable to nil. + + For example, if inTable is + { + Foo = 5, + Bar = 6, + Baz = { + InnerFoo = 5, + InnerBar = 6 + } + } + + and nilTable is + { + Foo = true, + Baz = { + InnerBar = true + } + } + + then the result will be + { + Foo = nil + Bar = 6, + Baz = { + InnerFoo = 5, + InnerBar = nil + } + } +--]] +function ModUtil.Table.NilMerge( inTable, nilTable ) + for nilKey, nilVal in pairs( nilTable ) do + local inVal = inTable[ nilKey ] + if type( nilVal ) == "table" and type( inVal ) == "table" then + ModUtil.Table.NilMerge( inVal, nilVal ) + else + inTable[ nilKey ] = nil + end + end + return inTable +end + +--[[ + Set all the the values in inTable corresponding to values + in setTable to their values in setTable. + + For example, if inTable is + { + Foo = 5, + Bar = 6 + } + + and setTable is + { + Foo = 7, + Baz = { + InnerBar = 8 + } + } + + then the result will be + { + Foo = 7, + Bar = 6, + Baz = { + InnerBar = 8 + } + } +--]] +function ModUtil.Table.Merge( inTable, setTable ) + for setKey, setVal in pairs( setTable ) do + local inVal = inTable[ setKey ] + if type( setVal ) == "table" and type( inVal ) == "table" then + ModUtil.Table.Merge( inVal, setVal ) + else + inTable[ setKey ] = setVal + end + end + return inTable +end + +function ModUtil.Table.MergeKeyed( inTable, setTable ) + for setKey, setVal in pairs( setTable ) do + local inVal = inTable[ setKey ] + if type( setVal ) == "table" and type( inVal ) == "table" then + if ModUtil.Table.UnKeyed( setVal ) and ModUtil.Table.UnKeyed( inVal ) then + ModUtil.Table.Replace( inVal, setVal ) + else + ModUtil.Table.MergeKeyed( inVal, setVal ) + end + else + inTable[ setKey ] = setVal + end + end + return inTable +end + +-- Index Array Manipulation + +--[[ + Safely retrieve the a value from deep inside a table, given + an array of indices into the table. + + For example, if indexArray is { "a", 1, "c" }, then + Table[ "a" ][ 1 ][ "c" ] is returned. If any of Table[ "a" ], + Table[ "a" ][ 1 ], or Table[ "a" ][ 1 ][ "c" ] are nil, then nil + is returned instead. + + Table - the table to retrieve from + indexArray - the list of indices +--]] +function ModUtil.IndexArray.Get( baseTable, indexArray ) + local node = baseTable + for _, key in ipairs( indexArray ) do + if type( node ) ~= "table" then + return nil + end + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + node = ModUtil.Node.Data[ nodeType ].Get( node ) + else + node = node[ key ] + end + end + return node +end + +--[[ + Safely set a value deep inside a table, given an array of + indices into the table, and creating any necessary tables + along the way. + + For example, if indexArray is { "a", 1, "c" }, then + Table[ "a" ][ 1 ][ "c" ] = Value once this function returns. + If any of Table[ "a" ] or Table[ "a" ][ 1 ] does not exist, they + are created. + + baseTable - the table to set the value in + indexArray - the list of indices + value - the value to add +--]] +function ModUtil.IndexArray.Set( baseTable, indexArray, value ) + if next( indexArray ) == nil then + return false -- can't set the input argument + end + local n = #indexArray -- change to shallow copy + table.remove later + local node = baseTable + for i = 1, n - 1 do + local key = indexArray[ i ] + if not ModUtil.Node.New( node, key ) then return false end + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + node = ModUtil.Node.Data[ nodeType ].Get( node ) + else + node = node[ key ] + end + end + local key = indexArray[ n ] + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + return ModUtil.Node.Data[ nodeType ].Set( node, value ) + end + node[ key ] = value + return true +end + +function ModUtil.IndexArray.Map( baseTable, indexArray, map, ... ) + return ModUtil.IndexArray.Set( baseTable, indexArray, map( ModUtil.IndexArray.Get( baseTable, indexArray ), ... ) ) +end + +-- Path Manipulation + +function ModUtil.Path.Join( p, ... ) + local q = ... + if not q then return p end + if p == '' then return ModUtil.Path.Join( q, ModUtil.Args.Drop( 1, ... ) ) end + if q == '' then return ModUtil.Path.Join( p, ModUtil.Args.Drop( 1, ... ) ) end + return ModUtil.Path.Join( p .. '.' .. q, ModUtil.Args.Drop( 1, ... ) ) +end + +function ModUtil.Path.Map( path, map, ... ) + return ModUtil.IndexArray.Map( _G, ModUtil.Path.IndexArray( path ), map, ... ) +end + +--[[ + Create an index array from the provided Path. + + The returned array can be used as an argument to the safe table + manipulation functions, such as ModUtil.IndexArray.Set and ModUtil.IndexArray.Get. + + path - a dot-separated string that represents a path into a table +--]] +function ModUtil.Path.IndexArray( path ) + if type( path ) == "table" then return path end -- assume index array is given + local s = "" + local i = { } + for c in path:gmatch( "." ) do + if c ~= "." then + s = s .. c + else + table.insert( i, s ) + s = "" + end + end + if #s > 0 then + table.insert( i, s ) + end + return i +end + +--[[ + Safely get a value from a Path. + + For example, ModUtil.Path.Get( "a.b.c" ) returns a.b.c. + If either a or a.b is nil, nil is returned instead. + + path - the path to get the value + base - (optional) The table to retreive the value from. + If not provided, retreive a global. +--]] +function ModUtil.Path.Get( path, base ) + return ModUtil.IndexArray.Get( base or _G, ModUtil.Path.IndexArray( path ) ) +end + +--[[ + Safely get set a value to a Path. + + For example, ModUtil.Path.Set( "a.b.c", 1 ) sets a.b.c = 1. + If either a or a.b is nil, they are created. + + path - the path to get the value + base - (optional) The table to retreive the value from. + If not provided, retreive a global. +--]] +function ModUtil.Path.Set( path, value, base ) + return ModUtil.IndexArray.Set( base or _G, ModUtil.Path.IndexArray( path ), value ) +end + +-- Metaprogramming Shenanigans + +---@type table +local stackLevelProperty +stackLevelProperty = { + here = function( self ) + local thread = objectData[ self ][ "thread" ] + local cursize = objectData[ self ][ "level" ] + 1 + while debug.getinfo( thread, cursize, "f" ) do + cursize = cursize + 1 + end + return cursize - objectData[ self ][ "size" ] - 1 + end, + top = function( self ) + local thread = objectData[ self ][ "thread" ] + local level = objectData[ self ][ "level" ] + local cursize = level + 1 + while debug.getinfo( thread, cursize, "f" ) do + cursize = cursize + 1 + end + return cursize - level - 1 + end, + there = function( self ) return objectData[ self ][ "level" ] end, + bottom = function( self ) return objectData[ self ][ "size" ] end, + co = function( self ) return objectData[ self ][ "thread" ] end, + func = function( self ) + return debug.getinfo( self.co, self.here, "f" ).func + end +} + +---@type table +local stackLevelFunction = { + gethook = function( self, ... ) + return pusherror( debug.gethook, self.co, ... ) + end, + sethook = function( self, ... ) + return pusherror( debug.sethook, self.co, ... ) + end, + getlocal = function( self, ... ) + return pusherror( debug.getlocal, self.co, self.here, ... ) + end, + setlocal = function( self, ... ) + return pusherror( debug.setlocal, self.co, self.here, ... ) + end, + getinfo = function( self, ... ) + return pusherror( debug.getinfo, self.co, self.here, ... ) + end, + getupvalue = function( self, ... ) + return pusherror( debug.getupvalue, self.func, ... ) + end, + setupvalue = function( self, ... ) + return pusherror( debug.setupvalue, self.func, ... ) + end, + upvalueid = function( self, ... ) + return pusherror( debug.upvalueid, self.func, ... ) + end, + upvaluejoin = function( self, ... ) + return pusherror( debug.upvaluejoin, self.func, ... ) + end +} + +local stackLevelInterface = {} +for k, v in pairs( stackLevelProperty ) do + stackLevelInterface[ k ] = v + stackLevelProperty[ k ] = true +end +for k, v in pairs( stackLevelFunction ) do + stackLevelInterface[ k ] = v + stackLevelFunction[ k ] = true +end + +ModUtil.Metatables.StackLevel = { + __index = function( self, key ) + if stackLevelProperty[ key ] then + return stackLevelInterface[ key ]( self ) + elseif stackLevelFunction[ key ] then + local func = stackLevelInterface[ key ] + return function( ... ) + return pusherror( func, self, ... ) + end + end + end, + __newindex = function( ) end, + __len = function( ) + return 0 + end, + __next = function( self, key ) + repeat + key = next( stackLevelInterface, key ) + until stackLevelFunction[ key ] == nil + return key, self[ key ] + end, + __inext = function( ) end, + __pairs = function( self ) + return qrawpairs( self ), self + end, + __ipairs = function( self ) + return function( ) end, self + end, + __eq = function( self, other ) + return objectData[ self ][ "thread" ] == objectData[ other ][ "thread" ] + and objectData[ self ][ "size" ] == objectData[ other ][ "size" ] + and objectData[ self ][ "level" ] == objectData[ other ][ "level" ] + end +} + +function ModUtil.StackLevel( level ) + level = ( level or 1 ) + local thread = coroutine.running( ) + local size = level + 1 + if not debug.getinfo( thread, level, "f" ) then return end + while debug.getinfo( thread, size, "f" ) do + size = size + 1 + end + size = size - level - 1 + if size > 0 then + return ModUtil.Proxy( { level = level, size = size, thread = thread }, ModUtil.Metatables.StackLevel ) + end +end + +ModUtil.Metatables.StackLevels = { + __index = function( self, level ) + return ModUtil.StackLevel( ( level or 0 ) + objectData[ self ][ "level" ].here ) + end, + __newindex = function( ) end, + __len = function( self ) + return objectData[ self ][ "level" ].bottom + end, + __next = function( self, level ) + level = ( level or 0 ) + 1 + local stackLevel = self[ level ] + if stackLevel then + return level, stackLevel + end + end, + __pairs = function( self ) + return qrawpairs( self ), self + end +} +ModUtil.Metatables.StackLevels.__ipairs = ModUtil.Metatables.StackLevels.__pairs +ModUtil.Metatables.StackLevels.__inext = ModUtil.Metatables.StackLevels.__next + +function ModUtil.StackLevels( level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( level or 0 ) }, ModUtil.Metatables.StackLevels ) +end + + +local excludedUpValueNames = toLookup{ "_ENV" } + +ModUtil.Metatables.UpValues = { + __index = function( self, name ) + if excludedUpValueNames[ name ] then return end + local func = objectData[ self ][ "func" ] + local idx = 0 + repeat + idx = idx + 1 + local n, value = debug.getupvalue( func, idx ) + if n == name then + return value + end + until not n + end, + __newindex = function( self, name, value ) + if excludedUpValueNames[ name ] then return end + local func = objectData[ self ][ "func" ] + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = debug.getupvalue( func, idx ) + if n == name then + debug.setupvalue( func, idx, value ) + return + end + until not n + end, + __len = function( ) + return 0 + end, + __next = function( self, name ) + local func = objectData[ self ][ "func" ] + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = debug.getupvalue( func, idx ) + if n == name then + local value + repeat + idx = idx + 1 + n, value = debug.getupvalue( func, idx ) + if n and not excludedUpValueNames[ n ] then + return n, value + end + until not n + end + until not n + end, + __inext = function( ) end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return function( ) end, self + end +} + +ModUtil.UpValues = ModUtil.Callable.Set( { }, function( _, func ) + if type( func ) ~= "function" then + func = debug.getinfo( ( func or 1 ) + 1, "f" ).func + end + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues ) +end ) + +local idData = { } +setmetatable( idData, { __mode = "k" } ) + +local function getUpValueIdData( id ) + local tbl = idData[ id ] + return tbl.func, tbl.idx +end + +local function setUpValueIdData( id, func, idx ) + local tbl = idData[ id ] + if not tbl then + tbl = { } + idData[ id ] = tbl + end + tbl.func, tbl.idx = func, idx +end + +local upvaluejoin = debug.upvaluejoin + +---@diagnostic disable-next-line: duplicate-set-field +function debug.upvaluejoin( f1, n1, f2, n2 ) + upvaluejoin( f1, n1, f2, n2 ) + setUpValueIdData( debug.upvalueid( f1, n1 ), f2, n2 ) +end + +ModUtil.Metatables.UpValues.Ids = { + __index = function( self, idx ) + local func = objectData[ self ][ "func" ] + local name = debug.getupvalue( func, idx ) + if name and not excludedUpValueNames[ name ] then + local id = debug.upvalueid( func, idx ) + setUpValueIdData( id, func, idx ) + return id + end + end, + __newindex = function( self, idx, value ) + local func = objectData[ self ][ "func" ] + local name = debug.getupvalue( func, idx ) + if name and not excludedUpValueNames[ name ] then + local func2, idx2 = getUpValueIdData( value ) + debug.upvaluejoin( func, idx, func2, idx2 ) + return + end + end, + __len = function( self ) + return debug.getinfo( objectData[ self ][ "func" ], 'u' ).nups + end, + __next = function ( self, idx ) + local func = objectData[ self ][ "func" ] + idx = idx or 0 + local name + while true do + idx = idx + 1 + name = debug.getupvalue( func, idx ) + if not name then return end + if not excludedUpValueNames[ name ] then + return idx, self[ idx ] + end + end + end, + __pairs = function( self ) + return qrawpairs( self ) + end +} +ModUtil.Metatables.UpValues.Ids.__inext = ModUtil.Metatables.UpValues.Ids.__next +ModUtil.Metatables.UpValues.Ids.__ipairs = ModUtil.Metatables.UpValues.Ids.__pairs + +function ModUtil.UpValues.Ids( func ) + if type(func) ~= "function" then + func = debug.getinfo( ( func or 1 ) + 1, "f" ).func + end + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues.Ids ) +end + +ModUtil.Metatables.UpValues.Values = { + __index = function( self, idx ) + local name, value = debug.getupvalue( objectData[ self ][ "func" ], idx ) + if name and not excludedUpValueNames[ name ] then + return value + end + end, + __newindex = function( self, idx, value ) + local func = objectData[ self ][ "func" ] + local name = debug.getupvalue( func, idx ) + if name and not excludedUpValueNames[ name ] then + debug.setupvalue( func, idx, value ) + return + end + end, + __len = function( self ) + return debug.getinfo( objectData[ self ][ "func" ], 'u' ).nups + end, + __next = function ( self, idx ) + local func = objectData[ self ][ "func" ] + idx = idx or 0 + local name, value + while true do + idx = idx + 1 + name, value = debug.getupvalue( func, idx ) + if not name then return end + if not excludedUpValueNames[ name ] then + return idx, value + end + end + end, + __pairs = ModUtil.Metatables.UpValues.Ids.__pairs, + __ipairs = ModUtil.Metatables.UpValues.Ids.__ipairs +} +ModUtil.Metatables.UpValues.Values.__inext = ModUtil.Metatables.UpValues.Values.__next + +function ModUtil.UpValues.Values( func ) + if type(func) ~= "function" then + func = debug.getinfo( ( func or 1 ) + 1, "f" ).func + end + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues.Values ) +end + +ModUtil.Metatables.UpValues.Names = { + __index = function( self, idx ) + local name = debug.getupvalue( objectData[ self ][ "func" ], idx ) + if name and not excludedUpValueNames[ name ] then + return name + end + end, + __newindex = function( ) end, + __len = ModUtil.Metatables.UpValues.Values.__len, + __next = function ( self, idx ) + local func = objectData[ self ][ "func" ] + idx = idx or 0 + local name + while true do + idx = idx + 1 + name = debug.getupvalue( func, idx ) + if not name then return end + if not excludedUpValueNames[ name ] then + return idx, name + end + end + end, + __pairs = ModUtil.Metatables.UpValues.Ids.__pairs, + __ipairs = ModUtil.Metatables.UpValues.Ids.__ipairs +} +ModUtil.Metatables.UpValues.Names.__inext = ModUtil.Metatables.UpValues.Names.__next + +function ModUtil.UpValues.Names( func ) + if type(func) ~= "function" then + func = debug.getinfo( ( func or 1 ) + 1, "f" ).func + end + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues.Names ) +end + +ModUtil.Metatables.UpValues.Stacked = { + __index = function( self, name ) + if excludedUpValueNames[ name ] then return end + for _, level in pairs( objectData[ self ][ "levels" ] ) do + local idx = 0 + repeat + idx = idx + 1 + local n, v = level.getupvalue( idx ) + if n == name then + return v + end + until not n + end + end, + __newindex = function( self, name, value ) + if excludedUpValueNames[ name ] then return end + for _, level in pairs( objectData[ self ][ "levels" ] ) do + local idx = 0 + repeat + idx = idx + 1 + local n = level.getupvalue( idx ) + if n == name then + level.setupvalue( idx, value ) + return + end + until not n + end + end, + __len = function( ) + return 0 + end, + __next = function( self, name ) + local levels = objectData[ self ][ "levels" ] + for _, level in pairs( levels ) do + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = level.getupvalue( idx ) + if name and n == name or not name then + local value + repeat + idx = idx + 1 + n, value = level.getupvalue( idx ) + if n and not excludedUpValueNames[ n ] then + return n, value + end + until not n + end + until not n + end + end, + __inext = function( ) end, + __pairs = function( self ) + return qrawpairs( self ), self + end, + __ipairs = function( self ) + return function( ) end, self + end +} + +function ModUtil.UpValues.Stacked( level ) + return ModUtil.Proxy( { levels = ModUtil.StackLevels( ( level or 1 ) ) }, ModUtil.Metatables.UpValues.Stacked ) +end + +local excludedLocalNames = toLookup{ "(*temporary)", "(for generator)", "(for state)", "(for control)" } + +ModUtil.Metatables.Locals = { + __index = function( self, name ) + if excludedLocalNames[ name ] then return end + local level = objectData[ self ][ "level" ] + local idx = 0 + repeat + idx = idx + 1 + local n, v = level.getlocal( level, idx ) + if n == name then + return v + end + until not n + end, + __newindex = function( self, name, value ) + if excludedLocalNames[ name ] then return end + local level = objectData[ self ][ "level" ] + local idx = 0 + repeat + idx = idx + 1 + local n = level.getlocal( idx ) + if n == name then + level.setlocal( idx, value ) + return + end + until not n + end, + __len = function( ) + return 0 + end, + __next = function( self, name ) + local level = objectData[ self ][ "level" ] + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = level.getlocal( idx ) + if name and n == name or not name then + local value + repeat + idx = idx + 1 + n, value = level.getlocal( idx ) + if n and not excludedLocalNames[ n ] then + return n, value + end + until not n + end + until not n + end, + __inext = function( ) end, + __pairs = function( self ) + return qrawpairs( self ), self + end, + __ipairs = function( self ) + return function( ) end, self + end +} + +ModUtil.Locals = ModUtil.Callable.Set( { }, function( _, level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) }, ModUtil.Metatables.Locals ) +end ) + +ModUtil.Metatables.Locals.Values = { + __index = function( self, idx ) + local name, value = objectData[ self ][ "level" ].getlocal( idx ) + if name then + if not excludedLocalNames[ name ] then + return value + end + end + end, + __newindex = function( self, idx, value ) + local level = objectData[ self ][ "level" ] + local name = level.getlocal( idx ) + if name then + if not excludedLocalNames[ name ] then + level.setlocal( idx, value ) + end + end + end, + __len = function( self ) + local level = objectData[ self ][ "level" ] + local idx = 1 + while level.getlocal( level, idx ) do + idx = idx + 1 + end + return idx - 1 + end, + __next = function( self, idx ) + idx = idx or 0 + idx = idx + 1 + local name, val = objectData[ self ][ "level" ].getlocal( idx ) + if name then + if not excludedLocalNames[ name ] then + return idx, val + end + end + end, + __pairs = function( self ) + return qrawpairs( self ), self + end, +} +ModUtil.Metatables.Locals.Values.__ipairs = ModUtil.Metatables.Locals.Values.__pairs +ModUtil.Metatables.Locals.Values.__inext = ModUtil.Metatables.Locals.Values.__next + +--[[ + Example Use: + for i, value in pairs( ModUtil.Locals.Values( level ) ) do + -- stuff + end +--]] +function ModUtil.Locals.Values( level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) }, ModUtil.Metatables.Locals.Values ) +end + +ModUtil.Metatables.Locals.Names = { + __index = function( self, idx ) + local name = objectData[ self ][ "level" ].getlocal( idx ) + if name then + if not excludedLocalNames[ name ] then + return name + end + end + end, + __newindex = function( ) end, + __len = ModUtil.Metatables.Locals.Values.__len, + __next = function( self, idx ) + if idx == nil then idx = 0 end + idx = idx + 1 + local name = objectData[ self ][ "level" ].getlocal( idx ) + if name then + if not excludedLocalNames[ name ] then + return idx, name + end + end + end, + __pairs = function( self ) + return qrawpairs( self ), self + end, +} +ModUtil.Metatables.Locals.Names.__ipairs = ModUtil.Metatables.Locals.Names.__pairs +ModUtil.Metatables.Locals.Names.__inext = ModUtil.Metatables.Locals.Names.__next + +--[[ + Example Use: + for i, name in pairs( ModUtil.Locals.Names( level ) ) do + -- stuff + end +--]] +function ModUtil.Locals.Names( level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) }, ModUtil.Metatables.Locals.Names ) +end + +ModUtil.Metatables.Locals.Stacked = { + __index = function( self, name ) + if excludedLocalNames[ name ] then return end + for _, level in pairs( objectData[ self ][ "levels" ] ) do + local idx = 0 + repeat + idx = idx + 1 + local n, v = level.getlocal( idx ) + if n == name then + return v + end + until not n + end + end, + __newindex = function( self, name, value ) + if excludedLocalNames[ name ] then return end + for _, level in pairs( objectData[ self ][ "levels" ] ) do + local idx = 0 + repeat + idx = idx + 1 + local n = level.getlocal( idx ) + if n == name then + level.setlocal( idx, value ) + return + end + until not n + end + end, + __len = function( ) + return 0 + end, + __next = function( self, name ) + local levels = objectData[ self ][ "levels" ] + for _, level in pairs( levels ) do + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = level.getlocal( idx ) + if name and n == name or not name then + local value + repeat + idx = idx + 1 + n, value = level.getlocal( idx ) + if n and not excludedLocalNames[ n ] then + return n, value + end + until not n + end + until not n + end + end, + __inext = function( ) end, + __pairs = function( self ) + return qrawpairs( self ), self + end, + __ipairs = function( self ) + return function( ) end, self + end +} + +--[[ + Access to local variables, in the current function and callers. + The most recent definition with a given name on the call stack will + be used. + + For example, if your function is called from CreateTraitRequirements, + you could access its 'local screen' as ModUtil.Locals.Stacked( ).screen + and its 'local hasRequirement' as ModUtil.Locals.Stacked( ).hasRequirement. +--]] +function ModUtil.Locals.Stacked( level ) + return ModUtil.Proxy( { levels = ModUtil.StackLevels( level or 1 ) }, ModUtil.Metatables.Locals.Stacked ) +end + +-- Entangled Data Structures + +ModUtil.Metatables.Entangled = { } + +ModUtil.Metatables.Entangled.Union = { + + __index = function( self, key ) + local value + for t in pairs( objectData[ self ][ "Members" ] ) do + value = t[ key ] + if value ~= nil then + return value + end + end + return objectData[ self ][ "Reserve" ][ key ] + end, + __newindex = function( self, key, value ) + if value ~= nil then + objectData[ self ][ "Keys" ][ key ] = true + else + objectData[ self ][ "Keys" ][ key ] = nil + end + local hit = false + for t in pairs( objectData[ self ][ "Members" ] ) do + if t[ key ] ~= nil then + t[ key ] = value + hit = true + end + end + if hit then + objectData[ self ][ "Reserve" ][ key ] = nil + else + objectData[ self ][ "Reserve" ][ key ] = value + end + end, + __len = function( self ) + local m = #objectData[ self ][ "Reserve" ] + local n + for t in pairs( objectData[ self ][ "Members" ] ) do + n = #t + if n > m then + m = n + end + end + return m + end, + __next = function( self, key ) + key = next( objectData[ self ][ "Keys" ], key ) + return key, self[ key ] + end, + __inext = function( self, idx ) + idx = ( idx or 0 ) + 1 + local val = self[ idx ] + if val == nil then return end + return idx, val + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + +} + +ModUtil.Entangled.Union = ModUtil.Callable.Set( { }, function( _, ... ) + local keys, members = { }, toLookup{ ... } + local union = { Reserve = { }, Keys = keys, Members = members } + for t in pairs( members ) do + for k in pairs( t ) do + keys[ k ] = true + end + end + return ModUtil.Proxy( union, ModUtil.Metatables.Entangled.Union ) +end ) + +function ModUtil.Entangled.Union.Add( union, ... ) + local members = objectData[ union ][ "Members" ] + local keys = objectData[ union ][ "Keys" ] + local reserve = objectData[ union ][ "Reserve" ] + for t in pairs( toLookup{ ... } ) do + members[ t ] = true + for k in pairs( t ) do + keys[ k ] = true + reserve[ k ] = nil + end + end +end + +function ModUtil.Entangled.Union.Sub( union, ... ) + local members = objectData[ union ][ "Members" ] + local keys = objectData[ union ][ "Keys" ] + for t in pairs( toLookup{ ... } ) do + members[ t ] = nil + end + for k in pairs( keys ) do + if union[ k ] == nil then + keys[ k ] = nil + end + end +end + +ModUtil.Metatables.Entangled.Map = { + + Data = { + __index = function( self, key ) + return objectData[ self ][ "Map" ][ key ] + end, + __newindex = function( self, key, value ) + local data = objectData[ self ][ "Map" ] + local prevValue = data[ key ] + data[ key ] = value + local preImage = objectData[ self ][ "PreImage" ] + local prevKeys + if prevValue ~= nil then + prevKeys = preImage[ prevValue ] + end + local keys = preImage[ value ] + if not keys then + keys = { } + preImage[ value ] = keys + end + if prevKeys then + prevKeys[ key ] = nil + end + keys[ key ] = true + end, + __len = function( self ) + return #objectData[ self ][ "Map" ] + end, + __next = function( self, key ) + return next( objectData[ self ][ "Map" ], key ) + end, + __inext = function( self, idx ) + return inext( objectData[ self ][ "Map" ], idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + }, + + PreImage = { + __index = function( self, value ) + return objectData[ self ][ "PreImage" ][ value ] + end, + __newindex = function( self, value, keys ) + objectData[ self ][ "PreImage" ][ value ] = keys + local data = objectData[ self ][ "Map" ] + for key in pairs( data ) do + data[ key ] = nil + end + for key in ipairs( keys ) do + data[ key ] = value + end + end, + __len = function( self ) + return #objectData[ self ][ "PreImage" ] + end, + __next = function( self, key ) + return next( objectData[ self ][ "PreImage" ], key ) + end, + __inext = function( self, idx ) + return inext( objectData[ self ][ "PreImage" ], idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + }, + + Unique = { + + Data = { + __index = function( self, key ) + return objectData[ self ][ "Data" ][ key ] + end, + __newindex = function( self, key, value ) + local data, inverse = objectData[ self ][ "Data" ], objectData[ self ][ "Inverse" ] + if value ~= nil then + local k = inverse[ value ] + if k ~= key then + if k ~= nil then + data[ k ] = nil + end + inverse[ value ] = key + end + end + if key ~= nil then + local v = data[ key ] + if v ~= value then + if v ~= nil then + inverse[ v ] = nil + end + data[ key ] = value + end + end + end, + __len = function( self ) + return #objectData[ self ][ "Data" ] + end, + __next = function( self, key ) + return next( objectData[ self ][ "Data" ], key ) + end, + __inext = function( self, idx ) + return inext( objectData[ self ][ "Data" ], idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + }, + + Inverse = { + __index = function( self, value ) + return objectData[ self ][ "Inverse" ][ value ] + end, + __newindex = function( self, value, key ) + local data, inverse = objectData[ self ][ "Data" ], objectData[ self ][ "Inverse" ] + if value ~= nil then + local k = inverse[ value ] + if k ~= key then + if k ~= nil then + data[ k ] = nil + end + inverse[ value ] = key + end + end + if key ~= nil then + local v = data[ key ] + if v ~= value then + if v ~= nil then + inverse[ v ] = nil + end + data[ key ] = value + end + end + end, + __len = function( self ) + return #objectData[ self ][ "Inverse" ] + end, + __next = function( self, value ) + return next( objectData[ self ][ "Inverse" ], value ) + end, + __inext = function( self, idx ) + return inext( objectData[ self ][ "Inverse" ], idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + } + + } + +} + +ModUtil.Entangled.Map = ModUtil.Callable.Set( { Unique = { } }, function( ) + local data, preImage = { }, { } + data, preImage = { Data = data, PreImage = preImage }, { Data = data, PreImage = preImage } + data = ModUtil.Proxy( data, ModUtil.Metatables.Entangled.Map.Data ) + preImage = ModUtil.Proxy( preImage, ModUtil.Metatables.Entangled.Map.PreImage ) + return { Data = data, Index = preImage, PreImage = preImage } +end ) + +ModUtil.Entangled.Map.Unique = ModUtil.Callable.Set( { }, function( ) + local data, inverse = { }, { } + data, inverse = { Data = data, Inverse = inverse }, { Data = data, Inverse = inverse } + data = ModUtil.Proxy( data, ModUtil.Metatables.Entangled.Map.Unique.Data ) + inverse = ModUtil.Proxy( inverse, ModUtil.Metatables.Entangled.Map.Unique.Inverse ) + return { Data = data, Index = inverse, Inverse = inverse } +end ) + +-- Context Managers + +local threadContexts = { } +setmetatable( threadContexts, { __mode = "kv" } ) + +ModUtil.Metatables.Context = { + __call = function( self, arg, callContext, ... ) + + local thread = coroutine.running( ) + + local contextInfo = { + call = callContext, + wrap = function( ... ) callContext( ... ) end, + parent = threadContexts[ thread ], + thread = thread + } + + threadContexts[ thread ] = contextInfo + + local penv = threadEnvironments[ thread ] + contextInfo.penv = threadEnvironments[ thread ] or _ENV_ORIGINAL + + contextInfo.context = self + contextInfo.args = table.pack( ... ) + contextInfo.arg = arg + contextInfo.data = { } + contextInfo.params = table.pack( objectData[ self ][ "prepContext" ]( contextInfo ) ) + + threadEnvironments[ thread ] = contextInfo.env + contextInfo.response = table.pack( contextInfo.wrap( table.unpack( contextInfo.params ) ) ) + threadEnvironments[ thread ] = penv + + if objectData[ self ][ "postCall" ] then + contextInfo.final = table.pack( objectData[ self ][ "postCall" ]( contextInfo ) ) + end + + threadContexts[ thread ] = contextInfo.parent + + if contextInfo.final then + return table.unpack( contextInfo.final ) + end + end +} + +ModUtil.Context = ModUtil.Callable.Set( { }, function( _, prepContext, postCall ) + return ModUtil.Proxy( { prepContext = prepContext, postCall = postCall }, ModUtil.Metatables.Context ) +end ) + +ModUtil.Context.Data = ModUtil.Context( function( info ) + local tbl = info.arg + info.env = setmetatable( { }, { + __index = function( _, key ) return tbl[ key ] or info.penv[ key ] end, + __newindex = tbl + } ) + info.final = { tbl } +end ) + +ModUtil.Context.Meta = ModUtil.Context( function( info ) + local tbl = ModUtil.Node.Data.Metatable.New( info.arg ) + info.env = setmetatable( { }, { + __index = function( _, key ) return tbl[ key ] or info.penv[ key ] end, + __newindex = tbl + } ) + info.final = { tbl } +end ) + +ModUtil.Context.Env = ModUtil.Context( function( info ) + local func = ModUtil.Overriden( info.arg ) + local env = getfenv( func ) + if env == nil then return end + if env == _ENV_REPLACED then + env = setfenv( func, setmetatable( { }, { + __index = _ENV_REPLACED, __newindex = _ENV_REPLACED + } ) ) + end + info.env = setmetatable( { }, { + __index = function( _, key ) return rawget( env, key ) or info.penv[ key ] end, + ---@diagnostic disable-next-line: redundant-return-value + __newindex = function( _, key, val ) return rawset( env, key, val ) end + } ) + info.final = { env } +end ) + +ModUtil.Context.Call = ModUtil.Context( + function( info ) + info.env = setmetatable( { }, { __index = info.penv } ) + return table.unpack( info.args ) + end, + function( info ) + local thread = coroutine.running( ) + local penv = threadEnvironments[ thread ] + threadEnvironments[ thread ] = info.env + local ret = table.pack( info.arg( table.unpack( info.args ) ) ) + threadEnvironments[ thread ] = penv + return table.unpack( ret ) + end +) + +ModUtil.Context.Wrap = ModUtil.Callable.Set( { }, function( _, func, context, mod ) + return ModUtil.Wrap.Bottom( func, function( base, ... ) return ModUtil.Context.Call( base, context, ... ) end, mod ) +end ) + + +ModUtil.Context.Wrap.Static = ModUtil.Context( + function( info ) + info.env = setmetatable( { }, { __index = info.penv } ) + end, + function( info ) + return ModUtil.Wrap( info.arg, function( base, ... ) + local thread = coroutine.running( ) + local penv = threadEnvironments[ thread ] + threadEnvironments[ thread ] = info.env + local ret = table.pack( base( ... ) ) + threadEnvironments[ thread ] = penv + return table.unpack( ret ) + end, info.args[1] ) + end +) + +-- Special Traversal Nodes + +ModUtil.Node = ModUtil.Entangled.Map.Unique( ) + +function ModUtil.Node.New( parent, key ) + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + return key.New( parent ) + end + local tbl = parent[ key ] + if tbl == nil then + tbl = { } + parent[ key ] = tbl + end + return tbl +end + +ModUtil.Node.Data.Meta = { + New = function( obj ) + local meta = getmetatable( obj ) + if meta == nil then + meta = { } + setmetatable( obj, meta ) + end + return meta + end, + Get = function( obj ) + return getmetatable( obj ) + end, + Set = function( obj, value ) + setmetatable( obj, value ) + return true + end +} + +ModUtil.Node.Data.Call = { + New = function( obj ) + return ModUtil.Callable.Func.Get( obj ) or error( "node new rejected, new call nodes are not meaningfully mutable.", 2 ) + end, + Get = function( obj ) + return ModUtil.Callable.Func.Get( obj ) + end, + Set = function( obj, value ) + ModUtil.Callable.Set( ModUtil.Callable.Get( obj ), value ) + return true + end +} + +ModUtil.Node.Data.UpValues = { + New = function( obj ) + return ModUtil.UpValues( obj ) + end, + Get = function( obj ) + return ModUtil.UpValues( obj ) + end, + Set = function( ) + error( "node set rejected, upvalues node cannot be set.", 2 ) + end +} + +-- Identifier System + +ModUtil.Identifiers = ModUtil.Entangled.Map.Unique( ) +setmetatable( objectData[ ModUtil.Identifiers.Data ][ "Inverse" ], { __mode = "k" } ) +setmetatable( objectData[ ModUtil.Identifiers.Inverse ][ "Data" ], { __mode = "v" } ) + +ModUtil.Identifiers.Inverse._G = _ENV_ORIGINAL +ModUtil.Identifiers.Inverse.ModUtil = ModUtil + +ModUtil.Mods = ModUtil.Entangled.Map.Unique( ) +setmetatable( objectData[ ModUtil.Mods.Data ][ "Inverse" ], { __mode = "k" } ) +setmetatable( objectData[ ModUtil.Mods.Inverse ][ "Data" ], { __mode = "v" } ) +ModUtil.Mods.Data.ModUtil = ModUtil + +-- Function Wrapping, Decoration, Overriding, Referral + +local decorators = setmetatable( { }, { __mode = "k" } ) +local overrides = setmetatable( { }, { __mode = "k" } ) + + +local function cloneDecorNode( node, base ) + local copy = ModUtil.Table.Copy( decorators[ node ] ) + base = base or copy.Base + local clone = copy.Func(base) + if decorators[ clone ] then + error( "decorator produced duplicate reference", 2 ) + end + decorators[ clone ] = copy + return clone +end + +local function cloneDecorHistory( parents, node, base ) + local clone = base + while node do + clone = cloneDecorNode( node, clone ) + node = parents[ node ] + end + return clone +end + +local function refreshDecorHistory( parent, stop ) + local node = decorators[ parent ].Base + if node == stop then return node end + node = decorators[ node ] and refreshDecorHistory( node ) or node + return cloneDecorNode( parent, node ) +end + +local function wrapDecorator( wrap ) + return function( base ) + return function( ... ) return wrap( base, ... ) end + end +end + +ModUtil.Decorate = ModUtil.Callable.Set( { }, function( _, base, func, mod ) + local out = func( base ) + if decorators[ out ] then + error( "decorator produced duplicate reference", 2 ) + end + decorators[ out ] = { Base = base, Func = func, Mod = mod } + return out +end ) + +function ModUtil.Decorate.Pop( obj ) + if not decorators[ obj ] then + error( "object has no decorators", 2 ) + end + return decorators[ obj ].Base +end + +function ModUtil.Decorate.Inject( base, func, mod ) + local out = func( base ) + if decorators[ out ] then + error( "decorator produced duplicate reference", 2 ) + end + local node, parent, parents = base, nil, { } + while decorators[ node ] do + parent = node + node = decorators[ node ].Base + parents[ node ] = parent + end + decorators[ out ] = { Base = node, Func = func, Mod = mod } + if parent then + return cloneDecorHistory( parents, parent, out ) + end + return out +end + +function ModUtil.Decorate.Eject( base ) + if not decorators[ base ] then + error( "object has no decorators", 2 ) + end + local node, parent, parents = base, nil, { } + while decorators[ node ] and decorators[ decorators[ node ].Base ] do + parent = node + node = decorators[ node ].Base + parents[ node ] = parent + end + if parent then + return cloneDecorHistory( parents, parent, decorators[ node ].Base ) + end + return decorators[ node ].Base +end + +function ModUtil.Decorate.Refresh( obj ) + if decorators[ obj ] then + return refreshDecorHistory( obj ) + end + return obj +end + +ModUtil.Wrap = ModUtil.Callable.Set( { }, function( _, base, wrap, mod ) + return ModUtil.Decorate( base, wrapDecorator( wrap ), mod ) +end ) + +function ModUtil.Wrap.Bottom( base, wrap, mod ) + return ModUtil.Decorate.Inject( base, wrapDecorator( wrap ), mod ) +end + +function ModUtil.Override( base, value, mod ) + if overrides[ value ] then + error( "cannot override with existing reference", 2 ) + end + local node, parent, parents = base, nil, { } + while decorators[ node ] do + parent = node + node = decorators[ node ].Base + parents[ node ] = parent + end + if type( value ) == "function" and type( node ) == "function" then + local env = getfenv( node ) + if getfenv( value ) == _ENV_REPLACED and env then + setfenv( value, env ) + end + end + overrides[ value ] = { Base = node, Mod = mod } + if parent then + return cloneDecorHistory( parents, parent, value ) + end + return value +end + +function ModUtil.Restore( base ) + local node, parent, parents = base, nil, { } + while decorators[ node ] do + parent = node + node = decorators[ node ].Base + parents[ node ] = parent + end + if overrides[ node ] then + node = overrides[ node ].Base + else + error( "object has no overrides", 2 ) + end + if parent then + return cloneDecorHistory( parents, parent, node ) + end + return node +end + +function ModUtil.Overriden( obj ) + local node = decorators[ obj ] + if not node then return obj end + return ModUtil.Overriden( node.Base ) +end + +function ModUtil.Original( obj ) + local node = decorators[ obj ] or overrides[ obj ] + if not node then return obj end + return ModUtil.Original( node.Base ) +end + +function ModUtil.ReferFunction( obtain ) + return function( ... ) + return obtain( )( ... ) + end +end + +ModUtil.Metatables.ReferTable = { + __index = function( self, key ) + return objectData[ self ][ "obtain" ]( )[ key ] + end, + __newindex = function( self, key, value ) + objectData[ self ][ "obtain" ]( )[ key ] = value + end, + __call = function( self, ... ) + return objectData[ self ][ "obtain" ]( )( ... ) + end, + __len = function( self ) + return #objectData[ self ][ "obtain" ]( ) + end, + __next = function( self, key ) + return next( objectData[ self ][ "obtain" ]( ), key ) + end, + __inext = function( self, idx ) + return inext( objectData[ self ][ "obtain" ]( ), idx ) + end, + __pairs = function( self ) + return pairs( objectData[ self ][ "obtain" ]( ) ) + end, + __ipairs = function( self ) + return ipairs( objectData[ self ][ "obtain" ]( ) ) + end +} + +function ModUtil.ReferTable( obtain, ... ) + return ModUtil.Proxy( { obtain = obtain }, ModUtil.Metatables.ReferTable ) +end + +-- Management + +local function setSaveIgnore(key, ignore) + ---@diagnostic disable-next-line: undefined-global + if SaveIgnores then + ---@diagnostic disable-next-line: undefined-global + SaveIgnores[key] = ignore + ---@diagnostic disable-next-line: undefined-global + elseif GlobalSaveWhitelist then + ---@diagnostic disable-next-line: undefined-global + GlobalSaveWhitelist[key] = not ignore + end +end + +--[[ + Create a namespace that can be used for the mod's functions + and data, and ensure that it doesn't end up in save files. + + modName - the name of the mod + parent - the parent mod, or nil if this mod stands alone +--]] +function ModUtil.Mod.Register( first, second, meta ) + local modName, parent + if type( first ) == "string" then + modName, parent = first, second + else + modName, parent = second, first + end + if not parent then + parent = _G + setSaveIgnore( modName, true ) + end + local mod = parent[ modName ] or { } + parent[ modName ] = mod + local path = ModUtil.Identifiers.Data[ parent ] + if path ~= nil then + path = path .. '.' + else + path = '' + end + path = path .. modName + ModUtil.Mods.Data[ path ] = mod + ModUtil.Identifiers.Inverse[ path ] = mod + if meta == false then + return mod + end + return setmetatable( mod, ModUtil.Metatables.Mod ) +end + +-- Internal access + +ModUtil.Internal = ModUtil.Entangled.Union( ) + +do + local ups = ModUtil.UpValues( function( ) + return _ENV_ORIGINAL, setSaveIgnore, + objectData, newObjectData, isInt, deepLoop, repk, repv, showTableAddrs, + decorators, overrides, refreshDecorHistory, cloneDecorHistory, cloneDecorNode, + threadContexts, threadEnvironments, getEnv, replaceGlobalEnvironment, + pusherror, getname, toLookup, wrapDecorator, isNamespace, + stackLevelFunction, stackLevelInterface, stackLevelProperty, + passByValueTypes, callableCandidateTypes, excludedFieldNames, escapeCharacters, literalString + end ) + ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) +end + +-- final actions + +replaceGlobalEnvironment() + +ModUtil.IndexArray.Context = { } + +function ModUtil.IndexArray.Context.Env( baseTable, indexArray, context ) + return ModUtil.Context.Env( ModUtil.IndexArray.Get( baseTable, indexArray ), context ) +end + +ModUtil.IndexArray.Wrap = ModUtil.Callable.Set( { }, function( _, baseTable, indexArray, wrap, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Wrap, wrap, mod ) +end ) + +function ModUtil.IndexArray.Wrap.Bottom( baseTable, indexArray, wrap, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Wrap.Bottom, wrap, mod ) +end + +ModUtil.IndexArray.Context.Wrap = ModUtil.Callable.Set( { }, function( _, baseTable, indexArray, context, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Context.Wrap, context, mod ) +end ) + +function ModUtil.IndexArray.Context.Wrap.Static( baseTable, indexArray, context, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Context.Wrap.Static, context, mod ) +end + +ModUtil.IndexArray.Decorate = ModUtil.Callable.Set( { }, function( _, baseTable, indexArray, func, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate, func, mod ) +end ) + +function ModUtil.IndexArray.Decorate.Pop( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate.Pop ) +end + +function ModUtil.IndexArray.Decorate.Inject( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate.Inject ) +end + +function ModUtil.IndexArray.Decorate.Eject( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate.Eject ) +end + +function ModUtil.IndexArray.Decorate.Refresh( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate.Refresh ) +end + +function ModUtil.IndexArray.Override( baseTable, indexArray, value, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Override, value, mod ) +end + +function ModUtil.IndexArray.Restore( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Restore ) +end + +function ModUtil.IndexArray.Overriden( baseTable, indexArray ) + return ModUtil.Overriden( ModUtil.IndexArray.Get( baseTable, indexArray ) ) +end + +function ModUtil.IndexArray.Original( baseTable, indexArray ) + return ModUtil.Original( ModUtil.IndexArray.Get( baseTable, indexArray ) ) +end + +function ModUtil.IndexArray.ReferFunction( baseTable, indexArray ) + return ModUtil.ReferFunction( function( ) + return ModUtil.IndexArray.Get( baseTable, indexArray ) + end ) +end + +function ModUtil.IndexArray.ReferTable( baseTable, indexArray ) + return ModUtil.ReferTable( function( ) + return ModUtil.IndexArray.Get( baseTable, indexArray ) + end ) +end + +--- + +ModUtil.Path.Context = { } + +function ModUtil.Path.Context.Env( path, context ) + return ModUtil.Context.Env( ModUtil.Path.Get( path ), context ) +end + +ModUtil.Path.Wrap = ModUtil.Callable.Set( { }, function( _, path, wrap, mod ) + return ModUtil.Path.Map( path, ModUtil.Wrap, wrap, mod ) +end ) + +function ModUtil.Path.Wrap.Bottom( path, wrap, mod ) + return ModUtil.Path.Map( path, ModUtil.Wrap.Bottom, wrap, mod ) +end + +ModUtil.Path.Context.Wrap = ModUtil.Callable.Set( { }, function( _, path, context, mod ) + return ModUtil.Path.Map( path, ModUtil.Context.Wrap, context, mod ) +end ) + +function ModUtil.Path.Context.Wrap.Static( path, context, mod ) + return ModUtil.Path.Map( path, ModUtil.Context.Wrap.Static, context, mod ) +end + +ModUtil.Path.Decorate = ModUtil.Callable.Set( { }, function( _, path, func, mod ) + return ModUtil.Path.Map( path, ModUtil.Decorate, func, mod ) +end ) + +function ModUtil.Path.Decorate.Pop( path ) + return ModUtil.Path.Map( path, ModUtil.Decorate.Pop ) +end + +function ModUtil.Path.Decorate.Refresh( path ) + return ModUtil.Path.Map( path, ModUtil.Decorate.Refresh ) +end + +function ModUtil.Path.Override( path, value, mod ) + return ModUtil.Path.Map( path, ModUtil.Override, value, mod ) +end + +function ModUtil.Path.Restore( path ) + return ModUtil.Path.Map( path, ModUtil.Restore ) +end + +function ModUtil.Path.Overriden( path ) + return ModUtil.Overriden( ModUtil.Path.Get( path ) ) +end + +function ModUtil.Path.Original( path ) + return ModUtil.Original( ModUtil.Path.Get( path ) ) +end + +function ModUtil.Path.ReferFunction( path ) + return ModUtil.ReferFunction( function( ) + return ModUtil.Path.Get( path ) + end ) +end + +function ModUtil.Path.ReferTable( path ) + return ModUtil.ReferTable( function( ) + return ModUtil.Path.Get( path ) + end ) +end + +--[[ + ModUtil Main + Components of ModUtil that depend on loading after Main.lua +]] + +-- Global Interception + +--[[ + Intercept global keys which are objects to return themselves + This way we can use other namespaces for UI etc +--]] + +local callableCandidateTypes = ModUtil.Internal.callableCandidateTypes +local setSaveIgnore = ModUtil.Internal.setSaveIgnore + +setSaveIgnore( "ModUtil", true ) + +rawset( _ENV, "GLOBALS", ModUtil.Internal._ENV_ORIGINAL ) +setSaveIgnore( "GLOBALS", true ) + +local function isPath( path ) + return path:find("[.]") + and not path:find("[.][.]+") + and not path:find("^[.]") + and not path:find("[.]$") +end + +local function routeKey( self, key ) + local t = type( key ) + if t == "string" and isPath( key ) then + return ModUtil.Path.Get( key ) + end + if callableCandidateTypes[ t ] then + return key + end +end + +local function extendGlobalEnvironment() + local meta = getmetatable( _ENV ) or { } + local mi = meta.__index + local mit = type(mi) + if mit == "function" then + meta.__index = ModUtil.Wrap( meta.__index, function( base, self, key ) + local value = base( self, key ) + if value ~= nil then return value end + return routeKey( self, key ) + end, ModUtil ) + elseif mit == "table" then + meta.__index = function( self, key ) + local value = mi[key] + if value ~= nil then return value end + return routeKey( self, key ) + end + else + meta.__index = routeKey + end + + setmetatable( _ENV, meta ) +end + +local objectData = ModUtil.Internal.objectData +local passByValueTypes = ModUtil.Internal.passByValueTypes + +local function modDataProxy( value, level ) + level = ( level or 1 ) + 1 + local t = type( value ) + if passByValueTypes[ t ] or t == "string" then + return value + end + if t == "table" then + if getmetatable( value ) then + error( "saved data tables cannot have values with metatables", level ) + end + return ModUtil.Entangled.ModData( value ) + end + error( "saved data tables cannot have values of type "..t..".", level ) +end + +local function modDataKey( key, level ) + local t = type( key ) + if passByValueTypes[ t ] or t == "string" then + return key + end + error( "saved data tables cannot have keys of type "..t..".", ( level or 1 ) + 1 ) +end + +local function modDataPlain( obj, key, value, level ) + level = ( level or 1 ) + 1 + if modDataKey( key, level ) ~= nil then + local t = type( value ) + if passByValueTypes[ t ] or t == "string" then + obj[ key ] = value + elseif t == "table" then + if getmetatable( value ) then + local state + state, value = pcall( function( ) return objectData[ value ] end ) + if not state or type( value ) ~= "table" then + error( "saved data tables cannot have values with metatables", level ) + end + end + for k, v in pairs( value ) do + modDataPlain( value, k, v, level ) + end + obj[ key ] = value + else + error( "saved data tables cannot have values of type "..t..".", level ) + end + end +end + +ModUtil.Metatables.Entangled.ModData = { + __index = function( self, key ) + return modDataProxy( objectData[ self ][ key ], 2 ) + end, + __newindex = function( self, key, value ) + modDataPlain( objectData[ self ], key, value, 2 ) + end, + __len = function( self ) + return #objectData[ self ] + end, + __next = function( self, key ) + local key = next( objectData[ self ], key ) + if modDataKey( key, 2 ) ~= nil then + return key, modDataProxy( objectData[ self ][ key ], 2 ) + end + end, + __inext = function( self, idx ) + local idx = inext( objectData[ self ], idx ) + if modDataKey( idx, 2 ) ~= nil then + return idx, modDataProxy( objectData[ self ][ idx ], 2 ) + end + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end +} + +function ModUtil.Entangled.ModData( value ) + return ModUtil.Proxy( value, ModUtil.Metatables.Entangled.ModData ) +end + +ModUtil.Mod.Data = setmetatable( { }, { + __call = function( _, mod ) + ModData = ModData or { } + setSaveIgnore( "ModData", false ) + local key = ModUtil.Mods.Inverse[ mod ] + local data = ModData[ key ] + if not data then + data = { } + ModData[ key ] = data + end + return modDataProxy( data, 2 ) + end, + __index = function( _, key ) + ModData = ModData or { } + return modDataProxy( ModData[ key ], 2 ) + end, + __newindex = function( _, key, value ) + ModData = ModData or { } + modDataPlain( ModData, key, value, 2 ) + end, + __len = function( ) + ModData = ModData or { } + return #ModData + end, + __next = function( _, key ) + ModData = ModData or { } + local key = next( ModData, key ) + if modDataKey( key, 2 ) ~= nil then + return key, modDataProxy( ModData[ key ], 2 ) + end + end, + __inext = function( _, idx ) + ModData = ModData or { } + local idx = inext( ModData, idx ) + if modDataKey( idx, 2 ) ~= nil then + return idx, modDataProxy( ModData[ idx ], 2 ) + end + end, + ---@type fun( t ): any, any?, any? + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end +} ) + +local relativeTable = { } + +relativeTable[ ModUtil.Mod.Data ] = true + +ModUtil.Metatables.Mod = { + __index = function( self, key ) + local val = ModUtil.Mod[ key ] + if relativeTable[ val ] then + return val( self ) + elseif type( val ) == "function" then + return ModUtil.Wrap( val, function( base, ... ) + return base( self, ... ) + end, ModUtil ) + end + return val + end +} + +-- Load Trigger Queue + +local funcsToLoad = { } + +local function loadFuncs( triggerArgs ) + for _, v in pairs( funcsToLoad ) do + v( triggerArgs ) + end + funcsToLoad = { } +end +---@diagnostic disable-next-line: undefined-global +if OnAnyLoad then + ---@diagnostic disable-next-line: undefined-global + OnAnyLoad{ function( triggerArgs ) loadFuncs( triggerArgs ) end } +end + +--[[ + Run the provided function once on the next in-game load. + + triggerFunction - the function to run +--]] +function ModUtil.LoadOnce( triggerFunction ) + table.insert( funcsToLoad, triggerFunction ) +end + +--[[ + Cancel running the provided function once on the next in-game load. + + triggerFunction - the function to cancel running +--]] +function ModUtil.CancelLoadOnce( triggerFunction ) + for i, v in ipairs( funcsToLoad ) do + if v == triggerFunction then + table.remove( funcsToLoad, i ) + end + end +end + +-- Internal Access + +do + local ups = ModUtil.UpValues( function( ) + return _ENV, funcsToLoad, loadFuncs, isPath, routeKey, callableCandidateTypes, setSaveIgnore, + objectData, passByValueTypes, modDataKey, modDataProxy, modDataPlain, relativeTable, extendGlobalEnvironment + end ) + ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) +end + +extendGlobalEnvironment() + +-- Debug Printing + +local printDisplay = nil + +function ModUtil.Hades.PrintDisplay( text , delay, color ) + if type(text) ~= "string" then + text = tostring(text) + end + text = " "..text.." " + if color == nil then + color = Color.Yellow + end + if delay == nil then + delay = 5 + end + if printDisplay then + Destroy({Ids = {printDisplay.Id}}) + end + printDisplay = CreateScreenComponent({Name = "BlankObstacle", Group = "PrintDisplay", X = ScreenCenterX, Y = 40 }) + CreateTextBox({ Id = printDisplay.Id, Text = text, FontSize = 22, Color = color, Font = "UbuntuMonoBold"}) + + if delay > 0 then + thread(function() + wait(delay) + Destroy({Ids = {printDisplay.Id}}) + printDisplay = nil + end) + end +end + +local printOverhead = { } + +function ModUtil.Hades.PrintOverhead(text, delay, color, dest) + if type(text) ~= "string" then + text = tostring(text) + end + text = " "..text.." " + if dest == nil then + dest = CurrentRun.Hero.ObjectId + end + if color == nil then + color = Color.Yellow + end + if delay == nil then + delay = 5 + end + Destroy({Ids = {printOverhead[dest]}}) + local id = SpawnObstacle({ Name = "BlankObstacle", Group = "PrintOverhead", DestinationId = dest }) + printOverhead[dest] = id + Attach({ Id = id, DestinationId = dest }) + CreateTextBox({ Id = id, Text = text, FontSize = 32, OffsetX = 0, OffsetY = -150, Color = color, Font = "AlegreyaSansSCBold", Justification = "Center" }) + if delay > 0 then + thread(function() + wait(delay) + if printOverhead[dest] then + Destroy({Ids = {id}}) + printOverhead[dest] = nil + end + end) + end +end + +local printStack = nil + +local function closePrintStack() + if printStack then + printStack.CullEnabled = false + PlaySound({ Name = "/SFX/Menu Sounds/GeneralWhooshMENU" }) + printStack.KeepOpen = false + + CloseScreen(GetAllIds(printStack.Components),0) + closeFuncs["PrintStack"] = nil + printStack = nil + end +end + +local function orderPrintStack(screen,components) + local v + if screen.CullPrintStack then + v = screen.TextStack[1] + if v.obj then + Destroy({Ids = {v.obj.Id}}) + components["TextStack_" .. v.tid] = nil + v.obj = nil + screen.TextStack[v.tid]=nil + end + thread( function() + local v = screen.TextStack[2] + if v then + wait(v.data.Delay) + if v.obj then + screen.CullPrintStack = true + end + end + end) + else + thread( function() + local v = screen.TextStack[1] + if v then + wait(v.data.Delay) + if v.obj then + screen.CullPrintStack = true + end + end + end) + end + screen.CullPrintStack = false + + for k,v in pairs(screen.TextStack) do + components["TextStack_" .. k] = nil + Destroy({Ids = {v.obj.Id}}) + end + + screen.TextStack = CollapseTable(screen.TextStack) + for i,v in pairs(screen.TextStack) do + v.tid = i + end + if #screen.TextStack == 0 then + return closePrintStack() + end + + local Ymul = screen.StackHeight+1 + local Ygap = 30 + local Yoff = 26*screen.StackHeight+22 + local n =#screen.TextStack + + if n then + for k=1,math.min(n,Ymul) do + v = screen.TextStack[k] + if v then + local data = v.data + screen.TextStack[k].obj = CreateScreenComponent({ Name = "rectangle01", Group = "PrintStack", X = -1000, Y = -1000}) + local textStack = screen.TextStack[k].obj + components["TextStack_" .. k] = textStack + SetScaleX({Id = textStack.Id, Fraction = 10/6}) + SetScaleY({Id = textStack.Id, Fraction = 0.1}) + SetColor({ Id = textStack.Id, Color = data.Bgcol }) + CreateTextBox({ Id = textStack.Id, Text = data.Text, FontSize = data.FontSize, OffsetX = 0, OffsetY = 0, Color = data.Color, Font = data.Font, Justification = "Center" }) + Attach({ Id = textStack.Id, DestinationId = components.Background.Id, OffsetX = 220, OffsetY = -Yoff }) + Yoff = Yoff - Ygap + end + end + end + +end + +function ModUtil.Hades.PrintStack( text, delay, color, bgcol, fontsize, font, sound ) + if color == nil then color = {1,1,1,1} end + if bgcol == nil then bgcol = {0.590, 0.555, 0.657,0.125} end + if fontsize == nil then fontsize = 13 end + if font == nil then font = "UbuntuMonoBold" end + if sound == nil then sound = "/Leftovers/SFX/AuraOff" end + if delay == nil then delay = 3 end + + if type(text) ~= "string" then + text = tostring(text) + end + text = " "..text.." " + + local first = false + if not printStack then + first = true + printStack = { Components = {} } + closeFuncs["PrintStack"] = closePrintStack + end + local screen = printStack + local components = screen.Components + + if first then + + screen.KeepOpen = true + screen.TextStack = {} + screen.CullPrintStack = false + screen.MaxStacks = ModUtil.Hades.PrintStackCapacity + screen.StackHeight = ModUtil.Hades.PrintStackHeight + PlaySound({ Name = "/SFX/Menu Sounds/DialoguePanelOutMenu" }) + components.Background = CreateScreenComponent({ Name = "BlankObstacle", Group = "PrintStack", X = ScreenCenterX, Y = 2*ScreenCenterY}) + components.Backing = CreateScreenComponent({ Name = "TraitTray_Center", Group = "PrintStack"}) + Attach({ Id = components.Backing.Id, DestinationId = components.Background.Id, OffsetX = -180, OffsetY = 0 }) + SetColor({ Id = components.Backing.Id, Color = {0.590, 0.555, 0.657, 0.8} }) + SetScaleX({Id = components.Backing.Id, Fraction = 6.25}) + SetScaleY({Id = components.Backing.Id, Fraction = 6/55*(2+screen.StackHeight)}) + + thread( function() + while screen do + wait(0.5) + if screen.CullEnabled then + if screen.CullPrintStack then + orderPrintStack(screen,components) + end + end + end + end) + + end + + if #screen.TextStack >= screen.MaxStacks then return end + + screen.CullEnabled = false + + local newText = {} + newText.obj = CreateScreenComponent({ Name = "rectangle01", Group = "PrintStack"}) + newText.data = {Delay = delay, Text = text, Color = color, Bgcol = bgcol, Font = font, FontSize = fontsize} + SetColor({ Id = newText.obj.Id, Color = {0,0,0,0}}) + table.insert(screen.TextStack, newText) + + PlaySound({ Name = sound }) + + orderPrintStack(screen,components) + + screen.CullEnabled = true + +end + +function ModUtil.Hades.PrintStackChunks( text, linespan, ... ) + if not linespan then linespan = 90 end + for _,s in ipairs( ModUtil.String.Chunk( text, linespan, ModUtil.Hades.PrintStackCapacity ) ) do + ModUtil.Hades.PrintStack( s, ... ) + end +end + +-- Trigger Proxy + +local triggers = { } + +local function isTrigger( name ) + if name:sub( 1, 2 ) ~= "On" then return false end + local func = _G[ name ] + return type( func ) == "function" and debug.getinfo( func, "S" ).what == "C" +end + +local proxyTriggerMeta = { + __index = function( s, k ) + if type( k ) == "string" then + local w, n = pcall( tonumber, k ) + if w then k = n end + end + return rawget( s, k ) + end, + __newindex = function( s, k, v ) + if type( k ) == "string" then + local w, n = pcall( tonumber, k ) + if w then k = n end + end + rawset( s, k, v ) + end +} + +local function proxyTriggerCallback( indexArray, func, args ) + local t = ModUtil.IndexArray.Get( triggers, indexArray ) or setmetatable( { }, proxyTriggerMeta ) + local n = #t + 1 + local f = ModUtil.Override( func, function( ... ) + return t[ n ].Call( ... ) + end ) + table.insert( t, { Args = args, Call = func } ) + ModUtil.IndexArray.Set( triggers, indexArray, t ) + return f +end + +local function proxyTrigger( name ) + ModUtil.IndexArray.Wrap( _G, { name }, function( base, args, ... ) + local cargs = ModUtil.Table.Copy( args ) + local func = table.remove( cargs ) + local file = debug.getinfo( 2, "S" ).source + args[ #args ] = proxyTriggerCallback( { name, file }, func, cargs ) + return base( args, ... ) + end ) +end + +setmetatable( triggers, { + __newindex = function( s, k, v ) + if v == true then + proxyTrigger( k ) + v = s[ k ] or { } + end + rawset( s, k, v ) + end +} ) +ModUtil.Hades.Triggers = triggers +ModUtil.Identifiers.Inverse[ triggers ] = "ModUtil.Hades.Triggers" + +--]==] + +-- Internal Access + +ModUtil.Internal = {} \ No newline at end of file diff --git a/src/main.lua b/src/main.lua new file mode 100644 index 0000000..3beeaa3 --- /dev/null +++ b/src/main.lua @@ -0,0 +1,6 @@ +---@meta _ +---@diagnostic disable + +---@module 'SGG_Modding-DemonDaemon' +local daemon = rom.mods['SGG_Modding-DemonDaemon'] +daemon.auto() \ No newline at end of file diff --git a/src/modfile.txt b/src/modfile.txt index 0e600d5..6754806 100644 --- a/src/modfile.txt +++ b/src/modfile.txt @@ -1,9 +1,8 @@ -:: Mod Utility - Load Priority 0 - To "Scripts/Main.lua" - Top Import "ModUtil.lua" - Import "ModUtil.Extra.lua" - Import "ModUtil.Main.lua" - Import "ModUtil.Compat.lua" - Import "ModUtil.Hades.lua" \ No newline at end of file +To "Scripts/Main.lua" +Top Import "ModUtil.lua" +Top Import "register.lua" +Top Import "config.lua" +Top Import "ModUtil.Extra.lua" +Import "ModUtil.Main.lua" +Import "ModUtil.Hades.lua" \ No newline at end of file diff --git a/src/register.lua b/src/register.lua new file mode 100644 index 0000000..2caa16a --- /dev/null +++ b/src/register.lua @@ -0,0 +1,3 @@ +---@meta _ +---@diagnostic disable +return ModUtil \ No newline at end of file diff --git a/thunderstore.toml b/thunderstore.toml index e9a34ed..78684cc 100644 --- a/thunderstore.toml +++ b/thunderstore.toml @@ -4,12 +4,14 @@ schemaVersion = "0.0.1" [package] namespace = "SGG_Modding" name = "ModUtil" -versionNumber = "2.10.1" -description = "Utility mod for mod interactions within lua for for SGG's games" +versionNumber = "3.0.0" +description = "Utility mod for mod interactions within lua for SGG's games" websiteUrl = "https://github.com/SGG-Modding/ModUtil" containsNsfwContent = false [package.dependencies] +Hell2Modding-Hell2Modding = "1.0.21" +SGG_Modding-DemonDaemon = "1.0.0" [build] icon = "./icon.png"