├── .gitignore ├── .luarc.json ├── LICENSE ├── README.md ├── classes.md ├── lua ├── autorun │ └── !!!!gpm.lua ├── gpm │ ├── .old │ │ ├── cli.yue │ │ ├── init_old.yue │ │ ├── libs │ │ │ ├── addon.yue │ │ │ ├── file.yue │ │ │ └── net.yue │ │ └── transport.yue │ ├── database.lua │ ├── detour.lua │ ├── engine.lua │ ├── init.lua │ ├── package │ │ ├── init.lua │ │ ├── loader.yue │ │ ├── repositories.yue │ │ └── sources │ │ │ ├── filesystem.yue │ │ │ └── github.yue │ └── std │ │ ├── addon.lua │ │ ├── audio.lua │ │ ├── bigint.lua │ │ ├── bit.lua │ │ ├── class.lua │ │ ├── client.lua │ │ ├── color.lua │ │ ├── console.logger.lua │ │ ├── console.lua │ │ ├── constants.lua │ │ ├── crypto.base64.lua │ │ ├── crypto.bitpack.lua │ │ ├── crypto.bytepack.lua │ │ ├── crypto.chacha20.lua │ │ ├── crypto.deflate.lua │ │ ├── crypto.hmac.lua │ │ ├── crypto.lua │ │ ├── crypto.lzw.lua │ │ ├── crypto.md5.lua │ │ ├── crypto.pack.lua │ │ ├── crypto.pbkdf2.lua │ │ ├── crypto.sha1.lua │ │ ├── crypto.uuid.lua │ │ ├── debug.gc.lua │ │ ├── debug.lua │ │ ├── decal.lua │ │ ├── effect.lua │ │ ├── entity.lua │ │ ├── error.lua │ │ ├── file.lua │ │ ├── file.path.lua │ │ ├── fluent.lua │ │ ├── futures.lua │ │ ├── game.classes.lua │ │ ├── game.hooks.lua │ │ ├── game.lua │ │ ├── gamemode.lua │ │ ├── hook.lua │ │ ├── http.github.lua │ │ ├── http.lua │ │ ├── input.lua │ │ ├── level.lua │ │ ├── math.classes.lua │ │ ├── math.ease.lua │ │ ├── math.lua │ │ ├── matproxy.lua │ │ ├── menu.lua │ │ ├── os.lua │ │ ├── os.window.lua │ │ ├── panel.lua │ │ ├── physics.lua │ │ ├── player.lua │ │ ├── protobuf.lua │ │ ├── render.lua │ │ ├── render.mesh.lua │ │ ├── render.surface.lua │ │ ├── server.lua │ │ ├── sqlite.lua │ │ ├── steam.identifier.lua │ │ ├── steam.lua │ │ ├── steam.workshop.lua │ │ ├── string.extensions.lua │ │ ├── string.lua │ │ ├── string.utf8.lua │ │ ├── structures.lua │ │ ├── table.lua │ │ ├── timer.lua │ │ ├── url.lua │ │ ├── version.lua │ │ └── weapon.lua ├── packages │ ├── b │ │ ├── async_module.yue │ │ ├── init.yue │ │ └── package.lua │ ├── exports │ │ ├── client.yue │ │ ├── example.yue │ │ ├── main.yue │ │ ├── menu.yue │ │ ├── package.yue │ │ └── server.yue │ ├── imports │ │ └── package.lua │ └── package_v2 │ │ ├── init.lua │ │ ├── package.lua │ │ └── submodule.lua └── tests │ └── gpm │ ├── libs │ └── file.yue │ ├── util.yue │ └── util │ ├── string.yue │ └── table.yue └── meta.lua /.gitignore: -------------------------------------------------------------------------------- 1 | lua/gpm/.old 2 | lua/gpm/vfs 3 | .vscode 4 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", 3 | "runtime.path": [ "?" ], 4 | "runtime.special": { 5 | "IncludeCS": "require", 6 | "include": "require" 7 | }, 8 | "runtime.version": "LuaJIT", 9 | "workspace.library": [ 10 | "${addons}/garrysmod/module/library" 11 | ], 12 | "runtime.nonstandardSymbol": [ 13 | "!", 14 | "!=", 15 | "&&", 16 | "||", 17 | "//", 18 | "/**/", 19 | "continue" 20 | ], 21 | "diagnostics.globals": ["gpm"], 22 | "diagnostics.groupFileStatus": { 23 | "await": "Any" 24 | }, 25 | "diagnostics.groupSeverity": { 26 | "await": "Error" 27 | }, 28 | "workspace.checkThirdParty": false, 29 | "completion": { 30 | "requireSeparator": "/" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Pika Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gLua Package Manager 2 | 3 | ⚠ **ATTENTION THE PROJECT IS STILL IN DEVELOPMENT, EXPECT A STABLE RELEASE. UNSTABLE OPERATION, BUGS AND UNPREDICTABLE EXECUTION OF FUNCTIONS ARE POSSIBLE.** 4 | 5 | ![under-construction](https://user-images.githubusercontent.com/2846578/50296605-d76e5780-0448-11e9-9e16-39917d203b98.gif) 6 | 7 | ## Description 8 | Garry's Mod Lua project aimed at expanding the game's functionality and improving performance. 9 | 10 | The package manager will allow asynchronous loading of Lua packages, introduces support for many really cool third-party APIs and significantly expands functionality for the developer. 11 | 12 | After many years of hard work I can finally say that we have created a trully powerful runtime for developers ;> 13 | 14 | ## Implemented 15 | * Built-in OOP (Object Oriented Programming) support 16 | * Built-in async support (python like futures) 17 | * Big Integer class 18 | * LZW compression 19 | * Deflate compression 20 | * HMAC hashing 21 | * PBKDF2 hashing 22 | * Separate lua environment 23 | * Extremely fast functions 24 | * Fast binary read/write functions 25 | * POSIX like file system 26 | * Built-in support for a huge number of community binary modules 27 | * Fast separate hook library 28 | * Server, Client, Menu realms support 29 | * Multiple native lua 5.1 - 5.4 functions and features 30 | * Highly extended base libraries 31 | * Development-ready functions for working with bits/bytes 32 | * Fully integrated Lua Language Server ( LuaLS ) 33 | * Fully implemented Steam Identifier ( SteamID ) class ( supports all account types and all existing transformations ) 34 | 35 | ## Planned 36 | * Downloading and building package dependency chains 37 | * Complete reworking of Garry's Mod codebase 38 | * Implement file multi-transport API 39 | * Built-in support of holy-lib 40 | * Addon ( GMA file ) class 41 | * and many, many more... 42 | 43 | ## Bugs and Suggestions 44 | If you have any ideas or found a bug or error, please report it to us, we will try to fix it or explain what the problem is. 45 | 46 | ## Documentation 47 | Documentation is in development, release time is not available. 48 | -------------------------------------------------------------------------------- /classes.md: -------------------------------------------------------------------------------- 1 | ## How to use this class system: 2 | 3 | ### 1. Define base class (basically a metatable for our cars) 4 | 5 | ```lua 6 | ---@class gpm.std.Car : gpm.std.Object <-- do not forget to inherit from gpm.std.Object 7 | ---@field __class gpm.std.CarClass 8 | local Car = std.class.base("Car") 9 | 10 | ---@alias Car gpm.std.Car <-- alias Car, so it is easier for us to reference it in params and etc. 11 | 12 | ---@protected <-- do not forget to add protected so __init won't be shown 13 | function Car:__init() 14 | self.speed = 0 15 | self.color = Color(255, 255, 255) 16 | end 17 | ``` 18 | 19 | ### 2. Define a class, which will be used to create a Car 20 | 21 | ```lua 22 | ---@class gpm.std.CarClass : gpm.std.Car <-- do not forget to inherit from base 23 | ---@field __base gpm.std.Car 24 | ---@overload fun(): gpm.std.Car 25 | local CarClass = std.class.create( Car ) 26 | std.Car = CarClass 27 | ``` 28 | 29 | ### 3. Create a new car 30 | 31 | ```lua 32 | local car = std.Car() 33 | print( car.__name ) -- Car 34 | print( std.Car.__name == car.__name ) -- true 35 | print( car.speed ) -- 0 36 | ``` 37 | 38 | ### 4. Inherit from the Car class 39 | 40 | ```lua 41 | ---@class gpm.std.Truck : gpm.std.Car 42 | ---@field __class gpm.std.TruckClass 43 | ---@field __parent gpm.std.Car <-- now we need to define the parent, so LuaLS can know how to access our parent 44 | local Truck = std.class.base( "Truck", false, std.Car ) 45 | 46 | ---@alias Truck gpm.std.Truck 47 | 48 | ---@class gpm.std.TruckClass : gpm.std.Truck 49 | ---@field __base gpm.std.Truck 50 | ---@field __parent gpm.std.CarClass 51 | ---@overload fun(): gpm.std.Truck 52 | local TruckClass = std.class.create( Truck ) 53 | std.Truck = TruckClass 54 | ``` 55 | 56 | ## Class Template 57 | ```lua 58 | 59 | ---@class gpm.std.Car : gpm.std.Object 60 | ---@field __class gpm.std.CarClass 61 | local Car = std.class.base( "Car" ) 62 | 63 | ---@alias Car gpm.std.Car 64 | 65 | ---@protected 66 | function Car:__init() 67 | self.speed = 0 68 | self.color = Color( 255, 255, 255 ) 69 | end 70 | 71 | ---@class gpm.std.CarClass : gpm.std.Car 72 | ---@field __base gpm.std.Car 73 | ---@overload fun(): gpm.std.Car 74 | local CarClass = std.class.create( Car ) 75 | std.Car = CarClass 76 | 77 | ---@class gpm.std.Truck : gpm.std.Car 78 | ---@field __class gpm.std.TruckClass 79 | ---@field __parent gpm.std.Car 80 | local Truck = std.class.base( "Truck", false, std.Car ) 81 | 82 | ---@alias Truck gpm.std.Truck 83 | 84 | ---@class gpm.std.TruckClass : gpm.std.Truck 85 | ---@field __base gpm.std.Truck 86 | ---@field __parent gpm.std.CarClass 87 | ---@overload fun(): Truck 88 | local TruckClass = std.class.create( Truck ) 89 | std.Truck = TruckClass 90 | 91 | ``` 92 | -------------------------------------------------------------------------------- /lua/autorun/!!!!gpm.lua: -------------------------------------------------------------------------------- 1 | if gpm == nil then 2 | if SERVER then 3 | AddCSLuaFile( "gpm/init.lua" ) 4 | end 5 | 6 | include( "gpm/init.lua" ) 7 | end 8 | -------------------------------------------------------------------------------- /lua/gpm/.old/cli.yue: -------------------------------------------------------------------------------- 1 | _G = _G 2 | import gpm from _G 3 | import environment, Logger from gpm 4 | import concommand from environment 5 | 6 | -- just in case :> 7 | unless concommand 8 | return 9 | 10 | import lower, StartsWith from environment.string 11 | import concat, remove from environment.table 12 | import pairs from environment 13 | import Add from concommand 14 | 15 | commands = { 16 | install: { 17 | help: "Install a package" 18 | call: ( args ) -> 19 | hint: ( args ) -> 20 | }, 21 | uninstall: { 22 | help: "Remove a package" 23 | call: ( args ) -> 24 | hint: ( args ) -> 25 | }, 26 | reload: { 27 | help: "Reload a package" 28 | call: ( args ) -> 29 | hint: ( args ) -> 30 | } 31 | run: { 32 | help: "Run arbitrary package scripts" 33 | call: ( args ) -> 34 | hint: ( args ) -> 35 | } 36 | update: { 37 | help: "Updates package list from repositories" 38 | call: ( args ) -> 39 | -- loader.GetVersions! 40 | 41 | hint: ( args ) -> 42 | }, 43 | upgrade: { 44 | help: "WIP" 45 | call: ( args ) -> 46 | hint: ( args ) -> 47 | }, 48 | purge: { 49 | help: "WIP" 50 | call: ( args ) -> 51 | hint: ( args ) -> 52 | }, 53 | pull: { 54 | help: "WIP" 55 | call: ( args ) -> 56 | hint: ( args ) -> 57 | }, 58 | list: { 59 | help: "Lists installed packages" 60 | call: ( args ) -> 61 | lines, count = {}, 0 62 | for name, versions in pairs( gpm.Packages ) 63 | buffer, length = {}, 0 64 | for version in pairs( versions ) 65 | length += 1 66 | buffer[ length ] = version\__tostring! 67 | 68 | count += 1 69 | lines[ count ] = count .. ". " .. name .. ": " .. concat( buffer, ", ", 1, length ) 70 | 71 | count += 1 72 | lines[ count ] = "Total: " .. count 73 | 74 | Logger\Info( "Package list:\n" .. concat( lines, "\n", 1, count ) ) 75 | return nil 76 | }, 77 | info: { 78 | help: "Shows information about the package manager" 79 | call: ( args ) -> 80 | hint: ( args ) -> 81 | }, 82 | search: { 83 | help: "Search for packages in repositories" 84 | call: ( args ) -> 85 | hint: ( args ) -> 86 | } 87 | } 88 | 89 | list = {} 90 | for name in pairs( commands ) 91 | list[] = "gpm " .. name 92 | 93 | do 94 | 95 | helpList = {} 96 | 97 | commands.help = { 98 | help: "Shows this help" 99 | call: ( _, args ) -> 100 | cmd = args[ 1 ] 101 | if cmd 102 | cmd = lower( cmd ) 103 | 104 | command = commands[ cmd ] 105 | if command 106 | :help = command 107 | if help 108 | Logger\Info( "help (%s): %s.", cmd, help ) 109 | return nil 110 | else 111 | cmd = "none" 112 | 113 | Logger\Warn( "help (%s): No help found.", cmd ) 114 | return nil 115 | hint: ( args ) -> 116 | str = args[ 1 ] 117 | unless str 118 | return helpList 119 | 120 | str = "gpm help " .. lower( str ) 121 | 122 | suggestions, length = {}, 0 123 | for name in *helpList 124 | if StartsWith( name, str ) 125 | length += 1 126 | suggestions[ length ] = name 127 | 128 | if length == 0 129 | return nil 130 | 131 | return suggestions 132 | } 133 | 134 | for name in pairs( commands ) 135 | helpList[] = "gpm help " .. name 136 | 137 | Add( "gpm", ( ply, _, args ) -> 138 | local command 139 | if #args ~= 0 140 | command = commands[ lower( remove( args, 1 ) ) ] 141 | 142 | if command 143 | command.call( ply, args ) 144 | 145 | return nil, 146 | ( _, __, args ) -> 147 | str = args[ 1 ] 148 | unless str 149 | return list 150 | 151 | cmd = lower( remove( args, 1 ) ) 152 | str = "gpm " .. cmd 153 | 154 | suggestions, length = {}, 0 155 | for name in *list 156 | if name == str 157 | suggestions = nil 158 | break 159 | 160 | elseif StartsWith( name, str ) 161 | length += 1 162 | suggestions[ length ] = name 163 | 164 | if suggestions and length ~= 0 165 | return suggestions 166 | 167 | command = commands[ cmd ] 168 | if command 169 | func = command.hint 170 | if func 171 | return func( args ) 172 | 173 | return nil, 174 | gpm.PREFIX ) 175 | -------------------------------------------------------------------------------- /lua/gpm/.old/init_old.yue: -------------------------------------------------------------------------------- 1 | -- -- Lua Transport 2 | -- include( "gpm/transport.lua" ) 3 | 4 | -- -- Plugins 5 | -- for fileName in *Find( "gpm/plugins/*.lua", "LUA" ) 6 | -- include( "gpm/plugins/" .. fileName ) 7 | 8 | -- -- Package Manager 9 | -- include( "gpm/repositories.lua" ) 10 | -- include( "gpm/loader.lua" ) 11 | 12 | -- if SERVER 13 | -- include( "gpm/cli.lua" ) 14 | 15 | -- -- Code Sources 16 | -- for fileName in *Find( "gpm/sources/*.lua", "LUA" ) 17 | -- include( "gpm/sources/" .. fileName ) 18 | 19 | -- -- our little sandbox ( TODO: remove on release ) 20 | -- if SERVER 21 | -- include( "gpm/test.lua" ) 22 | 23 | -- environment.futures.run( gpm.loader.Startup! ) 24 | -------------------------------------------------------------------------------- /lua/gpm/.old/transport.yue: -------------------------------------------------------------------------------- 1 | _G = _G 2 | import gpm from _G 3 | import environment, Logger from gpm 4 | 5 | if _G.SERVER 6 | 7 | transport = rawget( gpm, "Transport" ) 8 | unless istable( transport ) 9 | transport = {} 10 | rawset( gpm, "Transport", transport ) 11 | 12 | transport.legacy = _G.AddCSLuaFile 13 | 14 | transport.net = ( filePath ) -> 15 | 16 | return nil 17 | 18 | selected = transport[ _G.CreateConVar( "gpm_lua_transport", "legacy", _G.FCVAR_ARCHIVE, "Selected Lua transport" )\GetString! ] or transport.legacy 19 | 20 | _G.cvars.AddChangeCallback( "gpm_lua_transport", ( _, __, str ) -> 21 | selected = transport[ str ] or transport.legacy 22 | gpm.PREFIX .. "::Lua Transport:" ) 23 | 24 | gpm.SendFile = ( filePath ) -> 25 | Logger\Debug( "Sending file '".. filePath .. "' to client..." ) 26 | return selected( filePath ) 27 | else 28 | gpm.SendFile = environment.debug.fempty 29 | -------------------------------------------------------------------------------- /lua/gpm/detour.lua: -------------------------------------------------------------------------------- 1 | ---@class gpm 2 | local gpm = _G.gpm 3 | 4 | if gpm.detour ~= nil then return end 5 | 6 | -- https://github.com/unknown-gd/safety-lite/blob/main/src/detour.lua 7 | local functions = {} 8 | 9 | --- [SHARED AND MENU] 10 | --- 11 | --- The detour library. 12 | --- 13 | ---@class gpm.detour 14 | local detour = gpm.detour or {} 15 | gpm.detour = detour 16 | 17 | --- [SHARED AND MENU] 18 | --- 19 | --- Returns a function that calls the `new_fn` instead of the `old_fn`. 20 | ---@param in_fn function The original function. 21 | ---@param new_fn fun(hook: function, ...: any): ... Function to replace. 22 | ---@return function hooked Hooked function that calls `new_fn` instead of `old_fn`. 23 | function detour.attach( in_fn, new_fn ) 24 | local old_fn = functions[ in_fn ] 25 | if old_fn == nil then 26 | old_fn = in_fn 27 | end 28 | 29 | local function fn( ... ) 30 | return new_fn( old_fn, ... ) 31 | end 32 | 33 | functions[ fn ] = old_fn 34 | return fn 35 | end 36 | 37 | --- [SHARED AND MENU] 38 | --- 39 | --- Returns the original function that the function given hooked. 40 | ---@param fn function Hooked function. 41 | ---@return function original Original function to overwrite with. 42 | ---@return boolean @True if the hook was detached. 43 | function detour.detach( fn ) 44 | local old_fn = functions[ fn ] 45 | if old_fn == nil then 46 | return fn, false 47 | else 48 | functions[ fn ] = nil 49 | return old_fn, true 50 | end 51 | end 52 | 53 | --- [SHARED AND MENU] 54 | --- 55 | --- Returns the unhooked function if value is hooked, else returns ``fn``. 56 | ---@param fn function Function to check. Can actually be any type though. 57 | ---@return function original Unhooked value or function. 58 | ---@return boolean success Was the value hooked? 59 | function detour.shadow( fn ) 60 | local old_fn = functions[ fn ] 61 | if old_fn == nil then 62 | return fn, false 63 | else 64 | return old_fn, true 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lua/gpm/package/init.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm 4 | local gpm = _G.gpm 5 | 6 | local std = gpm.std 7 | local class = std.class 8 | local Version = std.Version 9 | 10 | ---@class gpm.Package: gpm.std.Object 11 | ---@field __class gpm.PackageClass 12 | ---@field name string 13 | ---@field prefix string 14 | ---@field version Version 15 | ---@field commands table 16 | local Package = class.base( "Package" ) 17 | 18 | local cache = {} 19 | 20 | --[[ 21 | 22 | Package: { 23 | name: string 24 | prefix: string 25 | version: Version 26 | environment: table ( _ENV ) 27 | } 28 | 29 | https://stackoverflow.com/questions/12021461/lua-setfenv-vs-env 30 | 31 | ]] 32 | 33 | ---@param name string 34 | ---@param version string | Version 35 | ---@protected 36 | function Package:__init( name, version ) 37 | local prefix = name .. "@" .. version 38 | self.prefix = prefix 39 | 40 | self.version = Version( version ) 41 | 42 | package.console_variables = {} 43 | package.console_commands = {} 44 | cache[ prefix ] = self 45 | end 46 | 47 | ---@protected 48 | function Package:__new( name, version ) 49 | return cache[ name .. "@" .. version ] 50 | end 51 | 52 | ---@class gpm.PackageClass: gpm.Package 53 | ---@field __base gpm.Package 54 | ---@overload fun( name: string, version: string | Version ): Package 55 | local PackageClass = class.create( Package ) 56 | gpm.Package = PackageClass 57 | 58 | local debug_getmetatable = std.debug.getmetatable 59 | local debug_getfmain = std.debug.getfmain 60 | local getfenv = std.getfenv 61 | local raw_get = std.raw.get 62 | 63 | function PackageClass.getName() 64 | local fn = debug_getfmain() 65 | if fn ~= nil then 66 | local fenv = getfenv( fn ) 67 | if fenv ~= nil then 68 | local metatable = debug_getmetatable( fenv ) 69 | if metatable ~= nil then 70 | return raw_get( metatable, "__package" ) 71 | end 72 | end 73 | end 74 | 75 | return "base" 76 | end 77 | 78 | function PackageClass.getMountName() 79 | local fn = debug_getfmain() 80 | if fn ~= nil then 81 | local fenv = getfenv( fn ) 82 | if fenv ~= nil then 83 | local metatable = debug_getmetatable( fenv ) 84 | if metatable ~= nil then 85 | return raw_get( metatable, "__mount" ) 86 | end 87 | end 88 | end 89 | 90 | return "GAME" 91 | end 92 | -------------------------------------------------------------------------------- /lua/gpm/package/sources/filesystem.yue: -------------------------------------------------------------------------------- 1 | import gpm from _G 2 | import environment from gpm 3 | import await, SourceError from environment 4 | import read from environment.Package 5 | 6 | environment.class( "FileSource", { 7 | FetchInfo: environment.async ( url ) => 8 | package = await read( url ) 9 | if package 10 | return { :url, :package } 11 | 12 | error SourceError "Failed to read or find package file for " .. url.href 13 | return nil 14 | }, nil, gpm.loader.Source )( "file" ) 15 | -------------------------------------------------------------------------------- /lua/gpm/package/sources/github.yue: -------------------------------------------------------------------------------- 1 | -- Seconds iteration of github source handler 2 | 3 | _G = _G 4 | import pairs, tostring, gpm from _G -- functions 5 | import environment, Logger from gpm -- environment 6 | 7 | import URL, Package, SourceError from environment -- classes 8 | import async, await from environment -- functions 9 | 10 | -- libraries 11 | import Read, IsFile, IterateZipFiles, MountGMA, Set, Write from environment.file -- file. 12 | import match, lower, sub, ByteSplit, IndexOf, StartsWith from environment.string -- string. 13 | import getRepository, getTree, getBlob, fetchZip from environment.github -- github. 14 | import CRC, JSONToTable, TableToJSON, ByteStream from environment.util -- util. 15 | import getFile, getDirectory, join from environment.path -- path. 16 | import GMA from environment.addon -- addon. 17 | 18 | -- constants 19 | CACHE_DIR = "/data/gpm/cache/github/" 20 | 21 | -- url syntax: 22 | -- github:user/repo[/branch] 23 | -- or 24 | -- github://user/repo[/branch] 25 | class GithubSource extends gpm.loader.Source 26 | GetDefaultBranch = ( user, repository ) -> 27 | -- if we have local default branch, just use it and do not fetch it from github 28 | -- probably it wont be changed, and there is no need to recheck it every time 29 | filePath = CACHE_DIR .. user .. "/" .. repository .. "/default_branch.txt" 30 | 31 | branch = Read( filePath, nil, nil, nil, true ) 32 | if branch 33 | return branch 34 | 35 | Logger\Debug( "Fetching information for Github repository '%s/%s'...", user, repository ) 36 | 37 | branch = ( await getRepository( user, repository ) ).default_branch 38 | unless branch 39 | error SourceError "Failed to fetch default branch for '#{user}/#{repository}' from Github API." 40 | 41 | -- save the default branch to the cache 42 | Write( filePath, branch, nil, nil, true ) 43 | 44 | return branch 45 | 46 | -- pattern: priority 47 | PACKAGE_PATH_PRIORITIES = { 48 | "^package%..+$": 10 49 | "package.yue": 11 50 | "package.moon": 12 51 | "package.lua": 15 52 | "package%..+": 20 53 | } 54 | 55 | FindPackageInfo = ( user, repository, tree_sha ) -> 56 | filePath = CACHE_DIR .. user .. "/" .. repository .. "/" .. tree_sha .. "/package.entry.json" 57 | 58 | entry = JSONToTable( Read( filePath, nil, nil, nil, true ) or "", true, true ) 59 | if entry 60 | return entry 61 | 62 | Logger\Debug( "Fetching file tree from Github repository '%s/%s/%s'...", user, repository, tree_sha ) 63 | 64 | res = await getTree( user, repository, tree_sha, true ) 65 | 66 | entries = [] 67 | for entry in *res.tree 68 | if entry.type == "blob" and match( entry.path, "package%..+$" ) 69 | entries[] = entry 70 | 71 | packageEntry = nil 72 | 73 | if #entries == 1 74 | packageEntry = entries[1] 75 | 76 | else 77 | -- welp, we have multiple package.lua files, lets try to find the correct one 78 | priority = math.huge -- 0 is the highest priority 79 | for entry in *entries 80 | for pattern, p in pairs PACKAGE_PATH_PRIORITIES 81 | if match( entry.path, pattern ) 82 | if pattern == entry.path 83 | p = p - 10 -- paths that match the exact pattern are more important 84 | 85 | if p < priority 86 | priority = p 87 | packageEntry = entry 88 | 89 | -- TODO: check if we have duplicates (i.e packages/a/package.lua and packages/b/package.lua) 90 | 91 | if packageEntry 92 | Write( filePath, TableToJSON( packageEntry, false ), nil, nil, true ) 93 | 94 | return packageEntry 95 | 96 | FetchPackageFile = ( user, repository, branch, entry ) -> 97 | filePath = CACHE_DIR .. user .. "/" .. repository .. "/" .. branch .. "/package.txt" 98 | 99 | package = Read( filePath, nil, nil, nil, true ) 100 | if package 101 | return package 102 | 103 | Logger\Debug( "Fetching package file from Github repository '%s/%s/%s'... (sha = '%s')", user, repository, branch, entry.sha ) 104 | 105 | res = await getBlob( user, repository, entry.sha ) 106 | Write( filePath, res.content, nil, nil, true ) 107 | return res.content 108 | 109 | DownloadRepository = ( user, repository, branch ) -> 110 | filePath = CACHE_DIR .. user .. "/" .. repository .. "/" .. branch .. "/files.zip.dat" 111 | 112 | data = Read( filePath, nil, nil, nil, true ) 113 | if data 114 | return ByteStream( data ) 115 | 116 | Logger\Debug( "Downloading repository '%s/%s/%s'...", user, repository, branch ) 117 | data = await fetchZip( user, repository, branch ) 118 | 119 | Write( filePath, data, nil, nil, true ) 120 | 121 | return ByteStream( data ) 122 | 123 | FetchInfo: async ( url ) => 124 | -- Parse user, repo and branch from the given url 125 | segments = ByteSplit( url.pathname, 0x2F --[[ / ]] ) 126 | 127 | :hostname = url 128 | if hostname 129 | insert( segments, 1, hostname ) 130 | 131 | user = lower( segments[ 1 ] ) 132 | repository = lower( segments[ 2 ] ) 133 | unless user and user != "" and repository and repository != "" 134 | error SourceError "Invalid url '#{url}' (missing user or repository, got '#{user}' and '#{repository}')." 135 | 136 | branch = segments[ 3 ] or GetDefaultBranch( user, repository ) 137 | 138 | packageEntry = FindPackageInfo( user, repository, branch ) 139 | unless packageEntry 140 | error SourceError "Failed to find package file in #{user}/#{repository} (#{branch})." 141 | 142 | -- Check if repository already was installed locally 143 | pkg = await Package.read( url ) 144 | if pkg 145 | return { 146 | package: pkg 147 | url: url 148 | metadata: { 149 | :user 150 | :repository 151 | :branch 152 | :packageEntry 153 | cached: true 154 | } 155 | } 156 | 157 | packageURL = URL( getFile( packageEntry.path ), @WorkingDirectory( url ) ) 158 | 159 | packageContent = FetchPackageFile( user, repository, branch, packageEntry ) 160 | unless packageContent 161 | error SourceError "Failed to fetch package file from #{url}." 162 | 163 | -- preventing overwriting existing package file 164 | unless IsFile( packageURL.pathname ) 165 | Set( packageURL.pathname, packageContent ) 166 | 167 | pkg = await Package.read( packageURL ) 168 | unless pkg 169 | error SourceError "Failed to read package file from #{packageURL}. (url = #{url})" 170 | 171 | return { 172 | package: pkg 173 | url: url 174 | metadata: { 175 | :user 176 | :repository 177 | :branch 178 | :packageEntry 179 | } 180 | } 181 | 182 | mountedRepositories = {} 183 | 184 | Install: async ( info, workdir ) => 185 | unless workdir 186 | workdir = @WorkingDirectory( info.url ).pathname 187 | 188 | if mountedRepositories[ workdir ] 189 | return nil 190 | 191 | root = getDirectory( info.metadata.packageEntry.path ) 192 | rootLength = #root + 1 -- +1 to remove the trailing slash 193 | 194 | handle = DownloadRepository( info.metadata.user, info.metadata.repository, info.metadata.branch ) 195 | 196 | -- just in case if Install was called multiple times 197 | if mountedRepositories[ workdir ] 198 | return nil 199 | 200 | Logger\Debug( "Installing package '%s@%s' from Github repository '%s/%s/%s'...", info.package.name, info.package.version, info.metadata.user, info.metadata.repository, info.metadata.branch ) 201 | 202 | gmaPath = CACHE_DIR .. info.metadata.user .. "/" .. info.metadata.repository .. "/" .. info.metadata.branch .. "/files-" .. CRC( workdir ) .. ".gma" 203 | if Read( gmaPath, nil, nil, nil, true ) 204 | unless MountGMA( gmaPath ) 205 | error SourceError "Failed to mount GMA file '#{gmaPath}'." 206 | 207 | return nil 208 | 209 | gma = GMA! 210 | gma\SetTitle( info.url.href ) 211 | 212 | for entry, err in IterateZipFiles( handle, false ) -- entry: { path, content } 213 | if err 214 | Logger\Debug( "Skipping file from zipball '%s/%s/%s' with path '%s' and reason '%s'", info.metadata.user, info.metadata.repository, info.metadata.branch, entry.path, err ) 215 | continue 216 | 217 | -- first remove first directory from the path (appended by github) 218 | entryPath = sub( entry.path, IndexOf( entry.path, "/" ) + 1 ) 219 | 220 | -- then remove the root directory 221 | unless StartsWith( entryPath, root ) 222 | continue 223 | 224 | entryPath = sub( entryPath, rootLength ) -- TODO: check if string is even valid 225 | if entryPath == "" 226 | continue 227 | 228 | -- add working directory 229 | entryPath = join( workdir, entryPath ) 230 | 231 | gma\SetFile( entryPath, entry.content ) 232 | 233 | await gma\AsyncWrite( gmaPath, true, true ) 234 | 235 | unless MountGMA( gmaPath ) 236 | error SourceError "Failed to mount GMA file '#{gmaPath}'." 237 | 238 | mountedRepositories[ workdir ] = true 239 | return nil 240 | 241 | GithubSource( "github" ) 242 | -------------------------------------------------------------------------------- /lua/gpm/std/addon.lua: -------------------------------------------------------------------------------- 1 | ---@class gpm.std 2 | local std = _G.gpm.std 3 | 4 | ---@class gpm.std.Addon: gpm.std.Object 5 | ---@field __class gpm.std.AddonClass 6 | local Addon = std.class.base( "Addon" ) 7 | 8 | ---@protected 9 | function Addon:__init() 10 | end 11 | 12 | ---@class gpm.std.AddonClass: gpm.std.Addon 13 | ---@field __base gpm.std.Addon 14 | ---@overload fun(): gpm.std.Addon 15 | local AddonClass = std.class.create(Addon) 16 | std.Addon = AddonClass 17 | 18 | ---@alias Addon gpm.std.Addon 19 | -------------------------------------------------------------------------------- /lua/gpm/std/audio.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | https://wiki.facepunch.com/gmod/Global.SoundDuration 4 | https://wiki.facepunch.com/gmod/Global.CreateSound 5 | 6 | https://wiki.facepunch.com/gmod/Global.SentenceDuration 7 | https://wiki.facepunch.com/gmod/Global.EmitSentence 8 | https://wiki.facepunch.com/gmod/Global.EmitSound 9 | 10 | https://wiki.facepunch.com/gmod/Global.Sound 11 | https://wiki.facepunch.com/gmod/util.PrecacheSound 12 | 13 | https://wiki.facepunch.com/gmod/CSoundPatch 14 | 15 | https://wiki.facepunch.com/gmod/sound 16 | 17 | 18 | https://wiki.facepunch.com/gmod/surface.PlaySound 19 | 20 | ]] 21 | 22 | -- TODO: bass 23 | -------------------------------------------------------------------------------- /lua/gpm/std/bit.lua: -------------------------------------------------------------------------------- 1 | -- Based on https://gist.github.com/x4fx77x4f/5b97e803825d3dc16f6e9c5227ec6a04 2 | local _G = _G 3 | 4 | ---@class gpm.std 5 | local std = _G.gpm.std 6 | local math = std.math 7 | 8 | --- [SHARED AND MENU] 9 | --- 10 | --- The bit library. 11 | --- 12 | ---@class gpm.std.bit 13 | local bit = std.bit or {} 14 | std.bit = bit 15 | 16 | if _G.bit ~= nil then 17 | 18 | local glua_bit = _G.bit 19 | 20 | bit.band = bit.band or glua_bit.band 21 | bit.bor = bit.bor or glua_bit.bor 22 | 23 | bit.bnot = bit.bnot or glua_bit.bnot 24 | bit.bxor = bit.bxor or glua_bit.bxor 25 | 26 | bit.arshift = bit.arshift or glua_bit.arshift 27 | bit.lshift = bit.lshift or glua_bit.lshift 28 | bit.rshift = bit.rshift or glua_bit.rshift 29 | 30 | bit.rol = bit.rol or glua_bit.rol 31 | bit.ror = bit.ror or glua_bit.ror 32 | 33 | bit.bswap = bit.bswap or glua_bit.bswap 34 | 35 | bit.tobit = bit.tobit or glua_bit.tobit 36 | bit.tohex = bit.tohex or glua_bit.tohex 37 | 38 | end 39 | 40 | if bit.tohex == nil then 41 | 42 | local string_format = std.string.format 43 | 44 | --- [SHARED AND MENU] 45 | --- 46 | --- Returns the hexadecimal representation of the number with the specified digits. 47 | ---@param value integer The value to be converted. 48 | ---@param length integer? The number of digits. Defaults to 8. 49 | ---@return string str The hexadecimal representation. 50 | ---@diagnostic disable-next-line: duplicate-set-field 51 | function bit.tohex( value, length ) 52 | return string_format( "%0" .. ( length or 8 ) .. "X", value ) 53 | end 54 | 55 | end 56 | 57 | if bit.tobit == nil then 58 | 59 | --- [SHARED AND MENU] 60 | --- 61 | --- Normalizes the specified value and clamps it in the range of a signed 32bit integer. 62 | --- 63 | ---@param value integer The value to be normalized. 64 | ---@return integer result The normalized value. 65 | ---@diagnostic disable-next-line: duplicate-set-field 66 | function bit.tobit( value ) 67 | value = value % 0x100000000 68 | return ( value >= 0x80000000 ) and ( value - 0x100000000 ) or value 69 | end 70 | 71 | end 72 | 73 | local math_floor = math.floor 74 | local bit_tobit = bit.tobit 75 | 76 | if bit.arshift == nil then 77 | 78 | --- [SHARED AND MENU] 79 | --- 80 | --- Returns the arithmetically shifted value. 81 | ---@param value integer The value to be manipulated. 82 | ---@param shift integer Amounts of bits to shift. 83 | ---@return integer result The arithmetically shifted value. 84 | ---@diagnostic disable-next-line: duplicate-set-field 85 | function bit.arshift( value, shift ) 86 | return bit_tobit( math_floor( value / ( 2 ^ shift ) ) * ( value >= 0x80000000 and -1 or 1 ) ) 87 | end 88 | 89 | end 90 | 91 | if bit.lshift == nil then 92 | 93 | --- [SHARED AND MENU] 94 | --- 95 | --- Returns the left shifted value. 96 | ---@param value integer The value to be manipulated. 97 | ---@param shift integer Amounts of bits to shift left by. 98 | ---@return integer result The left shifted value. 99 | ---@diagnostic disable-next-line: duplicate-set-field 100 | function bit.lshift( value, shift ) 101 | if shift > 31 then 102 | return 0 103 | else 104 | return bit_tobit( value * ( 2 ^ shift ) ) 105 | end 106 | end 107 | 108 | end 109 | 110 | if bit.rshift == nil then 111 | 112 | --- [SHARED AND MENU] 113 | --- 114 | --- Returns the right shifted value. 115 | --- 116 | ---@param value integer The value to be manipulated. 117 | ---@param shift integer Amounts of bits to shift right by. 118 | ---@return integer result The right shifted value. 119 | ---@diagnostic disable-next-line: duplicate-set-field 120 | function bit.rshift( value, shift ) 121 | if shift > 31 then 122 | return 0 123 | else 124 | return bit_tobit( math_floor( value / ( 2 ^ shift ) ) ) 125 | end 126 | end 127 | 128 | end 129 | 130 | if bit.bswap == nil then 131 | 132 | --- [SHARED AND MENU] 133 | --- 134 | --- Swaps the byte order of a 32-bit integer. 135 | --- 136 | ---@param value integer The 32-bit integer to be byte-swapped. 137 | ---@return integer result The byte-swapped value. 138 | ---@diagnostic disable-next-line: duplicate-set-field 139 | function bit.bswap( value ) 140 | return bit_tobit( ( ( value % 0x100 ) * 0x1000000 ) + ( ( math_floor( value / 0x100 ) % 0x100 ) * 0x10000 ) + ( ( math_floor( value / 0x10000 ) % 0x100 ) * 0x100 ) + ( math_floor( value / 0x1000000 ) % 0x100 ) ) 141 | end 142 | 143 | end 144 | 145 | if bit.bnot == nil then 146 | 147 | --- [SHARED AND MENU] 148 | --- 149 | --- Returns the bitwise `not` of the value. 150 | --- 151 | ---@param value integer The value to be manipulated. 152 | ---@return integer result The bitwise `not` of the value. 153 | ---@diagnostic disable-next-line: duplicate-set-field 154 | function bit.bnot( value ) 155 | local result = 0 156 | for i = 0, 31, 1 do 157 | if value % 2 == 0 then 158 | result = result + 2 ^ i 159 | end 160 | 161 | value = math_floor( value * 0.5 ) 162 | end 163 | 164 | return bit_tobit( result ) 165 | end 166 | 167 | end 168 | 169 | if bit.band == nil then 170 | 171 | --- [SHARED AND MENU] 172 | --- 173 | --- Performs the bitwise `and for all values specified. 174 | --- 175 | ---@param value integer The value to be manipulated. 176 | ---@param ... integer? Values bit and with. 177 | ---@return integer result The bitwise `and` result between all values. 178 | ---@diagnostic disable-next-line: duplicate-set-field 179 | function bit.band( value, ... ) 180 | local args = { value, ... } 181 | local result = 0xFFFFFFFF 182 | 183 | local bits = {} 184 | for i = 1, select( "#", value, ... ), 1 do 185 | local x = args[ i ] 186 | for j = 1, 32, 1 do 187 | if x % 2 == 0 and bits[ j ] == nil then 188 | bits[ j ] = true 189 | result = result - 2 ^ ( j - 1 ) 190 | end 191 | 192 | x = math_floor( x * 0.5 ) 193 | end 194 | end 195 | 196 | return bit_tobit( result ) 197 | end 198 | 199 | end 200 | 201 | if bit.bor == nil then 202 | 203 | --- [SHARED AND MENU] 204 | --- 205 | --- Returns the bitwise `or` of all values specified. 206 | --- 207 | ---@param value integer The value to be manipulated. 208 | ---@param ... integer? Values bit or with. 209 | ---@return integer result The bitwise `or` result between all values. 210 | ---@diagnostic disable-next-line: duplicate-set-field 211 | function bit.bor( value, ... ) 212 | local args = { value, ... } 213 | local result = 0 214 | 215 | local bits = {} 216 | for i = 1, select( "#", value, ... ), 1 do 217 | local x = args[ i ] 218 | for j = 1, 32, 1 do 219 | if x % 2 ~= 0 and bits[ j ] == nil then 220 | bits[ j ] = true 221 | result = result + 2 ^ ( j - 1 ) 222 | end 223 | 224 | x = math_floor( x * 0.5 ) 225 | end 226 | end 227 | 228 | return bit_tobit( result ) 229 | end 230 | 231 | end 232 | 233 | if bit.bxor == nil then 234 | 235 | --- [SHARED AND MENU] 236 | --- 237 | --- Returns the bitwise `xor` of all values specified. 238 | --- 239 | ---@param value integer The value to be manipulated. 240 | ---@param ... integer? Values bit xor with. 241 | ---@return integer result Result of bitwise `xor` operation. 242 | ---@diagnostic disable-next-line: duplicate-set-field 243 | function bit.bxor( value, ... ) 244 | local args = { value, ... } 245 | 246 | local bits = {} 247 | for i = 1, select( "#", value, ... ), 1 do 248 | local x = args[ i ] 249 | for j = 1, 32, 1 do 250 | if x % 2 ~= 0 then 251 | bits[ i ] = not bits[ i ] 252 | end 253 | 254 | x = math_floor( x * 0.5 ) 255 | end 256 | end 257 | 258 | local output = 0 259 | for i = 1, 32, 1 do 260 | if bits[ i ] == true then 261 | output = output + 2 ^ ( i - 1 ) 262 | end 263 | end 264 | 265 | return bit_tobit( output ) 266 | end 267 | 268 | end 269 | 270 | local bit_lshift, bit_rshift = bit.lshift, bit.rshift 271 | local bit_band, bit_bor = bit.band, bit.bor 272 | 273 | if bit.rol == nil then 274 | 275 | --- [SHARED AND MENU] 276 | --- 277 | --- Returns the left rotated value. 278 | --- 279 | ---@param value integer The value to be manipulated. 280 | ---@param shift integer Amounts of bits to rotate left by. 281 | ---@return integer result The left rotated value. 282 | ---@diagnostic disable-next-line: duplicate-set-field 283 | function bit.rol( value, shift ) 284 | return bit_bor( bit_lshift( value, shift ), bit_rshift( value, 32 - shift ) ) 285 | end 286 | 287 | end 288 | 289 | if bit.ror == nil then 290 | 291 | --- [SHARED AND MENU] 292 | --- 293 | --- Returns the right rotated value. 294 | --- 295 | ---@param value integer The value to be manipulated. 296 | ---@param shift integer Amounts of bits to rotate right by. 297 | ---@return integer result The right rotated value. 298 | ---@diagnostic disable-next-line: duplicate-set-field 299 | function bit.ror( value, shift ) 300 | return bit_bor( bit_rshift( value, shift ), bit_lshift( value, 32 - shift ) ) 301 | end 302 | 303 | end 304 | 305 | if bit.btest == nil then 306 | 307 | --- [SHARED AND MENU] 308 | --- 309 | --- Performs the bitwise `test for all values specified. 310 | --- 311 | ---@param value integer The value to be tested. 312 | ---@param ... integer? The values to be tested. 313 | ---@return boolean result `true` if all values are `true`, `false` otherwise. 314 | function bit.btest( value, ... ) 315 | return bit_band( value, ... ) ~= 0 316 | end 317 | 318 | end 319 | 320 | if bit.extract == nil then 321 | 322 | --- [SHARED AND MENU] 323 | --- 324 | --- Returns the unsigned number formed by the bits field to field + width - 1 from n. Bits are numbered from 0 (least significant) to 31 (most significant). 325 | --- 326 | --- All accessed bits must be in the range [0, 31]. 327 | --- 328 | ---@param value integer The value to be manipulated. 329 | ---@param field integer The starting bit. 330 | ---@param width integer? The number of bits to extract. 331 | ---@return integer result The extracted value. 332 | function bit.extract( value, field, width ) 333 | return bit_band( bit_rshift( value, field ), 2 ^ ( width or 1 ) - 1 ) 334 | end 335 | 336 | end 337 | 338 | if bit.replace == nil then 339 | 340 | local bit_bnot = bit.bnot 341 | 342 | --- [SHARED AND MENU] 343 | --- 344 | --- Replaces the bits field to field + width - 1 with the specified value. 345 | --- 346 | ---@param value integer The value to be manipulated. 347 | ---@param extract integer The value to be extracted. 348 | ---@param field integer The starting bit. 349 | ---@param width integer? The number of bits to extract. 350 | ---@return number result The modified value. 351 | function bit.replace( value, extract, field, width ) 352 | local mask = 2 ^ ( width or 1 ) - 1 353 | return bit_band( value, bit_bnot( bit_lshift( mask, field ) ) ) + bit_lshift( bit_band( extract, mask ), field ) 354 | end 355 | 356 | end 357 | -------------------------------------------------------------------------------- /lua/gpm/std/class.lua: -------------------------------------------------------------------------------- 1 | ---@class gpm.std 2 | local std = _G.gpm.std 3 | 4 | local debug = std.debug 5 | local string = std.string 6 | 7 | local debug_newproxy = debug.newproxy 8 | local setmetatable = std.setmetatable 9 | local raw_get = std.raw.get 10 | 11 | --- [SHARED AND MENU] 12 | --- 13 | --- The class (OOP) library. 14 | --- 15 | ---@class gpm.std.class 16 | local class = {} 17 | std.class = class 18 | 19 | ---@alias gpm.std.Class.__inherited fun( parent: gpm.std.Class, child: gpm.std.Class ) 20 | ---@alias gpm.std.Object.__new fun( cls: gpm.std.Object, ...: any? ): gpm.std.Object 21 | ---@alias gpm.std.Object.__init fun( obj: gpm.std.Object, ...: any? ) 22 | 23 | ---@class gpm.std.Object 24 | ---@field private __type string The name of object type. **READ ONLY** 25 | ---@field __class gpm.std.Class The class of the object. **READ ONLY** 26 | ---@field __parent? gpm.std.Object The parent of the object. **READ ONLY** 27 | ---@field protected __new? gpm.std.Object.__new A function that will be called when a new class is created and allows you to replace the result. 28 | ---@field private __init? gpm.std.Object.__init A function that will be called when creating a new object and should be used as the constructor. 29 | ---@field protected __serialize? fun( obj: gpm.std.Object, writer: gpm.std.crypto.pack.Writer, ...: any? ) A function that will be called when the object is serialized. 30 | ---@field protected __deserialize? fun( obj: gpm.std.Object, reader: gpm.std.crypto.pack.Reader, ...: any? ) A function that will be called when the object is deserialized. 31 | 32 | ---@alias Object gpm.std.Object 33 | 34 | ---@class gpm.std.Class : gpm.std.Object 35 | ---@field __base gpm.std.Object The base of the class. **READ ONLY** 36 | ---@field __parent? gpm.std.Class The parent of the class. **READ ONLY** 37 | ---@field __private boolean If the class is private. **READ ONLY** 38 | ---@field private __inherited? gpm.std.Class.__inherited The function that will be called when the class is inherited. 39 | 40 | ---@alias Class gpm.std.Class 41 | 42 | ---@type table 43 | local templates = {} 44 | 45 | debug.gc.setTableRules( templates, true, false ) 46 | 47 | do 48 | 49 | local debug_getmetavalue = debug.getmetavalue 50 | local debug_getmetatable = debug.getmetatable 51 | local raw_pairs = std.raw.pairs 52 | local string_sub = string.sub 53 | 54 | ---@param obj gpm.std.Object The object to convert to a string. 55 | ---@return string str The string representation of the object. 56 | local function base__tostring( obj ) 57 | return string.format( "%s: %p", debug_getmetavalue( obj, "__type" ) or "unknown", obj ) 58 | end 59 | 60 | ---@type table 61 | local meta_blacklist = { 62 | __private = true, 63 | __class = true, 64 | __base = true, 65 | __type = true, 66 | __init = true 67 | } 68 | 69 | --- [SHARED AND MENU] 70 | --- 71 | --- Creates a new class base ( metatable ). 72 | --- 73 | ---@param name string The name of the class. 74 | ---@param private boolean? If the class is private. 75 | ---@param parent gpm.std.Class? The parent of the class. 76 | ---@return gpm.std.Object base The base of the class. 77 | function class.base( name, private, parent ) 78 | local base 79 | 80 | if private then 81 | local template = debug_newproxy( true ) 82 | base = debug_getmetatable( template ) 83 | 84 | if base == nil then 85 | error( "userdata metatable is missing, lua is corrupted" ) 86 | end 87 | 88 | templates[ base ] = template 89 | 90 | base.__type = name 91 | base.__private = true 92 | base.__tostring = base__tostring 93 | else 94 | base = { 95 | __type = name, 96 | __tostring = base__tostring 97 | } 98 | end 99 | 100 | base.__index = base 101 | 102 | if parent ~= nil then 103 | local parent_base = raw_get( parent, "__base" ) 104 | if parent_base == nil then 105 | error( "parent class has no base", 2 ) 106 | end 107 | 108 | ---@cast parent_base gpm.std.Object 109 | base.__parent = parent_base 110 | setmetatable( base, { __index = parent_base } ) 111 | 112 | -- copy metamethods from parent 113 | for key, value in raw_pairs( parent_base ) do 114 | if string_sub( key, 1, 2 ) == "__" and not ( key == "__index" and value == parent_base ) and not meta_blacklist[ key ] then 115 | base[ key ] = value 116 | end 117 | end 118 | end 119 | 120 | return base 121 | end 122 | 123 | end 124 | 125 | local class__call 126 | do 127 | 128 | --- [SHARED AND MENU] 129 | --- 130 | --- This function is optional and can be used to re-initialize the object. 131 | --- 132 | --- Calls the base initialization function, if it exists, and returns the given object. 133 | --- 134 | ---@param base gpm.std.Object The base object, aka metatable. 135 | ---@param obj gpm.std.Object The object to initialize. 136 | ---@param ... any? Arguments to pass to the constructor. 137 | ---@return gpm.std.Object object The initialized object. 138 | local function class_init( base, obj, ... ) 139 | local init_fn = raw_get( base, "__init" ) 140 | if init_fn ~= nil then 141 | init_fn( obj, ... ) 142 | end 143 | 144 | return obj 145 | end 146 | 147 | class.init = class_init 148 | 149 | --- [SHARED AND MENU] 150 | --- 151 | --- Creates a new class object. 152 | --- 153 | ---@param base gpm.std.Object The base object, aka metatable. 154 | ---@return gpm.std.Object object The new object. 155 | local function class_new( base ) 156 | if raw_get( base, "__private" ) then 157 | ---@diagnostic disable-next-line: return-type-mismatch 158 | return debug_newproxy( templates[ base ] ) 159 | end 160 | 161 | local obj = {} 162 | setmetatable( obj, base ) 163 | return obj 164 | end 165 | 166 | class.new = class_new 167 | 168 | ---@param self gpm.std.Class The class. 169 | ---@return gpm.std.Object object The new object. 170 | function class__call( self, ... ) 171 | ---@type gpm.std.Object | nil 172 | local base = raw_get( self, "__base" ) 173 | if base == nil then 174 | error( "class base is missing, class creation failed.", 2 ) 175 | end 176 | 177 | ---@type gpm.std.Object | nil 178 | local obj 179 | 180 | ---@type gpm.std.Object.__new | nil 181 | local new_fn = raw_get( base, "__new" ) 182 | if new_fn ~= nil then 183 | obj = new_fn( base, ... ) 184 | end 185 | 186 | if obj == nil then 187 | obj = class_new( base ) 188 | end 189 | 190 | return class_init( base, obj, ... ) 191 | end 192 | 193 | end 194 | 195 | ---@param cls gpm.std.Class The class. 196 | ---@return string str The string representation of the class. 197 | local function class__tostring( cls ) 198 | return string.format( "%sClass: %p", raw_get( raw_get( cls, "__base" ), "__type" ), cls ) 199 | end 200 | 201 | local raw_set = std.raw.set 202 | 203 | --- [SHARED AND MENU] 204 | --- 205 | --- Creates a new class from the given base. 206 | --- 207 | ---@param base gpm.std.Object The base object, aka metatable. 208 | ---@return gpm.std.Class | unknown cls The class. 209 | function class.create( base ) 210 | local cls = { 211 | __base = base 212 | } 213 | 214 | local parent_base = raw_get( base, "__parent" ) 215 | if parent_base ~= nil then 216 | ---@cast parent_base gpm.std.Object 217 | cls.__parent = parent_base.__class 218 | 219 | ---@type gpm.std.Class | nil 220 | local parent = raw_get( parent_base, "__class" ) 221 | if parent == nil then 222 | error( "parent class has no class", 2 ) 223 | else 224 | ---@type gpm.std.Class.__inherited | nil 225 | local inherited_fn = raw_get( parent, "__inherited" ) 226 | if inherited_fn ~= nil then 227 | inherited_fn( parent, cls ) 228 | end 229 | end 230 | end 231 | 232 | setmetatable( cls, { 233 | __index = base, 234 | __call = class__call, 235 | __tostring = class__tostring, 236 | __type = raw_get( base, "__type" ) .. "Class" 237 | } ) 238 | 239 | ---@cast cls gpm.std.Object 240 | 241 | raw_set( base, "__class", cls ) 242 | return cls 243 | end 244 | -------------------------------------------------------------------------------- /lua/gpm/std/client.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local gpm = _G.gpm 3 | 4 | ---@class gpm.std 5 | local std = gpm.std 6 | 7 | --- [CLIENT AND MENU] 8 | --- 9 | --- The game's client library. 10 | --- 11 | ---@class gpm.std.client 12 | ---@field entity gpm.std.Player | nil The local player entity object that is associated with the client. 13 | local client = std.client or {} 14 | std.client = client 15 | 16 | client.getServerTime = client.getServerTime or _G.CurTime 17 | 18 | if _G.render ~= nil then 19 | 20 | local glua_render = _G.render 21 | 22 | client.SupportsPixelShadersV1 = ( glua_render.SupportsPixelShaders_1_4 or std.debug.fempty )() or false 23 | client.SupportsPixelShadersV2 = ( glua_render.SupportsPixelShaders_2_0 or std.debug.fempty )() or false 24 | client.SupportedVertexShaders = ( glua_render.SupportsVertexShaders_2_0 or std.debug.fempty )() or false 25 | 26 | local directx_level = ( ( glua_render.GetDXLevel() or std.debug.fempty ) or 80 ) * 0.1 27 | client.SupportedDirectX = directx_level 28 | client.SupportsHDR = directx_level >= 8 29 | 30 | end 31 | 32 | if std.CLIENT then 33 | 34 | do 35 | 36 | local ENTITY = std.debug.findmetatable( "Entity" ) ---@cast ENTITY Entity 37 | local ENTITY_IsValid = ENTITY.IsValid 38 | local LocalPlayer = _G.LocalPlayer 39 | 40 | std.setmetatable( client, { 41 | __index = function( _, key ) 42 | if key == "entity" then 43 | local entity = LocalPlayer() 44 | if entity and ENTITY_IsValid( entity ) then 45 | local player = gpm.transducers[ entity ] 46 | client.entity = player 47 | return player 48 | end 49 | end 50 | 51 | return nil 52 | end 53 | } ) 54 | 55 | end 56 | 57 | -- https://music.youtube.com/watch?v=78PjJ1soEZk (01:00) 58 | client.screenShake = _G.util.ScreenShake 59 | client.getViewEntity = _G.GetViewEntity 60 | client.getEyeVector = _G.EyeVector 61 | client.getEyeAngles = _G.EyeAngles 62 | client.getEyePosition = _G.EyePos 63 | 64 | do 65 | 66 | local voice_chat_state = false 67 | 68 | gpm.engine.hookCatch( "PlayerStartVoice", function( entity ) 69 | if entity ~= client.entity then return end 70 | voice_chat_state = true 71 | end ) 72 | 73 | gpm.engine.hookCatch( "PlayerEndVoice", function( entity ) 74 | if entity ~= client.entity then return end 75 | voice_chat_state = false 76 | end ) 77 | 78 | function client.getVoiceChat() 79 | return voice_chat_state 80 | end 81 | 82 | end 83 | 84 | client.setVoiceChat = _G.permissions.EnableVoiceChat 85 | 86 | end 87 | 88 | if std.MENU then 89 | client.isConnected = _G.IsInGame 90 | client.isConnecting = _G.IsInLoading 91 | else 92 | 93 | --- [CLIENT AND MENU] 94 | --- 95 | --- Checks if the client is connected to the server. 96 | --- 97 | --- NOTE: It always returns `true` on the client. 98 | ---@return boolean bool The `true` if connected, `false` if not. 99 | ---@diagnostic disable-next-line: duplicate-set-field 100 | function client.isConnected() return true end 101 | 102 | --- [CLIENT AND MENU] 103 | --- 104 | --- Checks if the client has connected to the server (looks at the loading screen). 105 | --- 106 | --- NOTE: It always returns `false` on the client. 107 | ---@return boolean bool The `true` if connecting, `false` if not. 108 | ---@diagnostic disable-next-line: duplicate-set-field 109 | function client.isConnecting() return false end 110 | 111 | end 112 | 113 | do 114 | 115 | local console = std.console 116 | local console_Command = console.Command 117 | local console_Variable = console.Variable 118 | 119 | if std.CLIENT then 120 | 121 | --- [CLIENT AND MENU] 122 | --- 123 | --- Disconnects the client from the server. 124 | --- 125 | function client.disconnect() 126 | console_Command.run( "disconnect" ) 127 | end 128 | 129 | else 130 | 131 | --- [CLIENT AND MENU] 132 | --- 133 | --- Disconnects the client from the server. 134 | --- 135 | function client.disconnect() 136 | std.menu.run( "Disconnect" ) 137 | end 138 | 139 | end 140 | 141 | --- [CLIENT AND MENU] 142 | --- 143 | --- Retry connection to last server. 144 | function client.retry() 145 | console_Command.run( "retry" ) 146 | end 147 | 148 | --- [CLIENT AND MENU] 149 | --- 150 | --- Take a screenshot. 151 | ---@param quality integer The quality of the screenshot (0-100), only used if `useTGA` is `false`. 152 | ---@param fileName string The name of the screenshot. 153 | function client.screencap( quality, fileName ) 154 | if std.menu.visible then 155 | return false, "The menu is open, can't take a screenshot." 156 | end 157 | 158 | if fileName == nil then 159 | fileName = std.level.getName() 160 | end 161 | 162 | local files = file.find( "/screenshots/" .. fileName .. "*.jpg" ) 163 | local last_one, count = files[ #files ], nil 164 | if last_one == nil then 165 | count = 0 166 | else 167 | count = ( std.tonumber( std.string.sub( std.file.path.stripExtension( last_one, false ), #fileName + 2 ), 10 ) or 0 ) + 1 168 | end 169 | 170 | fileName = std.string.format( "%s_%04d", fileName, count ) 171 | console_Command.run( "jpeg", fileName, quality or 90 ) 172 | return true, "/screenshots/" .. fileName .. ".jpg" 173 | end 174 | 175 | if std.CLIENT then 176 | client.connect = client.connect or _G.permissions.AskToConnect 177 | else 178 | client.connect = client.connect or _G.JoinServer or _G.permissions.Connect 179 | end 180 | 181 | if client.connect == nil then 182 | 183 | --- [CLIENT AND MENU] 184 | --- 185 | --- Connects client to the specified server. 186 | --- 187 | ---@param address string? The address of the server. ( IP:Port like `127.0.0.1:27015` ) 188 | ---@diagnostic disable-next-line: duplicate-set-field 189 | function client.connect( address ) 190 | console_Command.run( "connect", address ) 191 | end 192 | 193 | end 194 | 195 | --- [CLIENT AND MENU] 196 | --- 197 | --- Checks if close captions are enabled. 198 | --- 199 | ---@return boolean result `true` if close captions are enabled, `false` otherwise. 200 | function game.getCloseCaptions() 201 | console_Variable.getBoolean( "closecaption" ) 202 | end 203 | 204 | --- [CLIENT AND MENU] 205 | --- 206 | --- Enables or disables close captions. 207 | --- 208 | ---@param enable boolean `true` to enable close captions, `false` to disable them. 209 | function game.setCloseCaptions( enable ) 210 | console_Variable.set( "closecaption", enable ) 211 | end 212 | 213 | end 214 | -------------------------------------------------------------------------------- /lua/gpm/std/console.logger.lua: -------------------------------------------------------------------------------- 1 | local std = _G.gpm.std 2 | 3 | ---@class gpm.std.console 4 | local console = std.console 5 | 6 | local scheme = std.Color.scheme 7 | local realm_text, realm_color 8 | 9 | if std.MENU then 10 | realm_text, realm_color = "[Main Menu] ", scheme.realm_menu 11 | elseif std.CLIENT then 12 | realm_text, realm_color = "[ Client ] ", scheme.realm_client 13 | elseif std.SERVER then 14 | realm_text, realm_color = "[ Server ] ", scheme.realm_server 15 | else 16 | realm_text, realm_color = "[ Unknown ] ", color_white 17 | end 18 | 19 | --- [SHARED AND MENU] 20 | --- 21 | --- The logger object. 22 | --- 23 | ---@alias Logger gpm.std.console.Logger 24 | ---@class gpm.std.console.Logger : gpm.std.Object 25 | ---@field __class gpm.std.console.LoggerClass 26 | ---@field title string The logger title. 27 | ---@field title_color gpm.std.Color The logger title color. 28 | ---@field text_color gpm.std.Color The logger text color. 29 | ---@field interpolation boolean The logger interpolation. 30 | ---@field debug_fn fun( gpm.std.console.Logger ): boolean The logger debug function. 31 | local Logger = std.class.base( "console.Logger" ) 32 | 33 | --- [SHARED AND MENU] 34 | --- 35 | --- The logger class. 36 | --- 37 | ---@class gpm.std.console.LoggerClass : gpm.std.console.Logger 38 | ---@field __base gpm.std.console.Logger 39 | ---@overload fun( options: gpm.std.console.Logger.Options? ) : gpm.std.console.Logger 40 | local LoggerClass = std.class.create( Logger ) 41 | console.Logger = LoggerClass 42 | 43 | local function default_debug_fn() 44 | return std.DEVELOPER > 0 45 | end 46 | 47 | local white_color = scheme.white 48 | local primary_text_color = scheme.text_primary 49 | local secondary_text_color = scheme.text_secondary 50 | 51 | ---@protected 52 | function Logger:__init( options ) 53 | if options == nil then 54 | self.title = "unknown" 55 | self.title_color = white_color 56 | self.text_color = primary_text_color 57 | self.interpolation = true 58 | self.debug_fn = default_debug_fn 59 | else 60 | local title = options.title 61 | if title == nil then 62 | self.title = "unknown" 63 | else 64 | self.title = title 65 | end 66 | 67 | local color = options.color 68 | if color == nil then 69 | self.title_color = color_white 70 | else 71 | self.title_color = color 72 | end 73 | 74 | local text_color = options.text_color 75 | if text_color == nil then 76 | self.text_color = primary_text_color 77 | else 78 | self.text_color = text_color 79 | end 80 | 81 | local interpolation = options.interpolation 82 | if interpolation == nil then 83 | self.interpolation = true 84 | else 85 | self.interpolation = interpolation == true 86 | end 87 | 88 | local debug_fn = options.debug 89 | if debug_fn == nil then 90 | self.debug_fn = default_debug_fn 91 | else 92 | self.debug_fn = debug_fn 93 | end 94 | end 95 | end 96 | 97 | local write_log 98 | do 99 | 100 | local console_write = console.write 101 | local tostring = std.tostring 102 | local os_date = std.os.date 103 | 104 | local string = std.string 105 | local string_len, string_sub = string.len, string.sub 106 | local string_format, string_gsub = string.format, string.gsub 107 | 108 | --- [SHARED AND MENU] 109 | --- 110 | --- Logs a message. 111 | ---@param color Color The log level color. 112 | ---@param level string The log level name. 113 | ---@param str string The log message. 114 | ---@param ... any The log message arguments to format/interpolate. 115 | function write_log( object, color, level, str, ... ) 116 | if object.interpolation then 117 | local args = { ... } 118 | for index = 1, select( '#', ... ), 1 do 119 | args[ tostring( index ) ] = tostring( args[ index ] ) 120 | end 121 | 122 | str = string_gsub( str, "{([0-9]+)}", args ) 123 | else 124 | str = string_format( str, ... ) 125 | end 126 | 127 | local title = object.title 128 | 129 | local title_length = string_len( title ) 130 | if title_length > 64 then 131 | title = string_sub( title, 1, 64 ) 132 | title_length = 64 133 | object.title = title 134 | end 135 | 136 | if ( string_len( str ) + title_length ) > 950 then 137 | str = string_sub( str, 1, 950 - title_length ) .. "..." 138 | end 139 | 140 | console_write( secondary_text_color, os_date( "%d-%m-%Y %H:%M:%S " ), realm_color, realm_text, color, level, secondary_text_color, " --> ", object.title_color, title, secondary_text_color, " : ", object.text_color, str .. "\n") 141 | end 142 | 143 | Logger.log = write_log 144 | 145 | end 146 | 147 | do 148 | 149 | local info_color = scheme.info 150 | 151 | --- [SHARED AND MENU] 152 | --- 153 | --- Logs an info message. 154 | function Logger:info( ... ) 155 | return write_log( self, info_color, "INFO ", ... ) 156 | end 157 | 158 | end 159 | 160 | do 161 | 162 | local warn_color = scheme.warn 163 | 164 | --- [SHARED AND MENU] 165 | --- 166 | --- Logs a warning message. 167 | function Logger:warn( ... ) 168 | return write_log( self, warn_color, "WARN ", ... ) 169 | end 170 | 171 | end 172 | 173 | do 174 | 175 | local error_color = scheme.error 176 | 177 | --- [SHARED AND MENU] 178 | --- 179 | --- Logs an error message. 180 | function Logger:error( ... ) 181 | return write_log( self, error_color, "ERROR", ... ) 182 | end 183 | 184 | end 185 | 186 | do 187 | 188 | local debug_color = scheme.debug 189 | 190 | --- [SHARED AND MENU] 191 | --- 192 | --- Logs a debug message. 193 | function Logger:debug( ... ) 194 | if self.debug_fn( self ) then 195 | return write_log( self, debug_color, "DEBUG", ... ) 196 | end 197 | end 198 | 199 | end 200 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.bitpack.lua: -------------------------------------------------------------------------------- 1 | local std = _G.gpm.std 2 | local len = std.len 3 | 4 | local math = std.math 5 | local math_floor = math.floor 6 | 7 | local table_unpack = std.table.unpack 8 | 9 | ---@class gpm.std.crypto 10 | local crypto = std.crypto 11 | 12 | --- [SHARED AND MENU] 13 | --- 14 | --- The bit pack library. 15 | --- 16 | --- Library provides functions for reading and writing bits. 17 | --- 18 | ---@class gpm.std.crypto.bitpack 19 | local bitpack = crypto.bitpack or {} 20 | crypto.bitpack = bitpack 21 | 22 | 23 | --- [SHARED AND MENU] 24 | --- 25 | --- Reads unsigned integer from table of bits (booleans). 26 | --- 27 | ---@param bits boolean[] The table of bits. 28 | ---@param bit_count? integer The size of the table. 29 | ---@param start_position? integer The start position of the table. 30 | ---@return integer value 31 | local function bitpack_readUInt( bits, bit_count, start_position ) 32 | if start_position == nil then 33 | start_position = 1 34 | end 35 | 36 | if bit_count == nil then 37 | bit_count = len( bits ) 38 | elseif bit_count == 0 then 39 | return 0 40 | elseif bit_count < 0 then 41 | error( "bit count cannot be negative", 2 ) 42 | end 43 | 44 | local value = 0 45 | 46 | for i = start_position, bit_count + ( start_position - 1 ), 1 do 47 | if bits[ i ] then 48 | value = value * 2 + 1 49 | else 50 | value = value * 2 51 | end 52 | end 53 | 54 | return value 55 | end 56 | 57 | bitpack.readUInt = bitpack_readUInt 58 | 59 | --- [SHARED AND MENU] 60 | --- 61 | --- Writes unsigned integer as table of bits (booleans). 62 | --- 63 | ---@param value integer The number to explode. 64 | ---@param bit_count? integer The size of the table. 65 | ---@return boolean[] bits The table of bits. 66 | local function bitpack_writeUInt( value, bit_count ) 67 | if bit_count == nil then 68 | bit_count = 8 69 | end 70 | 71 | if value < 0 then 72 | error( "integer is too small to write", 1 ) 73 | elseif value > ( 2 ^ bit_count ) - 1 then 74 | error( "integer is too large to write", 2 ) 75 | end 76 | 77 | local bits = {} 78 | 79 | for j = bit_count, 1, -1 do 80 | if value == 0 then 81 | bits[ j ] = false 82 | else 83 | bits[ j ] = value % 2 == 1 84 | value = math_floor( value * 0.5 ) 85 | end 86 | end 87 | 88 | return bits 89 | end 90 | 91 | bitpack.writeUInt = bitpack_writeUInt 92 | 93 | --- [SHARED AND MENU] 94 | --- 95 | --- Reads signed integer from table of bits (booleans). 96 | --- 97 | ---@param bits any 98 | ---@param bit_count any 99 | ---@param start_position any 100 | ---@return unknown 101 | function bitpack.readInt( bits, bit_count, start_position ) 102 | return bitpack_readUInt( bits, bit_count, start_position ) - ( 2 ^ ( bit_count - 1 ) ) 103 | end 104 | 105 | --- [SHARED AND MENU] 106 | --- 107 | --- Writes signed integer as table of bits (booleans). 108 | --- 109 | ---@param value integer The number to explode. 110 | ---@param bit_count integer The size of the table. 111 | ---@return boolean[] bits The table of bits. 112 | function bitpack.writeInt( value, bit_count ) 113 | return bitpack_writeUInt( value + ( 2 ^ ( bit_count - 1 ) ), bit_count ) 114 | end 115 | 116 | --- [SHARED AND MENU] 117 | --- 118 | --- Writes table of bits (booleans) as bytecodes. 119 | --- 120 | ---@param bits boolean[] The table of bits. 121 | ---@param bit_count? integer The size of the bit table. 122 | ---@param big_endian? boolean `true` for big endian, `false` for little endian. 123 | ---@return integer ... The bytecodes (bytes as integers<0-255>). 124 | function bitpack.pack( bits, bit_count, big_endian ) 125 | if bit_count == nil then 126 | bit_count = len( bits ) 127 | elseif bit_count < 1 then 128 | error( "bit count cannot be less than 1", 1 ) 129 | end 130 | 131 | local byte_count = math.ceil( bit_count * 0.125 ) 132 | local bytes = {} 133 | 134 | if big_endian then 135 | for i = byte_count, 1, -1 do 136 | bytes[ i ] = bitpack_readUInt( bits, 8, i * 8 - 7 ) 137 | end 138 | else 139 | bit_count = bit_count + 1 140 | 141 | for i = 1, byte_count, 1 do 142 | bytes[ i ] = bitpack_readUInt( bits, 8, bit_count - i * 8 ) 143 | end 144 | end 145 | 146 | return table_unpack( bytes, 1, byte_count ) 147 | end 148 | 149 | --- [SHARED AND MENU] 150 | --- 151 | --- Reads bytecodes as table of bits (booleans). 152 | --- 153 | ---@param big_endian? boolean `true` for big endian, `false` for little endian. 154 | ---@param bytes integer[] The bytecodes (bytes as integers<0-255>). 155 | ---@param byte_count? integer The number of bytes. 156 | ---@return boolean[] bits The table of bits (booleans). 157 | ---@return integer bit_count The size of the bit table. 158 | function bitpack.unpack( big_endian, bytes, byte_count ) 159 | if byte_count == nil then 160 | byte_count = len( bytes ) 161 | end 162 | 163 | local bits, bit_count = {}, 0 164 | 165 | for i = big_endian and 1 or byte_count, big_endian and byte_count or 1, big_endian and 1 or -1 do 166 | local byte = bytes[ i ] 167 | 168 | for j = 8, 1, -1 do 169 | if byte == 0 then 170 | bits[ bit_count + j ] = false 171 | else 172 | bits[ bit_count + j ] = byte % 2 == 1 173 | byte = math_floor( byte * 0.5 ) 174 | end 175 | end 176 | 177 | bit_count = bit_count + 8 178 | end 179 | 180 | return bits, bit_count 181 | end 182 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.hmac.lua: -------------------------------------------------------------------------------- 1 | local std = _G.gpm.std 2 | local string = std.string 3 | local string_fromHex = string.fromHex 4 | 5 | ---@class gpm.std.crypto 6 | local crypto = std.crypto 7 | 8 | --- [SHARED AND MENU] 9 | --- 10 | --- A hmac library. 11 | --- 12 | ---@class gpm.std.crypto.hmac 13 | local hmac = crypto.hmac or {} 14 | crypto.hmac = hmac 15 | 16 | local key_normalize 17 | do 18 | 19 | local string_len, string_rep = string.len, string.rep 20 | 21 | --- [SHARED AND MENU] 22 | --- 23 | --- Normalizes the key to the block size of the hash function. 24 | --- 25 | ---@param key string The key to normalize. 26 | ---@param hash_fn function The hash function that must return hex string. 27 | ---@param block_size integer The block size of the hash function. 28 | ---@return string hmac_key The normalized key. 29 | function key_normalize( key, hash_fn, block_size ) 30 | local key_length = string_len( key ) 31 | if key_length > block_size then 32 | return key_normalize( string_fromHex( hash_fn( key ) ), hash_fn, block_size ) 33 | elseif key_length < block_size then 34 | return key .. string_rep( "\0", block_size - key_length ) 35 | else 36 | return key 37 | end 38 | end 39 | 40 | hmac.key = key_normalize 41 | 42 | end 43 | 44 | local key_padding 45 | do 46 | 47 | local string_byte, string_char = string.byte, string.char 48 | local table_unpack = std.table.unpack 49 | local bit_bxor = std.bit.bxor 50 | 51 | --- [SHARED AND MENU] 52 | --- 53 | --- Computes the key padding. 54 | --- 55 | --- This function will NOT check 56 | --- if the padding's entered into 57 | --- it is correct. 58 | --- 59 | --- See https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation for the algorithm. 60 | --- 61 | ---@param key string The normalized by given block size key. 62 | ---@param block_size integer The block size of the hash function. 63 | ---@param outer? string The known outer padding. 64 | ---@param inner? string The known inner padding. 65 | ---@return string outer The outer padding. 66 | ---@return string inner The inner padding. 67 | function key_padding( key, block_size, outer, inner ) 68 | if outer == nil and inner == nil then 69 | local outer_tbl, inner_tbl = {}, {} 70 | 71 | for i = 1, block_size, 1 do 72 | local byte = string_byte( key, i ) 73 | outer_tbl[ i ] = bit_bxor( byte, 0x5c ) 74 | inner_tbl[ i ] = bit_bxor( byte, 0x36 ) 75 | end 76 | 77 | return string_char( table_unpack( outer_tbl, 1, block_size ) ), string_char( table_unpack( inner_tbl, 1, block_size ) ) 78 | elseif outer == nil then 79 | ---@cast inner string 80 | local outer_tbl = {} 81 | 82 | for i = 1, block_size, 1 do 83 | outer_tbl[ i ] = bit_bxor( string_byte( key, i ), 0x5c ) 84 | end 85 | 86 | return string_char( table_unpack( outer_tbl, 1, block_size ) ), inner 87 | elseif inner == nil then 88 | ---@cast outer string 89 | local inner_tbl = {} 90 | 91 | for i = 1, block_size, 1 do 92 | inner_tbl[ i ] = bit_bxor( string_byte( key, i ), 0x36 ) 93 | end 94 | 95 | return outer, string_char( table_unpack( inner_tbl, 1, block_size ) ) 96 | else 97 | ---@cast inner string 98 | ---@cast outer string 99 | return outer, inner 100 | end 101 | end 102 | 103 | hmac.padding = key_padding 104 | 105 | end 106 | 107 | --- [SHARED AND MENU] 108 | --- 109 | --- Computes hmac and returns the result as a hex string. 110 | --- 111 | ---@param hash_fn function The hash function that must return hex string. 112 | ---@param outer string The outer hmac padding. 113 | ---@param inner string The inner hmac padding. 114 | ---@param msg string The message to compute hmac for. 115 | ---@return string hex_str The hex hmac string of the message. 116 | function hmac.computeHex( hash_fn, outer, inner, msg ) 117 | return hash_fn( outer .. string_fromHex( hash_fn( inner .. msg ) ) ) 118 | end 119 | 120 | --- [SHARED AND MENU] 121 | --- 122 | --- Computes hmac and returns the result as a binary string. 123 | --- 124 | ---@param hash_fn function The hash function that must return hex string. 125 | ---@param outer string The outer hmac padding. 126 | ---@param inner string The inner hmac padding. 127 | ---@param msg string The message to compute hmac for. 128 | ---@return string hmac_str The binary hmac string of the message. 129 | function hmac.computeBinary( hash_fn, outer, inner, msg ) 130 | return string_fromHex( hash_fn( outer .. string_fromHex( hash_fn( inner .. msg ) ) ) ) 131 | end 132 | 133 | do 134 | 135 | --- [SHARED AND MENU] 136 | --- 137 | --- Computes hmac and returns the result as a hex string. 138 | --- 139 | ---@param msg string The message to compute hmac for. 140 | ---@param key string The key to use. 141 | ---@param hash_fn function The hash function that must return hex string. 142 | ---@param block_size integer The block size of the hash function. 143 | ---@return string hex_str The hex hmac string of the message. 144 | local function hash( msg, key, hash_fn, block_size ) 145 | local outer, inner = key_padding( key_normalize( key, hash_fn, block_size ), block_size ) 146 | return hash_fn( outer .. string_fromHex( hash_fn( inner .. msg ) ) ) 147 | end 148 | 149 | hmac.hash = hash 150 | 151 | --- [SHARED AND MENU] 152 | --- 153 | --- Returns a function that computes hmac using the given hash function and block length. 154 | --- 155 | ---@param hash_fn function The hash function that must return hex string. 156 | ---@param block_size integer The block size of the hash function. 157 | function hmac.preset( hash_fn, block_size ) 158 | ---@param message string The message to compute hmac for. 159 | ---@param key string The key to use. 160 | ---@return string hex_str The hex hmac string of the message. 161 | return function( message, key ) 162 | return hash( message, key, hash_fn, block_size ) 163 | end 164 | end 165 | 166 | end 167 | 168 | --- [SHARED AND MENU] 169 | --- 170 | --- Computes a hmac using the md5 hash function. 171 | --- 172 | hmac.md5 = hmac.preset( crypto.md5.hash, crypto.md5.block ) 173 | 174 | --- [SHARED AND MENU] 175 | --- 176 | --- Computes a hmac using the sha1 hash function. 177 | --- 178 | hmac.sha1 = hmac.preset( crypto.sha1.hash, crypto.sha1.block ) 179 | 180 | if std.SHARED then 181 | 182 | --- [SHARED] 183 | --- 184 | --- Computes a hmac using the sha256 hash function. 185 | --- 186 | hmac.sha256 = hmac.preset( crypto.sha256.hash, crypto.sha256.block ) 187 | 188 | end 189 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.lzw.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Original library was made by Rochet2 3 | https://github.com/Rochet2/lualzw 4 | 5 | Edit by Unknown Developer 6 | ]] 7 | 8 | local std = _G.gpm.std 9 | local string = std.string 10 | 11 | local string_char, string_byte = string.char, string.byte 12 | local string_sub = string.sub 13 | local string_len = string.len 14 | 15 | local table_concat = std.table.concat 16 | 17 | ---@class gpm.std.crypto 18 | local crypto = std.crypto 19 | 20 | --- [SHARED AND MENU] 21 | --- 22 | --- A lzw library. 23 | --- 24 | ---@class gpm.std.crypto.lzw 25 | local lzw = crypto.lzw or {} 26 | crypto.lzw = lzw 27 | 28 | local basedictcompress = {} 29 | local basedictdecompress = {} 30 | 31 | for i = 0, 255 do 32 | local ic, iic = string_char( i ), string_char( i, 0 ) 33 | basedictcompress[ ic ], basedictdecompress[ iic ] = iic, ic 34 | end 35 | 36 | --- [SHARED AND MENU] 37 | --- 38 | --- Compresses a string using LZW compression. 39 | --- 40 | ---@param raw_data string The string to compress. 41 | ---@param forced? boolean If `true`, compression will ignore the excess length of compressed data relative to uncompressed data. 42 | ---@return string | nil compressed_data The compressed string or `nil` if the compression fails. 43 | ---@return nil | string error_message The error message or `nil` if the compression succeeds. 44 | function lzw.compress( raw_data, forced ) 45 | local data_length = string_len( raw_data ) 46 | if data_length == 0 then 47 | return nil, "compressed string cannot be empty" 48 | elseif data_length == 1 then 49 | return nil, "compressed string cannot be so short" 50 | end 51 | 52 | local parts, part_count = {}, 0 53 | forced = forced == true 54 | 55 | local parts_length 56 | if not forced then 57 | parts_length = 1 58 | end 59 | 60 | local dictionary = {} 61 | local a, b = 0, 1 62 | local word = "" 63 | 64 | for i = 1, data_length, 1 do 65 | local char = string_sub( raw_data, i, i ) 66 | 67 | local new_word = word .. char 68 | if basedictcompress[ new_word ] or dictionary[ new_word ] then 69 | word = new_word 70 | else 71 | local str = basedictcompress[ word ] or dictionary[ word ] 72 | if str == nil then 73 | return nil, "algorithm error, could not fetch word" 74 | end 75 | 76 | if not forced then 77 | parts_length = parts_length + string_len( str ) 78 | 79 | if data_length <= parts_length then 80 | return nil, "compressed data length exceeds uncompressed" 81 | end 82 | end 83 | 84 | part_count = part_count + 1 85 | parts[ part_count ] = str 86 | 87 | word = char 88 | 89 | if a >= 256 then 90 | a, b = 0, b + 1 91 | if b >= 256 then 92 | dictionary, b = {}, 1 93 | end 94 | end 95 | 96 | dictionary[ new_word ] = string_char( a, b ) 97 | a = a + 1 98 | end 99 | end 100 | 101 | local str = basedictcompress[ word ] or dictionary[ word ] 102 | 103 | if not forced then 104 | parts_length = parts_length + string_len( str ) 105 | 106 | if data_length < parts_length then 107 | return nil, "compressed data length exceeds uncompressed" 108 | elseif data_length == parts_length then 109 | return nil, "compressed data length equal to uncompressed" 110 | end 111 | end 112 | 113 | part_count = part_count + 1 114 | parts[ part_count ] = str 115 | 116 | return table_concat( parts, "", 1, part_count ) 117 | end 118 | 119 | --- [SHARED AND MENU] 120 | --- 121 | --- Decompresses a string using LZW compression. 122 | --- 123 | ---@param encoded_data string The string to decompress. 124 | ---@return string | nil decompressed_data The decompressed string or `nil` if the decompression fails. 125 | ---@return nil | string error_message The error message or `nil` if the decompression succeeds. 126 | function lzw.decompress( encoded_data ) 127 | if string_byte( encoded_data, 1, 1 ) == nil then 128 | return nil, "compressed string cannot be empty" 129 | end 130 | 131 | local data_length = string_len( encoded_data ) 132 | if data_length < 2 then 133 | return nil, "compressed string cannot be so short" 134 | end 135 | 136 | local parts, part_count = {}, 0 137 | local last = string_sub( encoded_data, 1, 2 ) 138 | 139 | local dictionary = {} 140 | local a, b = 0, 1 141 | 142 | part_count = part_count + 1 143 | parts[ part_count ] = basedictdecompress[ last ] or dictionary[ last ] 144 | 145 | for i = 3, data_length, 2 do 146 | local code = string_sub( encoded_data, i, i + 1 ) 147 | 148 | local last_string = basedictdecompress[ last ] or dictionary[ last ] 149 | if last_string == nil then 150 | return nil, "could not find last from dictionary. Invalid input?" 151 | end 152 | 153 | last = code 154 | local str 155 | 156 | local new_string = basedictdecompress[ code ] or dictionary[ code ] 157 | if new_string == nil then 158 | str = last_string .. string_sub( last_string, 1, 1 ) 159 | part_count = part_count + 1 160 | parts[ part_count ] = str 161 | else 162 | part_count = part_count + 1 163 | parts[ part_count ] = new_string 164 | str = last_string .. string_sub( new_string, 1, 1 ) 165 | end 166 | 167 | if a >= 256 then 168 | a, b = 0, b + 1 169 | 170 | if b >= 256 then 171 | dictionary, b = {}, 1 172 | end 173 | end 174 | 175 | dictionary[ string_char( a, b ) ] = str 176 | a = a + 1 177 | end 178 | 179 | return table_concat( parts, "", 1, part_count ) 180 | end 181 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.md5.lua: -------------------------------------------------------------------------------- 1 | -- TODO: md5 2 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.pbkdf2.lua: -------------------------------------------------------------------------------- 1 | local std = _G.gpm.std 2 | 3 | ---@class gpm.std.crypto 4 | local crypto = std.crypto 5 | 6 | local hmac = crypto.hmac 7 | local hmac_key = hmac.key 8 | local hmac_padding = hmac.padding 9 | local hmac_computeBinary = hmac.computeBinary 10 | 11 | local pack_writeUInt32 = crypto.pack.writeUInt32 12 | 13 | local string = std.string 14 | local string_toHex = string.toHex 15 | local string_len, string_sub = string.len, string.sub 16 | local string_char, string_byte = string.char, string.byte 17 | 18 | local bit_bxor = std.bit.bxor 19 | 20 | local table_concat, table_unpack = std.table.concat, std.table.unpack 21 | local math_ceil = std.math.ceil 22 | 23 | --- [SHARED AND MENU] 24 | --- 25 | --- Derives a password using the pbkdf2 algorithm. 26 | --- 27 | --- See https://en.wikipedia.org/wiki/PBKDF2 for the algorithm. 28 | --- 29 | ---@param options gpm.std.crypto.pbkdf2.Options 30 | ---@return string pbkdf2_hash The derived password as a hex string. 31 | function crypto.pbkdf2( options ) 32 | local pbkdf2_iterations = options.iterations or 4096 33 | local pbkdf2_length = options.length or 16 34 | local pbkdf2_password = options.password 35 | local pbkdf2_salt = options.salt 36 | 37 | local hash = options.hash 38 | if hash == nil then 39 | error( "hash name not specified", 2 ) 40 | end 41 | 42 | ---@cast hash gpm.std.crypto.hashlib 43 | 44 | local digest_length = hash.digest 45 | local hash_fn = hash.hash 46 | 47 | local hmac_outer, hmac_inner = hmac_padding( hmac_key( pbkdf2_password, hash_fn, hash.block ), hash.block ) 48 | local block_count = math_ceil( pbkdf2_length / digest_length ) 49 | local blocks = {} 50 | 51 | for block = 1, block_count, 1 do 52 | local u = hmac_computeBinary( hash_fn, hmac_outer, hmac_inner, pbkdf2_salt .. pack_writeUInt32( block, true ) ) 53 | local t = { string_byte( u, 1, digest_length ) } 54 | 55 | for _ = 2, pbkdf2_iterations, 1 do 56 | u = hmac_computeBinary( hash_fn, hmac_outer, hmac_inner, u ) 57 | 58 | for j = 1, digest_length, 1 do 59 | t[ j ] = bit_bxor( t[ j ], string_byte( u, j ) ) 60 | end 61 | end 62 | 63 | blocks[ block ] = string_char( table_unpack( t, 1, digest_length ) ) 64 | end 65 | 66 | local pbkdf2_hash = string_toHex( table_concat( blocks, "", 1, block_count ) ) 67 | 68 | if string_len( pbkdf2_hash ) ~= pbkdf2_length then 69 | pbkdf2_hash = string_sub( pbkdf2_hash, 1, pbkdf2_length ) 70 | end 71 | 72 | return pbkdf2_hash 73 | end 74 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.sha1.lua: -------------------------------------------------------------------------------- 1 | -- TODO: sha1 lol 2 | -------------------------------------------------------------------------------- /lua/gpm/std/crypto.uuid.lua: -------------------------------------------------------------------------------- 1 | local std = _G.gpm.std 2 | 3 | local math = std.math 4 | local math_random = math.random 5 | 6 | local string = std.string 7 | local string_format = string.format 8 | 9 | local bit_band, bit_bor = std.bit.band, std.bit.bor 10 | 11 | ---@class gpm.std.crypto 12 | local crypto = std.crypto 13 | 14 | do 15 | 16 | local string_len = string.len 17 | local string_byte = string.byte 18 | local string_gsub = string.gsub 19 | local string_fromHex = string.fromHex 20 | 21 | do 22 | 23 | local md5_hash = crypto.md5.hash 24 | 25 | --- [SHARED AND MENU] 26 | --- 27 | --- Generates a UUID version 3 (name-based, MD5). 28 | --- 29 | --- UUID v3 is generated based on a namespace and a name using MD5 hashing. 30 | --- 31 | ---@param namespace string The namespace UUID (must be a valid UUID string). 32 | ---@param name string The name to hash within the namespace. 33 | ---@return string uuid A UUID v3 string. 34 | function crypto.UUIDv3( namespace, name ) 35 | local uuid = string_gsub( namespace, "-", "" ) 36 | if string_len( uuid ) ~= 32 then 37 | error( "invalid namespace UUID format", 2 ) 38 | end 39 | 40 | local b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 = string_byte( string_fromHex( md5_hash( string_fromHex( uuid ) .. name ) ), 1, 16 ) 41 | 42 | return string_format( "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 43 | b1, b2, b3, b4, b5, b6, bit_bor( bit_band( b7, 0x0F ), 0x30 ), 44 | b8, bit_bor( bit_band( b9, 0x3F ), 0x80 ), 45 | b10, b11, b12, b13, b14, b15, b16 46 | ) 47 | end 48 | 49 | end 50 | 51 | do 52 | 53 | local sha1_hash = crypto.sha1.hash 54 | 55 | --- [SHARED AND MENU] 56 | --- 57 | --- Generates a UUID version 5 (name-based, SHA-1). 58 | --- 59 | --- UUID v5 is similar to v3, but uses SHA-1 instead of MD5 for hashing. 60 | --- 61 | ---@param namespace string The namespace UUID (must be a valid UUID string). 62 | ---@param name string The name to hash within the namespace. 63 | ---@return string uuid A UUID v5 string. 64 | function crypto.UUIDv5( namespace, name ) 65 | local uuid = string_gsub( namespace, "-", "" ) 66 | if string_len( uuid ) ~= 32 then 67 | error( "invalid namespace UUID format", 2 ) 68 | end 69 | 70 | local b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 = string_byte( string_fromHex( sha1_hash( string_fromHex( uuid ) .. name ) ), 1, 16 ) 71 | 72 | return string_format( "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 73 | b1, b2, b3, b4, b5, b6, bit_bor( bit_band( b7, 0x0F ), 0x50 ), 74 | b8, bit_bor( bit_band( b9, 0x3F ), 0x80 ), 75 | b10, b11, b12, b13, b14, b15, b16 76 | ) 77 | end 78 | 79 | end 80 | 81 | end 82 | 83 | --- [SHARED AND MENU] 84 | --- 85 | --- Generates a UUID version 4 (random). 86 | --- 87 | --- UUID v4 is generated using random or pseudo-random numbers. 88 | --- No input is needed; output is a fully random UUID. 89 | --- 90 | ---@return string uuid A UUID v4 string. 91 | function crypto.UUIDv4() 92 | return string_format( "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 93 | math_random( 0, 255 ), 94 | math_random( 0, 255 ), 95 | math_random( 0, 255 ), 96 | math_random( 0, 255 ), 97 | 98 | math_random( 0, 255 ), 99 | math_random( 0, 255 ), 100 | bit_bor( bit_band( math_random( 0, 255 ), 0x0F ), 0x40 ), 101 | math_random( 0, 255 ), 102 | 103 | bit_bor( bit_band( math_random( 0, 255 ), 0x3F ), 0x80 ), 104 | math_random( 0, 255 ), 105 | math_random( 0, 255 ), 106 | math_random( 0, 255 ), 107 | 108 | math_random( 0, 255 ), 109 | math_random( 0, 255 ), 110 | math_random( 0, 255 ), 111 | math_random( 0, 255 ) 112 | ) 113 | end 114 | 115 | do 116 | 117 | local BigInt = std.BigInt 118 | local BigInt_band, BigInt_rshift = BigInt.band, BigInt.rshift 119 | local BigInt_fromNumber = BigInt.fromNumber 120 | local game_getUptime = std.game.getUptime 121 | local math_floor = math.floor 122 | local os_time = std.os.time 123 | 124 | local bigint_0xFF = BigInt_fromNumber( 0xFF ) 125 | 126 | --- [SHARED AND MENU] 127 | --- 128 | --- Generates a UUID version 7 (time-ordered). 129 | --- 130 | --- UUID v7 is a 128-bit based on a timestamp unique identifier, 131 | --- like it's older siblings, such as the widely used UUIDv4. 132 | --- 133 | --- But unlike v4, UUIDv7 is time-sortable 134 | --- with 1 ms precision. 135 | --- 136 | --- By combining the timestamp and 137 | --- the random parts, UUIDv7 becomes an 138 | --- excellent choice for record identifiers 139 | --- in databases, including distributed ones. 140 | --- 141 | ---@param timestamp? gpm.std.BigInt The UNIX-64 timestamp to use. 142 | ---@return string uuid A UUID v7 string. 143 | function crypto.UUIDv7( timestamp ) 144 | if timestamp == nil then 145 | timestamp = BigInt_fromNumber( math_floor( ( os_time() + game_getUptime() % 1 ) * 1000 ) ) 146 | end 147 | 148 | return string_format( "0%s%s%s%s-%s%s-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 149 | BigInt_rshift( timestamp, 40 ):band( bigint_0xFF ):toHex( true ), 150 | BigInt_rshift( timestamp, 32 ):band( bigint_0xFF ):toHex( true ), 151 | BigInt_rshift( timestamp, 24 ):band( bigint_0xFF ):toHex( true ), 152 | BigInt_rshift( timestamp, 16 ):band( bigint_0xFF ):toHex( true ), 153 | 154 | BigInt_rshift( timestamp, 8 ):band( bigint_0xFF ):toHex( true ), 155 | BigInt_band( timestamp, bigint_0xFF ):toHex( true ), 156 | bit_bor( bit_band( math_random( 0, 255 ), 0x0F ), 0x70 ), 157 | math_random( 0, 255 ), 158 | 159 | bit_bor( bit_band( math_random( 0, 255 ), 0x3F ), 0x80 ), 160 | math_random( 0, 255 ), 161 | math_random( 0, 255 ), 162 | math_random( 0, 255 ), 163 | 164 | math_random( 0, 255 ), 165 | math_random( 0, 255 ), 166 | math_random( 0, 255 ), 167 | math_random( 0, 255 ) 168 | ) 169 | end 170 | 171 | end 172 | 173 | --- [SHARED AND MENU] 174 | --- 175 | --- Returns a UUID v8 string from the given bytes. 176 | --- 177 | --- UUIDv8 is a flexible version allowing custom data alongside version and variant bits. 178 | --- 179 | ---@param b1? integer Unsigned byte (0..255) 180 | ---@param b2? integer Unsigned byte (0..255) 181 | ---@param b3? integer Unsigned byte (0..255) 182 | ---@param b4? integer Unsigned byte (0..255) 183 | ---@param b5? integer Unsigned byte (0..255) 184 | ---@param b6? integer Unsigned byte (0..255) 185 | ---@param b7? integer Unsigned byte (0..15) 186 | ---@param b8? integer Unsigned byte (0..255) 187 | ---@param b9? integer Unsigned byte (0..63) 188 | ---@param b10? integer Unsigned byte (0..255) 189 | ---@param b11? integer Unsigned byte (0..255) 190 | ---@param b12? integer Unsigned byte (0..255) 191 | ---@param b13? integer Unsigned byte (0..255) 192 | ---@param b14? integer Unsigned byte (0..255) 193 | ---@param b15? integer Unsigned byte (0..255) 194 | ---@param b16? integer Unsigned byte (0..255) 195 | ---@return string uuid A UUID v8 string. 196 | function crypto.UUIDv8( b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 ) 197 | return string_format( 198 | "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 199 | b1 or 0, b2 or 0, b3 or 0, b4 or 0, b5 or 0, b6 or 0, 200 | bit_bor( bit_band( b7 or 0, 0x0F ), 0x80 ), b8 or 0, 201 | bit_bor( bit_band( b9 or 0, 0x3F ), 0x80 ), b10 or 0, 202 | b11 or 0, b12 or 0, b13 or 0, b14 or 0, b15 or 0, b16 or 0 203 | ) 204 | end 205 | -------------------------------------------------------------------------------- /lua/gpm/std/debug.gc.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local std = _G.gpm.std 3 | 4 | local collectgarbage = _G.collectgarbage 5 | 6 | if collectgarbage == nil then 7 | function collectgarbage( action ) 8 | if action == "count" or action == "setpause" or action == "setstepmul" then 9 | return 0 10 | elseif action == "isrunning" or action == "step" then 11 | return false 12 | end 13 | end 14 | end 15 | 16 | ---@class gpm.std.debug 17 | local debug = std.debug 18 | 19 | --- [SHARED AND MENU] 20 | --- 21 | --- Lua manages memory automatically by running a garbage collector to collect all dead objects (that is, objects that are no longer accessible from Lua). 22 | --- 23 | --- All memory used by Lua is subject to automatic management: strings, tables, userdata, functions, threads, internal structures, etc. 24 | --- 25 | ---@class gpm.std.debug.gc 26 | local gc = debug.gc or {} 27 | debug.gc = gc 28 | 29 | 30 | --- [SHARED AND MENU] 31 | --- 32 | --- Performs a full garbage-collection cycle. 33 | --- 34 | function gc.collect() 35 | collectgarbage( "collect" ) 36 | end 37 | 38 | --- [SHARED AND MENU] 39 | --- 40 | --- The value has a fractional part, so that it multiplied by 1024 gives the exact number of bytes in use by Lua (except for overflows). 41 | --- 42 | ---@return number count The total memory in use by Lua in Kbytes. 43 | function gc.getMemory() 44 | return collectgarbage( "count" ) 45 | end 46 | 47 | --- [SHARED AND MENU] 48 | --- 49 | --- Stops automatic execution of the garbage collector. 50 | --- The collector will run only when explicitly invoked, until a call to restart it. 51 | --- 52 | function gc.stop() 53 | collectgarbage( "stop" ) 54 | end 55 | 56 | --- [SHARED AND MENU] 57 | --- 58 | --- Restarts automatic execution of the garbage collector. 59 | --- 60 | function gc.restart() 61 | collectgarbage( "restart" ) 62 | end 63 | 64 | --- [SHARED AND MENU] 65 | --- 66 | --- Returns a boolean that tells whether the collector is running (i.e., not stopped). 67 | --- 68 | ---@return boolean is_running Returns true if the collector is running, false otherwise. 69 | function gc.isRunning() 70 | return collectgarbage( "isrunning" ) 71 | end 72 | 73 | --- [SHARED AND MENU] 74 | --- 75 | --- The garbage-collector pause controls how long the collector waits before starting a new cycle. 76 | --- Larger values make the collector less aggressive. 77 | --- 78 | --- Values smaller than 100 mean the collector will not wait to start a new cycle. 79 | --- A value of 200 means that the collector waits for the total memory in use to double before starting a new cycle. 80 | --- 81 | ---@param value number The new value for the pause of the collector. 82 | ---@return number pause_value The previous value for pause. 83 | function gc.setPause( value ) 84 | return collectgarbage( "setpause", value ) 85 | end 86 | 87 | --- [SHARED AND MENU] 88 | --- 89 | --- The garbage-collector step multiplier controls the relative speed of the collector relative to memory allocation. 90 | --- Larger values make the collector more aggressive but also increase the size of each incremental step. 91 | --- 92 | --- You should not use values smaller than 100, because they make the collector too slow and can result in the collector never finishing a cycle. 93 | --- The default is 200, which means that the collector runs at "twice" the speed of memory allocation. 94 | --- 95 | ---@param size number With a zero value, the collector will perform one basic (indivisible) step. For non-zero values, the collector will perform as if that amount of memory (in KBytes) had been allocated by Lua. 96 | ---@return boolean finished Returns `true` if the step finished a collection cycle. 97 | function gc.setStep( size ) 98 | return collectgarbage( "step", size ) 99 | end 100 | 101 | --- [SHARED AND MENU] 102 | --- 103 | --- If you set the step multiplier to a very large number (larger than 10% of the maximum number of bytes that the program may use), the collector behaves like a stop-the-world collector. 104 | --- If you then set the pause to 200, the collector behaves as in old Lua versions, doing a complete collection every time Lua doubles its memory usage. 105 | --- 106 | ---@param value number The new value for the step multiplier of the collector. 107 | ---@return number previous_value The previous value for step. 108 | function gc.setStepMultiplier( value ) 109 | return collectgarbage( "setstepmul", value ) 110 | end 111 | 112 | local debug_getmetatable = std.debug.getmetatable 113 | 114 | do 115 | 116 | local raw_get = std.raw.get 117 | 118 | --- [SHARED AND MENU] 119 | --- 120 | --- Returns info about the garbage collector rules for the given table. 121 | --- 122 | ---@param tbl table The table to query. 123 | ---@return boolean key_mode `true` if key c ollection is allowed, `false` otherwise. 124 | ---@return boolean value_mode `true` if value collection is allowed, `false` otherwise. 125 | function gc.getTableRules( tbl ) 126 | local metatable = debug_getmetatable( tbl ) 127 | if metatable == nil then 128 | return false, false 129 | end 130 | 131 | local mode = raw_get( metatable, "__mode" ) 132 | 133 | if mode == nil then 134 | return false, false 135 | elseif mode == "kv" then 136 | return true, true 137 | else 138 | return mode == "k", mode == "v" 139 | end 140 | end 141 | 142 | end 143 | 144 | do 145 | 146 | local setmetatable = std.setmetatable 147 | 148 | local presets = { 149 | [ 1 ] = { __mode = "kv" }, 150 | [ 2 ] = { __mode = "k" }, 151 | [ 3 ] = { __mode = "v" } 152 | } 153 | 154 | --- [SHARED AND MENU] 155 | --- 156 | --- Sets the garbage-collector rules for the given table. 157 | --- 158 | ---@param tbl table The table to set the rules for. 159 | ---@param key? boolean `true` if key collection is allowed, `false` otherwise. 160 | ---@param value? boolean `true` if value collection is allowed, `false` otherwise. 161 | function gc.setTableRules( tbl, key, value ) 162 | local mode_id 163 | if key and value then 164 | mode_id = 1 165 | elseif key then 166 | mode_id = 2 167 | elseif value then 168 | mode_id = 3 169 | else 170 | mode_id = 0 171 | end 172 | 173 | local metatable = debug_getmetatable( tbl ) 174 | if metatable == nil then 175 | if mode_id ~= 0 then 176 | setmetatable( tbl, presets[ mode_id ] ) 177 | end 178 | 179 | return 180 | end 181 | 182 | if metatable == presets[ mode_id ] then 183 | return 184 | end 185 | 186 | for i = 1, 3, 1 do 187 | if presets[ i ] == metatable then 188 | if mode_id == 0 then 189 | setmetatable( tbl, nil ) 190 | else 191 | setmetatable( tbl, presets[ mode_id ] ) 192 | end 193 | 194 | return 195 | end 196 | end 197 | 198 | if mode_id == 0 then 199 | metatable.__mode = nil 200 | elseif mode_id == 1 then 201 | metatable.__mode = "kv" 202 | elseif mode_id == 2 then 203 | metatable.__mode = "k" 204 | elseif mode_id == 3 then 205 | metatable.__mode = "v" 206 | end 207 | end 208 | 209 | end 210 | -------------------------------------------------------------------------------- /lua/gpm/std/debug.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local gpm = _G.gpm 3 | local glua_string = _G.string 4 | 5 | ---@class gpm.std 6 | local std = gpm.std 7 | 8 | --- [SHARED AND MENU] 9 | --- 10 | --- The debug library is intended to help you debug your scripts, 11 | --- however it also has several other powerful uses. 12 | --- 13 | --- In gpm this library has additional functions. 14 | --- 15 | ---@class gpm.std.debug : debuglib 16 | local debug = std.debug or {} 17 | std.debug = debug 18 | 19 | local fempty = debug.fempty 20 | if fempty == nil then 21 | 22 | --- [SHARED AND MENU] 23 | --- 24 | --- Just empty function, do nothing. 25 | --- 26 | --- Sometimes makes jit happy :> 27 | --- 28 | function fempty() 29 | -- yep, it's literally empty 30 | end 31 | 32 | debug.fempty = fempty 33 | 34 | end 35 | 36 | do 37 | 38 | local glua_debug = _G.debug 39 | 40 | -- LuaJIT 41 | debug.newproxy = debug.newproxy or _G.newproxy 42 | 43 | -- Lua 5.1 44 | debug.debug = debug.debug or glua_debug.debug or fempty 45 | debug.getinfo = debug.getinfo or glua_debug.getinfo or fempty 46 | debug.getregistry = debug.getregistry or glua_debug.getregistry or fempty 47 | debug.traceback = debug.traceback or glua_debug.traceback or fempty 48 | 49 | debug.getlocal = debug.getlocal or glua_debug.getlocal or fempty 50 | debug.setlocal = debug.setlocal or glua_debug.setlocal or fempty 51 | 52 | debug.getmetatable = debug.getmetatable or glua_debug.getmetatable or std.getmetatable or fempty 53 | debug.setmetatable = debug.setmetatable or glua_debug.setmetatable or function() return false end 54 | 55 | debug.getupvalue = debug.getupvalue or glua_debug.getupvalue or fempty -- fucked up in menu 56 | debug.setupvalue = debug.setupvalue or glua_debug.setupvalue or fempty -- fucked up in menu 57 | 58 | debug.getfenv = debug.getfenv or glua_debug.getfenv or std.getfenv or fempty 59 | debug.setfenv = debug.setfenv or glua_debug.setfenv or std.setfenv or fempty 60 | 61 | debug.gethook = debug.gethook or glua_debug.gethook or fempty 62 | debug.sethook = debug.sethook or glua_debug.sethook or fempty 63 | 64 | -- Lua 5.2 65 | debug.upvalueid = debug.upvalueid or glua_debug.upvalueid or fempty -- fucked up in menu 66 | debug.upvaluejoin = debug.upvaluejoin or glua_debug.upvaluejoin or fempty -- fucked up in menu 67 | 68 | debug.getuservalue = debug.getuservalue or glua_debug.getuservalue or fempty -- fucked up in menu 69 | debug.setuservalue = debug.setuservalue or glua_debug.setuservalue or fempty -- fucked up in menu 70 | 71 | end 72 | 73 | if debug.getmetatable == fempty or debug.setmetatable == fempty then 74 | error( "I tried my best, but it's over." ) 75 | end 76 | 77 | local debug_getinfo = debug.getinfo 78 | 79 | --- [SHARED AND MENU] 80 | --- 81 | --- Call function with given arguments. 82 | --- 83 | ---@param func function The function to call. 84 | ---@param ... any Arguments to be passed to the function. 85 | ---@return any ... The return values of the function. 86 | function debug.fcall( func, ... ) 87 | return func( ... ) 88 | end 89 | 90 | do 91 | 92 | local FindMetaTable = _G.FindMetaTable or fempty 93 | local registry = debug.getregistry() or {} 94 | 95 | --- [SHARED AND MENU] 96 | --- 97 | --- Returns the registry table. 98 | --- 99 | ---@diagnostic disable-next-line: duplicate-set-field 100 | function debug.getregistry() 101 | return registry 102 | end 103 | 104 | --- [SHARED AND MENU] 105 | --- 106 | --- Returns the metatable of the given name or `nil` if not found. 107 | --- 108 | ---@param name string The name of the metatable. 109 | ---@return table | nil meta The metatable. 110 | function debug.findmetatable( name ) 111 | local cached = registry[ name ] 112 | if cached ~= nil then 113 | return cached 114 | end 115 | 116 | local metatable = FindMetaTable( name ) 117 | if metatable ~= nil then 118 | registry[ name ] = metatable 119 | return metatable 120 | end 121 | 122 | return nil 123 | end 124 | 125 | --- [SHARED AND MENU] 126 | --- 127 | --- Returns all upvalues of the given function. 128 | --- 129 | ---@param fn function The function to get upvalues from. 130 | ---@param start_position? integer The start position of the upvalues, default is `0`. 131 | ---@return table values A table with the upvalues. 132 | ---@return integer value_count The count of upvalues. 133 | function debug.getupvalues( fn, start_position ) 134 | if start_position == nil then 135 | start_position = 0 136 | end 137 | 138 | start_position = start_position + 1 139 | 140 | local values = {} 141 | 142 | local i = start_position 143 | while true do 144 | local name, value = debug.getupvalue( fn, i ) 145 | if not name then break end 146 | values[ name ] = value 147 | i = i + 1 148 | end 149 | 150 | return values, i - start_position 151 | end 152 | 153 | ---@class gpm.std.raw 154 | local raw = std.raw 155 | 156 | if raw.type == nil then 157 | 158 | local glua_type = _G.type 159 | 160 | local values, count = debug.getupvalues( glua_type ) 161 | 162 | if count == 0 or values.C_type == nil then 163 | raw.type = glua_type 164 | else 165 | raw.type = values.C_type 166 | end 167 | 168 | end 169 | 170 | do 171 | 172 | local debug_getmetatable = debug.getmetatable 173 | local raw_get = raw.get 174 | 175 | -- in case the game gets killed (thanks Garry) 176 | if debug_getmetatable( fempty ) == nil and 177 | not debug.setmetatable( fempty, {} ) and 178 | debug_getmetatable( fempty ) == nil then 179 | 180 | local raw_type = raw.type 181 | 182 | --- [SHARED AND MENU] 183 | --- 184 | --- Returns the metatable of the given value or `nil` if not found. 185 | --- 186 | ---@param value any The value. 187 | ---@return table | nil meta The metatable. 188 | ---@diagnostic disable-next-line: duplicate-set-field 189 | function debug.getmetatable( value ) 190 | return debug_getmetatable( value ) or registry[ raw_type( value ) ] 191 | end 192 | 193 | std.print( "at any cost, but it will work..." ) 194 | 195 | end 196 | 197 | --- [SHARED AND MENU] 198 | --- 199 | --- Returns the value of the given key in the metatable of the given value. 200 | --- 201 | --- Returns `nil` if not found. 202 | --- 203 | ---@param value any The value to get the metatable from. 204 | ---@param key string The searchable key. 205 | ---@return any | nil value The value of the given key. 206 | function debug.getmetavalue( value, key, allow_index ) 207 | local metatable = debug_getmetatable( value ) 208 | if metatable == nil then 209 | return nil 210 | elseif allow_index then 211 | return metatable[ key ] 212 | else 213 | return raw_get( metatable, key ) 214 | end 215 | end 216 | 217 | end 218 | 219 | local RegisterMetaTable = _G.RegisterMetaTable or fempty 220 | 221 | --- [SHARED AND MENU] 222 | --- 223 | --- Registers the metatable of the given name and table. 224 | --- 225 | ---@param name string The name of the metatable. 226 | ---@param tbl table The metatable. 227 | ---@param do_full_register? boolean If `true`, the metatable will be registered. 228 | ---@return integer meta_id The ID of the metatable or -1 if not fully registered. 229 | function debug.registermetatable( name, tbl, do_full_register ) 230 | tbl = registry[ name ] or tbl 231 | registry[ name ] = tbl 232 | 233 | if do_full_register then 234 | RegisterMetaTable( name, tbl ) 235 | return tbl.MetaID or -1 236 | else 237 | return -1 238 | end 239 | end 240 | 241 | end 242 | 243 | --- [SHARED AND MENU] 244 | --- 245 | --- Returns current stack trace as a debuginfo list. 246 | --- 247 | ---@param start_position? integer The start position of the stack trace. 248 | ---@param fields? string The fields of the stack trace. 249 | ---@return debuginfo[] stack The stack trace. 250 | ---@return integer length The length of the stack trace. 251 | function debug.getstack( start_position, fields ) 252 | local stack, length = {}, 0 253 | 254 | for location = 1 + ( start_position or 1 ), 16, 1 do 255 | local info = debug_getinfo( location, fields or "Snluf" ) 256 | if info then 257 | length = length + 1 258 | stack[ length ] = info 259 | else 260 | break 261 | end 262 | end 263 | 264 | return stack, length 265 | end 266 | 267 | --- [SHARED AND MENU] 268 | --- 269 | --- Returns the function within which the call was made or `nil` if not found. 270 | --- 271 | ---@param level? integer The stack level. 272 | ---@return function | nil fn The function within which the call was made or `nil` if not found. 273 | function debug.getfmain( level ) 274 | if level == nil then 275 | level = 1 276 | end 277 | 278 | for location = level + 1, level + 16, 1 do 279 | local info = debug_getinfo( location, "fS" ) 280 | if info == nil then 281 | return nil 282 | elseif info.what == "main" then 283 | return info.func 284 | end 285 | 286 | end 287 | end 288 | 289 | do 290 | 291 | local string_sub, string_gsub = glua_string.sub, glua_string.gsub 292 | local gsub_formatter = function( _, str ) return str end 293 | 294 | --- [SHARED AND MENU] 295 | --- 296 | --- Returns the path to the file of the given location or `nil` if not found. 297 | --- 298 | ---@param location function | integer 299 | ---@return string | nil path_to_file 300 | function debug.getfpath( location ) 301 | local info = debug_getinfo( location, "S" ) 302 | if info.what == "main" then 303 | ---@diagnostic disable-next-line: redundant-return-value 304 | return "/garrysmod/" .. string_gsub( string_gsub( string_sub( info.source, 2 ), "^(.-)(lua/.*)$", gsub_formatter ), "^(.-)([%w_]+/gamemode/.*)$", gsub_formatter ), nil 305 | end 306 | end 307 | 308 | end 309 | 310 | if std.jit then 311 | 312 | ---@diagnostic disable-next-line: undefined-field 313 | local util_funcinfo = std.jit.util.funcinfo 314 | 315 | --- [SHARED AND MENU] 316 | --- 317 | --- Checks if the function is jit compilable. 318 | --- 319 | ---@param fn function The function to check. 320 | ---@return boolean bool `true` if the function is jit compilable, otherwise `false`. 321 | function debug.isjitcompilable( fn ) 322 | local info = util_funcinfo( fn ) 323 | return info and info.ffid ~= nil 324 | end 325 | 326 | else 327 | 328 | function debug.isjitcompilable() 329 | return false 330 | end 331 | 332 | end 333 | 334 | -- do 335 | 336 | -- local getfenv = std.getfenv 337 | 338 | -- --- [SHARED AND MENU] 339 | -- --- 340 | -- --- Returns the function package or `nil` if not found. 341 | -- --- 342 | -- ---@param location function | integer The function or stack level. 343 | -- ---@return Package? pkg The function package or `nil` if not found. 344 | -- function debug.getfpackage( location ) 345 | -- -- TODO: Check this after creating the package class 346 | -- local fenv = getfenv( location ) 347 | -- return fenv == nil and nil or fenv.__package 348 | -- end 349 | 350 | -- end 351 | 352 | -- do 353 | 354 | -- local setfenv = std.setfenv 355 | 356 | -- --- [SHARED AND MENU] 357 | -- --- 358 | -- --- Sets the function package. 359 | -- --- 360 | -- ---@param location function | integer The function or stack level. 361 | -- ---@param package Package The package to set. 362 | -- function debug.setfpackage( location, package ) 363 | -- -- TODO: Check this after creating the package class 364 | -- setfenv( location, package.env ) 365 | -- end 366 | 367 | -- end 368 | -------------------------------------------------------------------------------- /lua/gpm/std/decal.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Decal class 2 | -------------------------------------------------------------------------------- /lua/gpm/std/effect.lua: -------------------------------------------------------------------------------- 1 | -- TODO: create particle class for registry and creation 2 | -- TODO: rename in particles or something eles idk 3 | 4 | --[[ 5 | 6 | https://wiki.facepunch.com/gmod/Effects 7 | 8 | 9 | https://wiki.facepunch.com/gmod/util.Effect 10 | 11 | https://wiki.facepunch.com/gmod/game.AddParticles 12 | 13 | https://wiki.facepunch.com/gmod/util.ParticleTracer 14 | https://wiki.facepunch.com/gmod/util.ParticleTracerEx 15 | 16 | https://wiki.facepunch.com/gmod/Global.EffectData 17 | 18 | https://wiki.facepunch.com/gmod/Global.ParticleEffect 19 | https://wiki.facepunch.com/gmod/Global.ParticleEffectAttach 20 | 21 | https://wiki.facepunch.com/gmod/Global.ParticleEmitter 22 | 23 | https://wiki.facepunch.com/gmod/Global.PrecacheParticleSystem 24 | 25 | https://wiki.facepunch.com/gmod/Global.CreateParticleSystem 26 | https://wiki.facepunch.com/gmod/Global.CreateParticleSystemNoEntity 27 | 28 | https://wiki.facepunch.com/gmod/CLuaEmitter 29 | 30 | https://wiki.facepunch.com/gmod/CLuaParticle 31 | 32 | https://wiki.facepunch.com/gmod/CEffectData 33 | 34 | https://wiki.facepunch.com/gmod/CNewParticleEffect 35 | 36 | ]] 37 | -------------------------------------------------------------------------------- /lua/gpm/std/entity.lua: -------------------------------------------------------------------------------- 1 | -- TODO: entity class 2 | -------------------------------------------------------------------------------- /lua/gpm/std/error.lua: -------------------------------------------------------------------------------- 1 | ---@class gpm.std 2 | local std = _G.gpm.std 3 | 4 | local debug_getstack, debug_getupvalue, debug_getlocal = std.debug.getstack, std.debug.getupvalue, std.debug.getlocal 5 | 6 | local string = std.string 7 | local string_rep, string_format = string.rep, string.format 8 | 9 | local table_concat = std.table.concat 10 | local class_base, class_create = std.class.base, std.class.create 11 | 12 | ---@diagnostic disable-next-line: undefined-field 13 | local ErrorNoHalt, ErrorNoHaltWithStack = _G.ErrorNoHalt, _G.ErrorNoHaltWithStack 14 | 15 | if not ErrorNoHalt then 16 | ErrorNoHalt = std.print 17 | end 18 | 19 | if not ErrorNoHaltWithStack then 20 | ErrorNoHaltWithStack = std.print 21 | end 22 | 23 | local callStack, callStackSize = {}, 0 24 | 25 | -- local function pushCallStack( stack ) 26 | -- local size = callStackSize + 1 27 | -- callStack[ size ] = stack 28 | -- callStackSize = size 29 | -- end 30 | 31 | -- local function popCallStack() 32 | -- local pos = callStackSize 33 | -- if pos == 0 then 34 | -- return nil 35 | -- end 36 | 37 | -- local stack = callStack[ pos ] 38 | -- callStack[ pos ] = nil 39 | -- callStackSize = pos - 1 40 | -- return stack 41 | -- end 42 | 43 | -- local function appendStack( stack ) 44 | -- return pushCallStack( { stack, callStack[ callStackSize ] } ) 45 | -- end 46 | 47 | local function mergeStack( stack ) 48 | local pos = #stack 49 | 50 | local currentCallStack = callStack[ callStackSize ] 51 | while currentCallStack do 52 | local lst = currentCallStack[ 1 ] 53 | for i = 1, #lst do 54 | local info = lst[ i ] 55 | pos = pos + 1 56 | stack[ pos ] = info 57 | end 58 | 59 | currentCallStack = currentCallStack[ 2 ] 60 | end 61 | 62 | return stack 63 | end 64 | 65 | local dumpFile 66 | do 67 | 68 | local math_min, math_max, math_floor, math_log10, math_huge 69 | do 70 | local math = std.math 71 | math_min, math_max, math_floor, math_log10, math_huge = math.min, math.max, math.floor, math.log10, math.huge 72 | end 73 | 74 | local string_split, string_find, string_sub, string_len = string.split, string.find, string.sub, string.len 75 | local console_write = std.console.write 76 | 77 | -- TODO: Repace later with new File class 78 | local file_Open = _G.file.Open 79 | 80 | local gray = Color( 180, 180, 180 ) 81 | local white = Color( 225, 225, 225 ) 82 | local danger = Color( 239, 68, 68 ) 83 | 84 | dumpFile = function( message, fileName, line ) 85 | if not ( fileName and line ) then return end 86 | 87 | ---@class File 88 | ---@diagnostic disable-next-line: assign-type-mismatch 89 | local handler = file_Open( fileName, "rb", "GAME" ) 90 | if handler == nil then return end 91 | 92 | local str = handler:Read( handler:Size() ) 93 | handler:Close() 94 | 95 | if string_len( str ) == 0 then 96 | return 97 | end 98 | 99 | local lines = string_split( str, "\n" ) 100 | if not ( lines and lines[ line ] ) then 101 | return 102 | end 103 | 104 | local start = math_max( 1, line - 5 ) 105 | local finish = math_min( #lines, line + 3 ) 106 | local numWidth = math_floor( math_log10( finish ) ) + 1 107 | 108 | local longestLine, firstChar = 0, math_huge 109 | for i = start, finish do 110 | local code = lines[ i ] 111 | local pos = string_find( code, "%S" ) 112 | if pos and pos < firstChar then 113 | firstChar = pos 114 | end 115 | 116 | longestLine = math_max( longestLine, string_len( code ) ) 117 | end 118 | 119 | longestLine = math_min( longestLine - firstChar, 120 ) 120 | console_write( gray, string_rep( " ", numWidth + 3 ), string_rep( "_", longestLine + 4 ), "\n", string_rep( " ", numWidth + 2 ), "|\n" ) 121 | 122 | local numFormat = " %0" .. numWidth .. "d | " 123 | for i = start, finish do 124 | local code = lines[ i ] 125 | 126 | console_write( i == line and white or gray, string_format( numFormat, i ), string_sub( code, firstChar, longestLine + firstChar ), "\n" ) 127 | 128 | if i == line then 129 | console_write( 130 | gray, string_rep(" ", numWidth + 2), "| ", string_sub( code, firstChar, ( string_find( code, "%S" ) or 1 ) - 1 ), danger, "^ ", tostring( message ), "\n", 131 | gray, string_rep(" ", numWidth + 2), "|\n" 132 | ) 133 | end 134 | end 135 | 136 | console_write( gray, string_rep( " ", numWidth + 2 ), "|\n", string_rep( " ", numWidth + 3 ), string_rep( "¯", longestLine + 4 ), "\n\n" ) 137 | end 138 | 139 | end 140 | 141 | --- [SHARED AND MENU] 142 | --- 143 | --- Error object. 144 | ---@alias Error gpm.std.Error 145 | ---@class gpm.std.Error : gpm.std.Object 146 | ---@field __class gpm.std.ErrorClass 147 | ---@field __parent gpm.std.Error | nil 148 | ---@field name string 149 | local Error = class_base( "Error" ) 150 | 151 | ---@protected 152 | function Error:__index( key ) 153 | if key == "name" then 154 | return self.__type 155 | end 156 | 157 | return Error[ key ] 158 | end 159 | 160 | ---@protected 161 | function Error:__tostring() 162 | if self.fileName then 163 | return string_format( "%s:%d: %s: %s", self.fileName, self.lineNumber or 0, self.name, self.message ) 164 | else 165 | return self.name .. ": " .. self.message 166 | end 167 | end 168 | 169 | ---@protected 170 | ---@param message string 171 | ---@param fileName string? 172 | ---@param lineNumber number? 173 | ---@param stackPos integer? 174 | function Error:__init( message, fileName, lineNumber, stackPos ) 175 | if stackPos == nil then stackPos = 0 end 176 | self.lineNumber = lineNumber 177 | self.fileName = fileName 178 | self.message = message 179 | 180 | local stack = debug_getstack( stackPos ) 181 | self.stack = stack 182 | mergeStack( stack ) 183 | 184 | local first = stack[ 1 ] 185 | if first == nil then return end 186 | 187 | self.fileName = self.fileName or first.short_src 188 | self.lineNumber = self.lineNumber or first.currentline 189 | 190 | if debug_getupvalue and first.func and first.nups and first.nups > 0 then 191 | local upvalues = {} 192 | self.upvalues = upvalues 193 | 194 | for i = 1, first.nups do 195 | local name, value = debug_getupvalue( first.func, i ) 196 | if name == nil then 197 | self.upvalues = nil 198 | break 199 | end 200 | 201 | upvalues[ i ] = { name, value } 202 | end 203 | end 204 | 205 | if debug_getlocal then 206 | local locals, count, i = {}, 0, 1 207 | while true do 208 | local name, value = debug_getlocal( stackPos, i ) 209 | if name == nil then break end 210 | 211 | if name ~= "(*temporary)" then 212 | count = count + 1 213 | locals[ count ] = { name, value } 214 | end 215 | 216 | i = i + 1 217 | end 218 | 219 | if count ~= 0 then 220 | self.locals = locals 221 | end 222 | end 223 | end 224 | 225 | --- [SHARED AND MENU] 226 | --- 227 | --- Displays the error. 228 | function Error:display() 229 | if isstring( self ) then 230 | ---@diagnostic disable-next-line: cast-type-mismatch 231 | ---@cast self string 232 | ErrorNoHaltWithStack( self ) 233 | return 234 | end 235 | 236 | local lines, length = { "\n[ERROR] " .. tostring( self ) }, 1 237 | 238 | local stack = self.stack 239 | if stack then 240 | for i = 1, #stack do 241 | local info = stack[ i ] 242 | length = length + 1 243 | lines[ length ] = string_format( "%s %d. %s - %s:%d", string_rep( " ", i ), i, info.name or "unknown", info.short_src, info.currentline or -1 ) 244 | end 245 | end 246 | 247 | local locals = self.locals 248 | if locals then 249 | length = length + 1 250 | lines[ length ] = "\n=== Locals ===" 251 | 252 | for i = 1, #locals do 253 | local entry = locals[ i ] 254 | length = length + 1 255 | lines[ length ] = string_format( " - %s = %s", entry[ 1 ], entry[ 2 ] ) 256 | end 257 | end 258 | 259 | local upvalues = self.upvalues 260 | if upvalues ~= nil then 261 | length = length + 1 262 | lines[ length ] = "\n=== Upvalues ===" 263 | 264 | for i = 1, #upvalues do 265 | local entry = upvalues[ i ] 266 | length = length + 1 267 | lines[ length ] = string_format( " - %s = %s", entry[ 1 ], entry[ 2 ] ) 268 | end 269 | end 270 | 271 | length = length + 1 272 | lines[ length ] = "\n" 273 | ErrorNoHalt( table_concat( lines, "\n", 1, length ) ) 274 | 275 | if self.message and self.fileName and self.lineNumber then 276 | dumpFile( self.name .. ": " .. self.message, self.fileName, self.lineNumber ) 277 | end 278 | end 279 | 280 | --- [SHARED AND MENU] 281 | --- 282 | --- Basic error class. 283 | ---@class gpm.std.ErrorClass : gpm.std.Error 284 | ---@field __base gpm.std.Error 285 | ---@overload fun(message: string, fileName: string?, lineNumber: number?, stackPos: number?): Error 286 | local ErrorClass = class_create( Error ) 287 | std.Error = ErrorClass 288 | 289 | --- [SHARED AND MENU] 290 | --- 291 | --- Creates a new `Error` with custom name. 292 | ---@param name string The name of the error. 293 | ---@param base Error | nil The base class of the error. 294 | ---@return Error cls The new error class. 295 | function ErrorClass.make( name, base ) 296 | return class_create( class_base( name, base or ErrorClass ) ) ---@type Error 297 | end 298 | 299 | -- Built-in error classes. 300 | std.NotImplementedError = ErrorClass.make( "NotImplementedError" ) 301 | std.FutureCancelError = ErrorClass.make( "FutureCancelError" ) 302 | std.InvalidStateError = ErrorClass.make( "InvalidStateError" ) 303 | std.CodeCompileError = ErrorClass.make( "CodeCompileError" ) 304 | std.FileSystemError = ErrorClass.make( "FileSystemError" ) 305 | std.HTTPClientError = ErrorClass.make( "HTTPClientError" ) 306 | std.RuntimeError = ErrorClass.make( "RuntimeError" ) 307 | std.PackageError = ErrorClass.make( "PackageError" ) 308 | std.ModuleError = ErrorClass.make( "ModuleError" ) 309 | std.SourceError = ErrorClass.make( "SourceError" ) 310 | std.FutureError = ErrorClass.make( "FutureError" ) 311 | std.AddonError = ErrorClass.make( "AddonError" ) 312 | std.RangeError = ErrorClass.make( "RangeError" ) 313 | std.TypeError = ErrorClass.make( "TypeError" ) 314 | 315 | ---@alias gpm.std.ErrorType 316 | ---| number # error with level 317 | ---| `-1` # ErrorNoHalt 318 | ---| `-2` # ErrorNoHaltWithStack 319 | -------------------------------------------------------------------------------- /lua/gpm/std/fluent.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | TODO: 3 | 4 | https://github.com/alerque/fluent-lua 5 | https://github.com/Def-Try/thorium/blob/main/lua/thorium/localisation.lua 6 | https://github.com/Pika-Software/plib_translate 7 | https://wiki.facepunch.com/gmod/language 8 | https://projectfluent.org/ 9 | web language files support 10 | 11 | ]] 12 | -------------------------------------------------------------------------------- /lua/gpm/std/game.classes.lua: -------------------------------------------------------------------------------- 1 | 2 | ---@class gpm.std 3 | local std = _G.gpm.std 4 | local class = std.class 5 | 6 | local Model = class.base( "Model" ) 7 | 8 | local ModelClass = class.create( Model ) 9 | std.Model = ModelClass 10 | 11 | --[[ 12 | 13 | TODO: 14 | 15 | https://wiki.facepunch.com/gmod/Global.Material 16 | 17 | https://wiki.facepunch.com/gmod/Global.CreateMaterial 18 | 19 | https://wiki.facepunch.com/gmod/Global.DynamicMaterial 20 | 21 | https://wiki.facepunch.com/gmod/Material_Parameters 22 | 23 | ]] 24 | 25 | local Material = class.base( "Material" ) 26 | 27 | local MaterialClass = class.create( Material ) 28 | std.Material = MaterialClass 29 | 30 | local Sound = class.base( "Sound" ) 31 | 32 | local SoundClass = class.create( Sound ) 33 | std.Sound = SoundClass 34 | 35 | -- TODO: csound 36 | -------------------------------------------------------------------------------- /lua/gpm/std/game.hooks.lua: -------------------------------------------------------------------------------- 1 | local gpm = _G.gpm 2 | local std = gpm.std 3 | 4 | local engine_hookCatch = gpm.engine.hookCatch 5 | local Hook = std.Hook 6 | 7 | do 8 | 9 | ---@class gpm.std.game 10 | local game = std.game 11 | 12 | if game.Tick == nil then 13 | 14 | --- [SHARED AND MENU] 15 | --- 16 | --- A hook that is called every tick. 17 | local Tick = Hook( "game.Tick" ) 18 | engine_hookCatch( "Tick", Tick ) 19 | game.Tick = Tick 20 | 21 | end 22 | 23 | if game.ShutDown == nil then 24 | 25 | --- [SHARED] 26 | --- 27 | --- A hook that is called when the game is shutting down. 28 | local ShutDown = Hook( "game.ShutDown" ) 29 | engine_hookCatch( "ShutDown", ShutDown ) 30 | game.ShutDown = ShutDown 31 | 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lua/gpm/std/game.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | local gpm = _G.gpm 4 | local engine = gpm.engine 5 | 6 | ---@class gpm.std 7 | local std = gpm.std 8 | 9 | local debug = std.debug 10 | 11 | local glua_engine = _G.engine 12 | local glua_game = _G.game 13 | local glua_util = _G.util 14 | 15 | --- [SHARED AND MENU] 16 | --- 17 | --- The game library. 18 | --- 19 | ---@class gpm.std.game 20 | local game = std.game or {} 21 | std.game = game 22 | 23 | game.getUptime = game.getUptime or _G.SysTime 24 | game.addDebugInfo = game.addDebugInfo or _G.DebugInfo 25 | game.getFrameTime = game.getFrameTime or _G.FrameTime 26 | game.getTickCount = game.getTickCount or glua_engine.TickCount 27 | game.getTickInterval = game.getTickInterval or glua_engine.TickInterval 28 | 29 | do 30 | 31 | --- [SHARED AND MENU] 32 | --- 33 | --- Returns a list of items corresponding to all games from which Garry's Mod supports content mounting. 34 | --- 35 | ---@return gpm.std.game.Item[] items The list of games. 36 | ---@return integer item_count The length of the items array (`#items`). 37 | function game.getAll() 38 | local item_count = engine.game_count 39 | local games = engine.games 40 | local items = {} 41 | 42 | for i = 1, item_count, 1 do 43 | local data = games[ i ] 44 | items[ i ] = { 45 | installed = data.installed, 46 | mounted = data.mounted, 47 | folder = data.folder, 48 | appid = data.depot, 49 | owned = data.owned, 50 | title = data.title 51 | } 52 | end 53 | 54 | return items, item_count 55 | end 56 | 57 | local name2game = engine.name2game 58 | 59 | --- [SHARED AND MENU] 60 | --- 61 | --- Checks whether or not a game is currently mounted. 62 | --- 63 | ---@param folder_name string The folder name of the game. 64 | ---@return boolean is_mounted Returns `true` if the game is mounted, `false` otherwise. 65 | function game.isMounted( folder_name ) 66 | if folder_name == "episodic" or folder_name == "ep2" or folder_name == "lostcoast" then 67 | folder_name = "hl2" 68 | end 69 | 70 | return name2game[ folder_name ] == true 71 | end 72 | 73 | end 74 | 75 | if std.MENU then 76 | 77 | local SetMounted = glua_game.SetMounted 78 | local tostring = std.tostring 79 | 80 | --- [MENU] 81 | --- 82 | --- Mounts or unmounts a game content to the client. 83 | --- 84 | ---@param appID number Steam AppID of the game. 85 | ---@param value boolean `true` to mount, `false` to unmount. 86 | function game.setMount( appID, value ) 87 | SetMounted( tostring( appID ), true ) 88 | end 89 | 90 | end 91 | 92 | do 93 | 94 | local fnName, fn = debug.getupvalue( _G.Material, 1 ) 95 | if fnName ~= "C_Material" then fn = nil end 96 | game.Material = fn 97 | 98 | end 99 | 100 | if std.CLIENT_MENU then 101 | do 102 | 103 | --- [CLIENT AND MENU] 104 | --- 105 | --- The game demo library. 106 | --- 107 | ---@class gpm.std.game.demo 108 | local demo = { 109 | getTotalPlaybackTicks = glua_engine.GetDemoPlaybackTotalTicks, 110 | getPlaybackStartTick = glua_engine.GetDemoPlaybackStartTick, 111 | getPlaybackSpeed = glua_engine.GetDemoPlaybackTimeScale, 112 | getPlaybackTick = glua_engine.GetDemoPlaybackTick, 113 | isRecording = glua_engine.IsRecordingDemo, 114 | isPlaying = glua_engine.IsPlayingDemo 115 | } 116 | 117 | if std.MENU then 118 | demo.getFileDetails = _G.GetDemoFileDetails 119 | end 120 | 121 | game.demo = demo 122 | 123 | end 124 | 125 | do 126 | 127 | local glua_achievements = _G.achievements 128 | local achievements_Count, achievements_GetName, achievements_GetDesc, achievements_GetCount, achievements_GetGoal, achievements_IsAchieved = glua_achievements.Count, glua_achievements.GetName, glua_achievements.GetDesc, glua_achievements.GetCount, glua_achievements.GetGoal, glua_achievements.IsAchieved 129 | 130 | -- TODO: update code/docs 131 | 132 | --- [CLIENT AND MENU] 133 | --- 134 | --- Returns information about an achievement (name, description, value, etc.) 135 | --- 136 | ---@param id number The ID of achievement to retrieve name of. Note IDs start from 0, not 1. 137 | ---@return table | nil table Returns nil if the ID is invalid. 138 | local function get( id ) 139 | local goal = achievements_GetGoal( id ) 140 | if goal == nil then return nil end 141 | 142 | local isAchieved = achievements_IsAchieved( id ) 143 | 144 | return { 145 | name = achievements_GetName( id ), 146 | description = achievements_GetDesc( id ), 147 | value = isAchieved and goal or achievements_GetCount( id ), 148 | achieved = isAchieved, 149 | goal = goal 150 | } 151 | end 152 | 153 | --- [CLIENT AND MENU] 154 | --- 155 | --- The game achievement library. 156 | --- 157 | ---@class gpm.std.game.achievement 158 | local achievement = { 159 | getCount = achievements_Count, 160 | get = get 161 | } 162 | 163 | --- [CLIENT AND MENU] 164 | --- 165 | --- Checks if an achievement with the given ID exists. 166 | --- 167 | ---@param id number 168 | ---@return boolean exist Returns true if the achievement exists. 169 | function achievement.exists( id ) 170 | return achievements_IsAchieved( id ) ~= nil 171 | end 172 | 173 | --- [CLIENT AND MENU] 174 | --- 175 | --- Returns information about all achievements. 176 | --- 177 | ---@return table achievement_list The list of all achievements. 178 | ---@return number achievement_count The count of achievements. 179 | function achievement.getAll() 180 | local tbl, count = {}, achievements_Count() 181 | for index = 0, count - 1 do 182 | tbl[ index + 1 ] = get( index ) 183 | end 184 | 185 | return tbl, count 186 | end 187 | 188 | game.achievement = achievement 189 | 190 | end 191 | 192 | end 193 | 194 | if std.CLIENT then 195 | 196 | game.getTimeoutInfo = _G.GetTimeoutInfo 197 | 198 | end 199 | 200 | if std.SHARED then 201 | 202 | game.getAbsoluteFrameTime = glua_engine.AbsoluteFrameTime 203 | game.isDedicatedServer = glua_game.IsDedicated 204 | game.isSinglePlayer = glua_game.SinglePlayer 205 | game.getDifficulty = glua_game.GetSkillLevel 206 | game.getTimeScale = glua_game.GetTimeScale 207 | game.getRealTime = _G.RealTime 208 | 209 | game.getActivityName = glua_util.GetActivityNameByID 210 | game.getActivityID = glua_util.GetActivityIDByName 211 | 212 | game.getAnimEventName = glua_util.GetAnimEventNameByID 213 | game.getAnimEventID = glua_util.GetAnimEventIDByName 214 | 215 | game.getModelMeshes = glua_util.GetModelMeshes 216 | game.getModelInfo = glua_util.GetModelInfo 217 | 218 | -- TODO: https://wiki.facepunch.com/gmod/Global.PrecacheSentenceFile 219 | -- TODO: https://wiki.facepunch.com/gmod/Global.PrecacheSentenceGroup 220 | 221 | game.precacheModel = glua_util.PrecacheModel 222 | game.precacheSound = glua_util.PrecacheSound 223 | 224 | if std.SERVER then 225 | game.precacheScene = _G.PrecacheScene 226 | end 227 | 228 | game.getFrameNumber = _G.FrameNumber 229 | 230 | end 231 | 232 | if std.SERVER then 233 | 234 | game.setDifficulty = glua_game.SetSkillLevel 235 | game.setTimeScale = glua_game.SetTimeScale 236 | game.exit = glua_engine.CloseServer 237 | game.print = _G.PrintMessage 238 | 239 | end 240 | -------------------------------------------------------------------------------- /lua/gpm/std/gamemode.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm.std 4 | local std = _G.gpm.std 5 | 6 | ---@alias Gamemode gpm.std.Gamemode 7 | ---@class gpm.std.Gamemode : gpm.std.Object 8 | ---@field __class gpm.std.GamemodeClass 9 | ---@field name string 10 | local Gamemode = std.class.base( "Gamemode" ) 11 | 12 | ---@protected 13 | function Gamemode:__init() 14 | 15 | end 16 | 17 | ---@class gpm.std.GamemodeClass : gpm.std.Gamemode 18 | ---@field __base gpm.std.Gamemode 19 | ---@overload fun(): Gamemode 20 | local GamemodeClass = std.class.create( Gamemode ) 21 | std.Gamemode = GamemodeClass 22 | 23 | -- TODO: make gamemode class and gamemode handler 24 | -------------------------------------------------------------------------------- /lua/gpm/std/http.github.lua: -------------------------------------------------------------------------------- 1 | local std = _G.gpm.std 2 | 3 | local game_getUptime = std.game.getUptime 4 | local raw_tonumber = std.raw.tonumber 5 | local tostring = std.tostring 6 | 7 | ---@class gpm.std.http 8 | local http = std.http 9 | 10 | local json_deserialize = std.crypto.json.deserialize 11 | local base64_decode = std.crypto.base64.decode 12 | local string_gsub = std.string.gsub 13 | local http_request = http.request 14 | local futures_sleep = std.sleep 15 | local os_time = std.os.time 16 | 17 | ---@type string 18 | local api_token 19 | do 20 | 21 | local variable = std.console.Variable( { 22 | name = "gpm.github.token", 23 | description = "https://github.com/settings/tokens", 24 | protected = true, 25 | type = "string", 26 | hidden = true 27 | } ) 28 | 29 | variable:attach( function( _, value ) 30 | ---@cast value string 31 | api_token = value 32 | end, "http.github" ) 33 | 34 | ---@diagnostic disable-next-line: cast-local-type 35 | api_token = variable.value 36 | 37 | end 38 | 39 | --- [SHARED AND MENU] 40 | --- 41 | --- The Github API library. 42 | --- 43 | ---@class gpm.std.http.github 44 | local github = http.github or {} 45 | http.github = github 46 | 47 | -- https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting 48 | local next_mutation_time = 0 49 | local ratelimit_reset_time = 0 50 | 51 | --- [SHARED AND MENU] 52 | --- 53 | --- Sends a request to the Github API. 54 | --- 55 | ---@param method gpm.std.http.Request.method The request method. 56 | ---@param pathname string The path to send the request to. 57 | ---@param headers? table The headers to send with the request. 58 | ---@param body? string The body to send with the request. 59 | ---@param do_cache? boolean Whether to cache the response. 60 | ---@return gpm.std.http.Response response The response. 61 | ---@async 62 | local function request( method, pathname, headers, body, do_cache ) 63 | if headers == nil then 64 | headers = {} 65 | end 66 | 67 | if headers.Authorization == nil and api_token ~= "" then 68 | headers.Authorization = "Bearer " .. api_token 69 | end 70 | 71 | if headers.Accept == nil then 72 | headers.Accept = "application/vnd.github+json" 73 | end 74 | 75 | if headers["X-GitHub-Api-Version"] == nil then 76 | headers["X-GitHub-Api-Version"] = "2022-11-28" 77 | end 78 | 79 | local href = "https://api.github.com" .. pathname 80 | 81 | local current_time = os_time() 82 | if ratelimit_reset_time > current_time then 83 | local diff = ratelimit_reset_time - current_time 84 | if diff < 30 then 85 | futures_sleep( diff ) 86 | else 87 | error( "Github API rate limit exceeded (" .. href .. ")" ) 88 | end 89 | end 90 | 91 | -- Rate limit mutative requests 92 | if method > 1 and method < 6 then 93 | local diff = next_mutation_time - game_getUptime() 94 | if diff > 0 then 95 | next_mutation_time = next_mutation_time + 1000 96 | futures_sleep( diff ) 97 | else 98 | next_mutation_time = game_getUptime() + 1000 99 | end 100 | end 101 | 102 | -- i believe there is no reason to implement queue, since requests are queued by the engine 103 | ---@diagnostic disable-next-line: missing-fields 104 | local result = http_request( { 105 | url = href, 106 | method = method, 107 | headers = headers, 108 | body = body, 109 | etag = do_cache ~= false, 110 | cache = do_cache ~= false 111 | } ) 112 | 113 | if ( result.status == 429 or result.status == 403 ) and headers["x-ratelimit-remaining"] == "0" then 114 | local ratelimit_reset = raw_tonumber( headers["x-ratelimit-reset"], 10 ) 115 | if ratelimit_reset == nil then 116 | error( "Github API rate limit exceeded (" .. tostring( result.status ) .. ") (" .. href .. ")" ) 117 | else 118 | ratelimit_reset_time = ratelimit_reset 119 | end 120 | end 121 | 122 | return result 123 | end 124 | 125 | github.request = request 126 | 127 | --- [SHARED AND MENU] 128 | --- 129 | --- Makes a request to the Github API. 130 | --- 131 | ---@param method gpm.std.http.Request.method The request method. 132 | ---@param pathname string The path to send the request to. 133 | ---@param headers? table The headers to send with the request. 134 | ---@param body? string The body to send with the request. 135 | ---@param do_cache? boolean Whether to cache the response. 136 | ---@return table data The data returned from the API. 137 | ---@async 138 | local function apiRequest( method, pathname, headers, body, do_cache ) 139 | local result = request( method, pathname, headers, body, do_cache ) 140 | if not ( result.status >= 200 and result.status < 300 ) then 141 | error( "failed to fetch data from Github API (" .. tostring( result.status ) .. ") (" .. tostring( pathname ) .. ")" ) 142 | end 143 | 144 | local data = json_deserialize( result.body or "" ) 145 | if data == nil then 146 | error( "failed to parse JSON response from Github API (" .. tostring( result.status ) .. ") (" .. tostring( pathname ) .. ")" ) 147 | end 148 | 149 | ---@cast data table 150 | 151 | return data 152 | end 153 | 154 | github.apiRequest = apiRequest 155 | 156 | --- [SHARED AND MENU] 157 | --- 158 | --- Replaces all occurrences of `{name}` in `pathname` with `tbl[name]`. 159 | --- 160 | ---@param pathname string The path to replace placeholders in. 161 | ---@param replaces table The table to replace placeholders with. 162 | ---@return string pathname The path with placeholders replaced. 163 | local function template( pathname, replaces ) 164 | return string_gsub( pathname, "{([%w_-]-)}", function( str ) 165 | return tostring( replaces[ str ] ) 166 | ---@diagnostic disable-next-line: redundant-return-value 167 | end ), nil 168 | end 169 | 170 | github.template = template 171 | 172 | --- [SHARED AND MENU] 173 | --- 174 | --- Replaces all occurrences of `{name}` in `pathname` with `tbl[name]` and makes a request to the Github API. 175 | --- 176 | ---@param method gpm.std.http.Request.method The request method. 177 | ---@param pathname string The path to send the request to. 178 | ---@param replaces table The table to replace placeholders with. 179 | ---@return table data The data returned from the API. 180 | ---@async 181 | local function templateRequest( method, pathname, replaces ) 182 | return apiRequest( method, template( pathname, replaces ) ) 183 | end 184 | 185 | github.templateRequest = templateRequest 186 | 187 | --- [SHARED AND MENU] 188 | --- 189 | --- Fetches a list of all github emojis. 190 | --- 191 | ---@return table data The list of emojis. 192 | ---@async 193 | function github.getEmojis() 194 | return apiRequest( "GET", "/emojis" ) 195 | end 196 | 197 | --- [SHARED AND MENU] 198 | --- 199 | --- Fetches a list of all github licenses. 200 | --- 201 | ---@return gpm.std.http.github.License[] 202 | ---@async 203 | function github.getLicenses() 204 | return apiRequest( "GET", "/licenses" ) 205 | end 206 | 207 | --- [SHARED AND MENU] 208 | --- 209 | --- Fetches a list of all repositories owned by an organization. 210 | --- 211 | ---@param organization string The name of the organization. 212 | ---@return gpm.std.http.github.Repository[] repos The list of repositories. 213 | ---@async 214 | function github.getRepositories( organization ) 215 | return templateRequest( "GET", "/orgs/{org}/repos", { 216 | org = organization 217 | } ) 218 | end 219 | 220 | --- [SHARED AND MENU] 221 | --- 222 | --- Fetches a detailed information about a specific repository. 223 | --- 224 | ---@param owner string The owner of the repository. 225 | ---@param repo string The name of the repository. 226 | ---@return gpm.std.http.github.Repository repo The repository. 227 | ---@async 228 | function github.getRepository( owner, repo ) 229 | return templateRequest( "GET", "/repos/{owner}/{repo}", { 230 | owner = owner, 231 | repo = repo 232 | } ) 233 | end 234 | 235 | --- [SHARED AND MENU] 236 | --- 237 | --- Fetches a lists the tags (versions) of a repository. 238 | --- 239 | ---@param owner string The owner of the repository. 240 | ---@param repo string The name of the repository. 241 | ---@param page? integer The page number, default value is `1`. 242 | ---@return gpm.std.http.github.Repository.Tag[] tags The list of tags. 243 | ---@async 244 | function github.getRepositoryTags( owner, repo, page ) 245 | return templateRequest( "GET", "/repos/{owner}/{repo}/tags?per_page=100&page={page}", { 246 | owner = owner, 247 | repo = repo, 248 | page = page or 1 249 | } ) 250 | end 251 | 252 | --- [SHARED AND MENU] 253 | --- 254 | --- Fetches a single Git tree — that is, a snapshot of the repository's file structure at a specific commit or tree. 255 | --- 256 | ---@param owner string The account owner of the repository. The name is not case sensitive. 257 | ---@param repo string The name of the repository without the .git extension. The name is not case sensitive. 258 | ---@param tree_sha string The SHA1 value or ref (branch or tag) name of the tree. 259 | ---@param recursive? boolean Setting this parameter to any value returns the objects or subtrees referenced by the tree specified in :tree_sha. 260 | ---@return gpm.std.http.github.Tree tree The tree. 261 | ---@async 262 | function github.getTree( owner, repo, tree_sha, recursive ) 263 | return templateRequest( "GET", "/repos/{owner}/{repo}/git/trees/{tree_sha}?recursive={recursive}", { 264 | owner = owner, 265 | repo = repo, 266 | tree_sha = tree_sha, 267 | recursive = recursive == true 268 | } ) 269 | end 270 | 271 | --- [SHARED AND MENU] 272 | --- 273 | --- Fetches the content of a blob (a blob = file contents) in a repository, using its SHA-1. 274 | --- 275 | ---@param owner string The owner of the repository. 276 | ---@param repo string The name of the repository. 277 | ---@param file_sha string The SHA1 value of the blob. 278 | ---@return gpm.std.http.github.Blob blob The blob. 279 | ---@async 280 | function github.getBlob( owner, repo, file_sha ) 281 | local result = templateRequest( "GET", "/repos/{owner}/{repo}/git/blobs/{file_sha}", { 282 | owner = owner, 283 | repo = repo, 284 | file_sha = file_sha 285 | } ) 286 | 287 | if result.encoding == "base64" then 288 | result.content = base64_decode( result.content ) 289 | result.encoding = "raw" 290 | end 291 | 292 | return result 293 | end 294 | 295 | --- [SHARED AND MENU] 296 | --- 297 | --- Fetches a list of repository contributors. 298 | --- 299 | ---@param owner string The owner of the repository. 300 | ---@param repo string The name of the repository. 301 | ---@return gpm.std.http.github.Contributor[] contributors The list of contributors. 302 | ---@async 303 | function github.getContributors( owner, repo ) 304 | return templateRequest( "GET", "/repos/{owner}/{repo}/contributors", { 305 | owner = owner, 306 | repo = repo 307 | } ) 308 | end 309 | 310 | --- [SHARED AND MENU] 311 | --- 312 | --- Fetches a list of repository languages. 313 | --- 314 | ---@param owner string The owner of the repository. 315 | ---@param repo string The name of the repository. 316 | ---@return table languages The languages. 317 | ---@async 318 | function github.getLanguages( owner, repo ) 319 | return templateRequest( "GET", "/repos/{owner}/{repo}/languages", { 320 | owner = owner, 321 | repo = repo 322 | } ) 323 | end 324 | 325 | --- [SHARED AND MENU] 326 | --- 327 | --- Fetches the repository contents as a ZIP archive based on a specific reference (branch name, tag name, or commit sha-1). 328 | --- 329 | ---@param owner string The owner of the repository. 330 | ---@param repo string The name of the repository. 331 | ---@param ref string The branch name, tag name, or commit sha-1. 332 | ---@return string content The body of the zipball. 333 | ---@async 334 | function github.fetchZip( owner, repo, ref ) 335 | local result = templateRequest( "GET", "/repos/{owner}/{repo}/zipball/{ref}", { 336 | owner = owner, 337 | repo = repo, 338 | ref = ref 339 | } ) 340 | 341 | if result.status ~= 200 then 342 | error( "failed to fetch zipball (" .. tostring( owner ) .. "/" .. tostring( repo ) .. "/" .. tostring( ref ) .. ") from Github API (" .. tostring( result.status ) .. ")" ) 343 | end 344 | 345 | return result.body 346 | end 347 | -------------------------------------------------------------------------------- /lua/gpm/std/level.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local glua_game, glua_engine, glua_util = _G.game, _G.engine, _G.util 3 | local file_Exists = _G.file.Exists 4 | local gpm = _G.gpm 5 | 6 | ---@class gpm.std 7 | local std = gpm.std 8 | 9 | local console = std.console 10 | local console_Variable = console.Variable 11 | 12 | --- [SHARED AND MENU] 13 | --- 14 | --- The game-level library. 15 | --- 16 | --- BSP: `maps/{name}.bsp` 17 | --- AI navigation: `maps/{name}.ain` 18 | --- Navigation Mesh: `maps/{name}.nav` 19 | --- 20 | ---@class gpm.std.level 21 | ---@field name string The name of the current loaded level. 22 | local level = std.level or {} 23 | std.level = level 24 | 25 | -- TODO: rewrite this library 26 | 27 | do 28 | 29 | local game_GetMap = level.getName or glua_game.GetMap 30 | level.getName = game_GetMap 31 | 32 | setmetatable( level, { 33 | __index = function( _, key ) 34 | if key ~= "name" then return end 35 | 36 | local map = game_GetMap() 37 | if map == nil or map == "" then 38 | return "unknown" 39 | end 40 | 41 | level.name = map 42 | return map 43 | end 44 | } ) 45 | 46 | end 47 | 48 | 49 | if std.CLIENT then 50 | level.getSunInfo = level.getSunInfo or glua_util.GetSunInfo 51 | level.redownloadLightmaps = level.redownloadLightmaps or _G.render.RedownloadAllLightmaps 52 | end 53 | 54 | --- [SHARED AND MENU] 55 | --- 56 | --- Checks if a map/level exists. 57 | --- 58 | ---@param name string The map/level name in maps/*.bsp folder. 59 | ---@param gamePath? string An optional argument specifying the search location, as an example for addon searches. 60 | ---@return boolean 61 | local function exists( name, gamePath ) 62 | return file_Exists( "maps/" .. name .. ".bsp", gamePath or "GAME" ) 63 | end 64 | 65 | level.exists = exists 66 | 67 | if std.SHARED then 68 | 69 | level.getEntity = level.getEntity or glua_game.GetWorld 70 | 71 | level.cleanup = level.cleanup or glua_game.CleanUpMap 72 | level.cleanupRagdolls = level.cleanupRagdolls or glua_game.RemoveRagdolls -- idk what to do with this 73 | 74 | level.traceLine = level.traceLine or glua_util.TraceLine 75 | level.traceHull = level.traceHull or glua_util.TraceHull 76 | level.traceEntity = level.traceEntity or glua_util.TraceEntityHull 77 | 78 | level.getStartSpot = level.getStartSpot or glua_game.StartSpot 79 | level.getContents = level.getContents or glua_util.PointContents 80 | 81 | --- [SHARED AND MENU] 82 | --- 83 | --- Returns the gravity of the current level. 84 | --- 85 | ---@return integer gravity The gravity of the current level. 86 | function level.getGravity() 87 | return console_Variable.getNumber( "sv_gravity" ) 88 | end 89 | 90 | end 91 | 92 | if std.SERVER then 93 | 94 | level.changeLightStyle = level.changeLightStyle or glua_engine.LightStyle 95 | level.getCounter = level.getCounter or glua_game.GetGlobalCounter 96 | level.setCounter = level.setCounter or glua_game.SetGlobalCounter 97 | level.getVersion = level.getVersion or glua_game.GetMapVersion 98 | level.getLoadType = level.getLoadType or glua_game.MapLoadType 99 | level.getState = level.getState or glua_game.GetGlobalState 100 | level.setState = level.setState or glua_game.SetGlobalState 101 | level.sendCommand = level.sendCommand or _G.hammer.SendCommand 102 | level.getNext = level.getNext or glua_game.GetMapNext 103 | 104 | do 105 | 106 | local glua_navmesh = _G.navmesh 107 | 108 | -- T0D0: make a classes 109 | local navmesh = { 110 | getEditCursorPosition = glua_navmesh.GetEditCursorPosition, 111 | getPlayerSpawnName = glua_navmesh.GetPlayerSpawnName, 112 | setPlayerSpawnName = glua_navmesh.SetPlayerSpawnName, 113 | clearWalkableSeeds = glua_navmesh.ClearWalkableSeeds, 114 | addWalkableSeed = glua_navmesh.AddWalkableSeed, 115 | getGroundHeight = glua_navmesh.GetGroundHeight, 116 | isGenerating = glua_navmesh.IsGenerating, 117 | generate = glua_navmesh.BeginGeneration, 118 | isLoaded = glua_navmesh.IsLoaded, 119 | reset = glua_navmesh.Reset, 120 | load = glua_navmesh.Load, 121 | save = glua_navmesh.Save, 122 | area = std.setmetatable( { 123 | getNearest = glua_navmesh.GetNearestNavArea, 124 | getBlocked = glua_navmesh.GetBlockedAreas, 125 | getByIndex = glua_navmesh.GetNavAreaByID, 126 | getByPosition = glua_navmesh.GetNavArea, 127 | getCount = glua_navmesh.GetNavAreaCount, 128 | getMarked = glua_navmesh.GetMarkedArea, 129 | setMarked = glua_navmesh.SetMarkedArea, 130 | getAll = glua_navmesh.GetAllNavAreas, 131 | create = glua_navmesh.CreateNavArea, 132 | findInBox = glua_navmesh.FindInBox, 133 | find = glua_navmesh.Find, 134 | }, { 135 | __index = std.debug.findmetatable( "CNavArea" ) 136 | } ), 137 | ladder = std.setmetatable( { 138 | getByIndex = glua_navmesh.GetNavLadderByID, 139 | getMarked = glua_navmesh.GetMarkedLadder, 140 | setMarked = glua_navmesh.SetMarkedLadder, 141 | create = glua_navmesh.CreateNavLadder, 142 | }, { 143 | __index = std.debug.findmetatable( "CNavLadder" ) 144 | } ) 145 | } 146 | 147 | --- [SHARED AND MENU] 148 | --- 149 | --- Checks if a map/level navmesh exists. 150 | ---@param name string The map/level name in maps/*.nav folder. 151 | ---@param gamePath? string An optional argument specifying the search location, as an example for addon searches. 152 | ---@return boolean 153 | function navmesh.exists( name, gamePath ) 154 | return file_Exists( "maps/" .. name .. ".nav", gamePath or "GAME" ) 155 | end 156 | 157 | level.navmesh = navmesh 158 | 159 | end 160 | 161 | do 162 | 163 | local command_run = console.Command.run 164 | 165 | --- [SHARED AND MENU] 166 | --- 167 | --- It will end the current game, load the specified map and start a new game on it. Players are not kicked from the server. 168 | ---@param name string 169 | ---@return boolean isSuccessful 170 | function level.change( name ) 171 | if exists( name ) then 172 | command_run( "changelevel", name ) 173 | return true 174 | else 175 | return false 176 | end 177 | end 178 | 179 | end 180 | 181 | --- [SHARED AND MENU] 182 | --- 183 | --- Sets the gravity of the current level. 184 | ---@param value integer The value to set. Default: 600 185 | function level.setGravity( value ) 186 | console_Variable.set( "sv_gravity", value ) 187 | end 188 | 189 | end 190 | 191 | if std.MENU then 192 | 193 | --- [SHARED AND MENU] 194 | --- 195 | ---@class gpm.std.level.save 196 | local save = {} 197 | level.save = save 198 | 199 | if std.MENU then 200 | save.getFileDetails = _G.GetSaveFileDetails 201 | end 202 | 203 | -- TODO: https://wiki.facepunch.com/gmod/engine.WriteSave 204 | 205 | end 206 | -------------------------------------------------------------------------------- /lua/gpm/std/math.ease.lua: -------------------------------------------------------------------------------- 1 | ---@class gpm.std.math 2 | local math = _G.gpm.std.math 3 | 4 | -- Source code of functions 5 | -- https://github.com/Facepunch/garrysmod/pull/1755 6 | -- https://web.archive.org/web/20201212082306/https://easings.net/ 7 | -- https://web.archive.org/web/20201218211606/https://raw.githubusercontent.com/ai/easings.net/master/src/easings.yml 8 | 9 | local math_pi = math.pi 10 | local math_cos = math.cos 11 | local math_sin = math.sin 12 | local math_sqrt = math.sqrt 13 | 14 | local c1 = 1.70158 15 | local c3 = c1 + 1 16 | local c2 = c1 * 1.525 17 | local c4 = ( 2 * math_pi ) / 3 18 | local c5 = ( 2 * math_pi ) / 4.5 19 | local n1 = 7.5625 20 | local d1 = 2.75 21 | 22 | --- [SHARED AND MENU] 23 | --- 24 | --- The math easing functions. 25 | --- 26 | ---@class gpm.std.math.ease 27 | local ease = math.ease or {} 28 | math.ease = ease 29 | 30 | --- [SHARED AND MENU] 31 | --- 32 | --- Eases in using `math.sin`. 33 | --- 34 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 35 | ---@return number eased The eased number. 36 | function ease.sineIn( fraction ) 37 | return 1 - math_cos( ( fraction * math_pi ) * 0.5 ) 38 | end 39 | 40 | --- [SHARED AND MENU] 41 | --- 42 | --- Eases out using `math.sin`. 43 | --- 44 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 45 | ---@return number eased The eased number. 46 | function ease.sineOut( fraction ) 47 | return math_sin( ( fraction * math_pi ) * 0.5 ) 48 | end 49 | 50 | --- [SHARED AND MENU] 51 | --- 52 | --- Eases in and out using `math.sin`. 53 | --- 54 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 55 | ---@return number eased The eased number. 56 | function ease.sineInOut( fraction ) 57 | return -( math_cos( math_pi * fraction ) - 1 ) * 0.5 58 | end 59 | 60 | --- [SHARED AND MENU] 61 | --- 62 | --- Eases in by squaring the fraction. 63 | --- 64 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 65 | ---@return number eased The eased number. 66 | function ease.quadIn( fraction ) 67 | return fraction ^ 2 68 | end 69 | 70 | --- [SHARED AND MENU] 71 | --- 72 | --- Eases out by squaring the fraction. 73 | --- 74 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 75 | ---@return number eased The eased number. 76 | function ease.quadOut( fraction ) 77 | return 1 - ( 1 - fraction ) * ( 1 - fraction ) 78 | end 79 | 80 | --- [SHARED AND MENU] 81 | --- 82 | --- Eases in and out by squaring the fraction. 83 | --- 84 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 85 | ---@return number eased The eased number. 86 | function ease.quadInOut( fraction ) 87 | return fraction < 0.5 and 2 * fraction ^ 2 88 | or 1 - ( ( -2 * fraction + 2 ) ^ 2 ) * 0.5 89 | end 90 | 91 | --- [SHARED AND MENU] 92 | --- 93 | --- Eases in by cubing the fraction. 94 | --- 95 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 96 | ---@return number eased The eased number. 97 | function ease.cubicIn( fraction ) 98 | return fraction ^ 3 99 | end 100 | 101 | --- [SHARED AND MENU] 102 | --- 103 | --- Eases out by cubing the fraction. 104 | --- 105 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 106 | ---@return number eased The eased number. 107 | function ease.cubicOut( fraction ) 108 | return 1 - ( ( 1 - fraction ) ^ 3 ) 109 | end 110 | 111 | --- [SHARED AND MENU] 112 | --- 113 | --- Eases in and out by cubing the fraction. 114 | --- 115 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 116 | ---@return number eased The eased number. 117 | function ease.cubicInOut( fraction ) 118 | return fraction < 0.5 and 4 * fraction ^ 3 119 | or 1 - ( ( -2 * fraction + 2 ) ^ 3 ) * 0.5 120 | end 121 | 122 | --- [SHARED AND MENU] 123 | --- 124 | --- Eases in by raising the fraction to the power of 4. 125 | --- 126 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 127 | ---@return number eased The eased number. 128 | function ease.quartIn( fraction ) 129 | return fraction ^ 4 130 | end 131 | 132 | --- [SHARED AND MENU] 133 | --- 134 | --- Eases out by raising the fraction to the power of 4. 135 | --- 136 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 137 | ---@return number eased The eased number. 138 | function ease.quartOut( fraction ) 139 | return 1 - ( ( 1 - fraction ) ^ 4 ) 140 | end 141 | 142 | --- [SHARED AND MENU] 143 | --- 144 | --- Eases in and out by raising the fraction to the power of 4. 145 | --- 146 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 147 | ---@return number eased The eased number. 148 | function ease.quartInOut( fraction ) 149 | return fraction < 0.5 and 8 * fraction ^ 4 150 | or 1 - ( ( -2 * fraction + 2 ) ^ 4 ) * 0.5 151 | end 152 | 153 | --- [SHARED AND MENU] 154 | --- 155 | --- Eases in by raising the fraction to the power of 5. 156 | --- 157 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 158 | ---@return number eased The eased number. 159 | function ease.quintIn( fraction ) 160 | return fraction ^ 5 161 | end 162 | 163 | --- [SHARED AND MENU] 164 | --- 165 | --- Eases out by raising the fraction to the power of 5. 166 | --- 167 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 168 | ---@return number eased The eased number. 169 | function ease.quintOut( fraction ) 170 | return 1 - ( ( 1 - fraction ) ^ 5 ) 171 | end 172 | 173 | --- [SHARED AND MENU] 174 | --- 175 | --- Eases in and out by raising the fraction to the power of 5. 176 | --- 177 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 178 | ---@return number eased The eased number. 179 | function ease.quintInOut( fraction ) 180 | return fraction < 0.5 and 16 * fraction ^ 5 181 | or 1 - ( ( -2 * fraction + 2 ) ^ 5 ) * 0.5 182 | end 183 | 184 | --- [SHARED AND MENU] 185 | --- 186 | --- Eases in using an exponential equation with a base of 2 and where the fraction is used in the exponent. 187 | --- 188 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 189 | ---@return number eased The eased number. 190 | function ease.expoIn( fraction ) 191 | return fraction == 0 and 0 or ( 2 ^ ( 10 * fraction - 10 ) ) 192 | end 193 | 194 | --- [SHARED AND MENU] 195 | --- 196 | --- Eases out using an exponential equation with a base of 2 and where the fraction is used in the exponent. 197 | --- 198 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 199 | ---@return number eased The eased number. 200 | function ease.expoOut( fraction ) 201 | return fraction == 1 and 1 or 1 - ( 2 ^ ( -10 * fraction ) ) 202 | end 203 | 204 | --- [SHARED AND MENU] 205 | --- 206 | --- Eases in and out using an exponential equation with a base of 2 and where the fraction is used in the exponent. 207 | --- 208 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 209 | ---@return number eased The eased number. 210 | function ease.expoInOut( fraction ) 211 | return fraction == 0 and 0 212 | or fraction == 1 and 1 213 | or fraction < 0.5 and ( 2 ^ ( 20 * fraction - 10 ) ) * 0.5 or ( 2 - ( 2 ^ ( -20 * fraction + 10 ) ) ) * 0.5 214 | end 215 | 216 | --- [SHARED AND MENU] 217 | --- 218 | --- Eases in using a circular function. 219 | --- 220 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 221 | ---@return number eased The eased number. 222 | function ease.circIn( fraction ) 223 | return 1 - math_sqrt( 1 - ( fraction ^ 2 ) ) 224 | end 225 | 226 | --- [SHARED AND MENU] 227 | --- 228 | --- Eases out using a circular function. 229 | --- 230 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 231 | ---@return number eased The eased number. 232 | function ease.circOut( fraction ) 233 | return math_sqrt( 1 - ( ( fraction - 1 ) ^ 2 ) ) 234 | end 235 | 236 | --- [SHARED AND MENU] 237 | --- 238 | --- Eases in and out using a circular function. 239 | --- 240 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 241 | ---@return number eased The eased number. 242 | function ease.circInOut( fraction ) 243 | return fraction < 0.5 and ( 1 - math_sqrt( 1 - ( ( 2 * fraction ) ^ 2 ) ) ) * 0.5 244 | or ( math_sqrt( 1 - ( ( -2 * fraction + 2 ) ^ 2 ) ) + 1 ) * 0.5 245 | end 246 | 247 | --- [SHARED AND MENU] 248 | --- 249 | --- Eases in by reversing the direction of the ease slightly before returning. 250 | --- 251 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 252 | ---@return number eased The eased number. 253 | function ease.backIn( fraction ) 254 | return c3 * fraction ^ 3 - c1 * fraction ^ 2 255 | end 256 | 257 | --- [SHARED AND MENU] 258 | --- 259 | --- Eases out by reversing the direction of the ease slightly before finishing. 260 | --- 261 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 262 | ---@return number eased The eased number. 263 | function ease.backOut( fraction ) 264 | return 1 + c3 * ( ( fraction - 1 ) ^ 3 ) + c1 * ( ( fraction - 1 ) ^ 2 ) 265 | end 266 | 267 | --- [SHARED AND MENU] 268 | --- 269 | --- Eases in and out by reversing the direction of the ease slightly before returning on both ends. 270 | --- 271 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 272 | ---@return number eased The eased number. 273 | function ease.backInOut( fraction ) 274 | return fraction < 0.5 and ( ( ( 2 * fraction ) ^ 2 ) * ( ( c2 + 1 ) * 2 * fraction - c2 ) ) * 0.5 275 | or ( ( ( 2 * fraction - 2 ) ^ 2 ) * ( ( c2 + 1 ) * ( fraction * 2 - 2 ) + c2 ) + 2 ) * 0.5 276 | end 277 | 278 | --- [SHARED AND MENU] 279 | --- 280 | --- Eases in like a rubber band. 281 | --- 282 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 283 | ---@return number eased The eased number. 284 | function ease.elasticIn( fraction ) 285 | return fraction == 0 and 0 286 | or fraction == 1 and 1 287 | or -( 2 ^ ( 10 * fraction - 10 ) ) * math_sin( ( fraction * 10 - 10.75 ) * c4 ) 288 | end 289 | 290 | --- [SHARED AND MENU] 291 | --- 292 | --- Eases out like a rubber band. 293 | --- 294 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 295 | ---@return number eased The eased number. 296 | function ease.elasticOut( fraction ) 297 | return fraction == 0 and 0 or fraction == 1 and 1 298 | or ( 2 ^ ( -10 * fraction ) ) * math_sin( ( fraction * 10 - 0.75 ) * c4 ) + 1 299 | end 300 | 301 | --- [SHARED AND MENU] 302 | --- 303 | --- Eases in and out like a rubber band. 304 | --- 305 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 306 | ---@return number eased The eased number. 307 | function ease.elasticInOut( fraction ) 308 | return fraction == 0 and 0 or fraction == 1 and 1 309 | or fraction < 0.5 and -( ( 2 ^ ( 20 * fraction - 10 ) ) * math_sin( ( 20 * fraction - 11.125 ) * c5 ) ) * 0.5 310 | or ( ( 2 ^ ( -20 * fraction + 10 ) ) * math_sin( ( 20 * fraction - 11.125 ) * c5 ) ) * 0.5 + 1 311 | end 312 | 313 | --- [SHARED AND MENU] 314 | --- 315 | --- Eases out like a bouncy ball. 316 | --- 317 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 318 | ---@return number eased The eased number. 319 | local function ease_bounceOut( fraction ) 320 | if ( fraction < 1 / d1 ) then 321 | return n1 * fraction ^ 2 322 | elseif ( fraction < 2 / d1 ) then 323 | fraction = fraction - ( 1.5 / d1 ) 324 | return n1 * fraction ^ 2 + 0.75 325 | elseif ( fraction < 2.5 / d1 ) then 326 | fraction = fraction - ( 2.25 / d1 ) 327 | return n1 * fraction ^ 2 + 0.9375 328 | else 329 | fraction = fraction - ( 2.625 / d1 ) 330 | return n1 * fraction ^ 2 + 0.984375 331 | end 332 | end 333 | 334 | --- [SHARED AND MENU] 335 | --- 336 | --- Eases in like a bouncy ball. 337 | --- 338 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 339 | ---@return number eased The eased number. 340 | function ease.bounceIn( fraction ) 341 | return 1 - ease_bounceOut( 1 - fraction ) 342 | end 343 | 344 | ease.bounceOut = ease_bounceOut 345 | 346 | --- [SHARED AND MENU] 347 | --- 348 | --- Eases in and out like a bouncy ball. 349 | --- 350 | ---@param fraction number Fraction of the progress to ease, from 0 to 1. 351 | ---@return number eased The eased number. 352 | function ease.bounceInOut( fraction ) 353 | return fraction < 0.5 and ( 1 - ease_bounceOut( 1 - 2 * fraction ) ) * 0.5 354 | or ( 1 + ease_bounceOut( 2 * fraction - 1 ) ) * 0.5 355 | end 356 | 357 | return ease 358 | -------------------------------------------------------------------------------- /lua/gpm/std/matproxy.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm.std.matproxy 4 | local matproxy = {} 5 | 6 | 7 | -- TODO: https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/includes/modules/matproxy.lua 8 | 9 | 10 | 11 | return matproxy 12 | -------------------------------------------------------------------------------- /lua/gpm/std/os.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@type oslib 4 | local glua_os = _G.os 5 | 6 | ---@class gpm.std 7 | local std = _G.gpm.std 8 | 9 | --- [SHARED AND MENU] 10 | --- 11 | --- Library for interacting with the operating system. 12 | --- 13 | ---@class gpm.std.os : oslib 14 | ---@field name string The name of the operating system. 15 | ---@field arch string The architecture of the operating system. 16 | ---@field endianness boolean `true` if the operating system is big endianness, `false` if not. 17 | local os = std.os or {} 18 | std.os = os 19 | 20 | do 21 | 22 | local jit = std.jit 23 | local jit_os = jit.os 24 | 25 | os.name = jit_os 26 | os.arch = jit.arch 27 | 28 | std.OSX = jit_os == "OSX" 29 | std.LINUX = jit_os == "Linux" 30 | std.WINDOWS = jit_os == "Windows" 31 | 32 | end 33 | 34 | if glua_os == nil then 35 | error( "os library not found, yep it's over." ) 36 | end 37 | 38 | os.date = os.date or glua_os.date 39 | os.time = os.time or glua_os.time 40 | os.clock = os.clock or glua_os.clock 41 | os.difftime = os.difftime or glua_os.difftime 42 | 43 | os.endianness = os.endianness or std.string.byte( std.string.dump( std.debug.fempty ), 7 ) == 0x00 44 | 45 | if std.MENU then 46 | os.openFolder = os.openFolder or _G.OpenFolder or std.debug.fempty 47 | end 48 | -------------------------------------------------------------------------------- /lua/gpm/std/os.window.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local gpm = _G.gpm 3 | local std = gpm.std 4 | 5 | ---@class gpm.std.os 6 | local os = std.os 7 | 8 | --- [CLIENT AND MENU] 9 | --- 10 | --- The game's os window library. 11 | --- 12 | ---@class gpm.std.os.window 13 | ---@field width number The width of the game's window (in pixels). 14 | ---@field height number The height of the game's window (in pixels). 15 | ---@field focus boolean `true` if the game's window has focus, `false` otherwise. 16 | local window = os.window or { focus = true } 17 | os.window = window 18 | 19 | local width, height = _G.ScrW(), _G.ScrH() 20 | window.width, window.height = width, height 21 | 22 | if window.SizeChanged == nil then 23 | 24 | local SizeChanged = std.Hook( "os.window.SizeChanged" ) 25 | window.SizeChanged = SizeChanged 26 | 27 | gpm.engine.hookCatch( "OnScreenSizeChanged", function( old_width, old_height, new_width, new_height ) 28 | width, height = new_width, new_height 29 | window.width, window.height = new_width, new_height 30 | SizeChanged( new_width, new_height, old_width, old_height ) 31 | end ) 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lua/gpm/std/panel.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm.std 4 | local std = _G.gpm.std 5 | 6 | local glua_vgui = _G.vgui 7 | 8 | --[[ 9 | 10 | https://wiki.facepunch.com/gmod/Global.DisableClipping 11 | 12 | https://wiki.facepunch.com/gmod/vgui 13 | 14 | ]] 15 | 16 | ---@class gpm.std.Panel : gpm.std.Object 17 | ---@field __class gpm.std.PanelClass 18 | local Panel = std.class.base( "Panel" ) 19 | 20 | ---@diagnostic disable-next-line: duplicate-doc-alias 21 | ---@alias Panel gpm.std.Panel 22 | 23 | ---@protected 24 | function Panel:__init() 25 | 26 | -- TODO 27 | 28 | end 29 | 30 | ---@class gpm.std.PanelClass : gpm.std.Panel 31 | ---@field __base gpm.std.Panel 32 | ---@overload fun(): Panel 33 | local PanelClass = std.class.create( Panel ) 34 | std.Panel = PanelClass 35 | 36 | local transducers = gpm.transducers 37 | 38 | do 39 | 40 | local vgui_GetWorldPanel = glua_vgui.GetWorldPanel 41 | 42 | function PanelClass:getMain() 43 | return transducers[ vgui_GetWorldPanel() ] 44 | end 45 | 46 | end 47 | 48 | do 49 | 50 | local vgui_GetHoveredPanel = glua_vgui.GetHoveredPanel 51 | 52 | function PanelClass:getHovered() 53 | return transducers[ vgui_GetHoveredPanel() ] 54 | end 55 | 56 | end 57 | 58 | if std.CLIENT then 59 | 60 | local GetHUDPanel = _G.GetHUDPanel 61 | 62 | function PanelClass:getHUD() 63 | return transducers[ GetHUDPanel() ] 64 | end 65 | 66 | end 67 | 68 | if std.MENU then 69 | 70 | local GetOverlayPanel = _G.GetOverlayPanel 71 | 72 | function PanelClass:getOverlay() 73 | return transducers[ GetOverlayPanel() ] 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lua/gpm/std/physics.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local std = _G.gpm.std 3 | local isstring = std.isstring 4 | local setmetatable = std.setmetatable 5 | local physenv, util = _G.physenv, _G.util 6 | 7 | ---@class gpm.std.physics 8 | local physics = { 9 | getSimulationDuration = physenv.GetLastSimulationTime 10 | } 11 | 12 | do 13 | 14 | ---@class gpm.std.physics.collide 15 | local collide = { 16 | createFromModel = _G.CreatePhysCollidesFromModel, 17 | createBox = _G.CreatePhysCollideBox, 18 | } 19 | 20 | setmetatable( collide, { __index = std.debug.findmetatable( "PhysCollide" ) } ) 21 | 22 | physics.collide = collide 23 | 24 | end 25 | 26 | ---@type PhysObj 27 | ---@diagnostic disable-next-line: assign-type-mismatch 28 | physics.object = setmetatable( {}, { __index = std.debug.findmetatable( "PhysObj" ) } ) 29 | 30 | do 31 | 32 | ---@class gpm.std.physics.surface 33 | local surface = { 34 | getID = util.GetSurfaceIndex, 35 | getData = util.GetSurfaceData, 36 | getName = util.GetSurfacePropName 37 | } 38 | 39 | local physenv_AddSurfaceData = physenv.AddSurfaceData 40 | local table_concat = std.table.concat 41 | local tostring = std.tostring 42 | 43 | local MAT = std.MAT 44 | local mat2name = { 45 | [ MAT.A ] = "A", 46 | [ MAT.B ] = "B", 47 | [ MAT.C ] = "C", 48 | [ MAT.D ] = "D", 49 | [ MAT.E ] = "E", 50 | [ MAT.F ] = "F", 51 | [ MAT.G ] = "G", 52 | [ MAT.H ] = "H", 53 | [ MAT.I ] = "I", 54 | [ MAT.L ] = "L", 55 | [ MAT.M ] = "M", 56 | [ MAT.N ] = "N", 57 | [ MAT.O ] = "O", 58 | [ MAT.P ] = "P", 59 | [ MAT.S ] = "S", 60 | [ MAT.T ] = "T", 61 | [ MAT.V ] = "V", 62 | [ MAT.W ] = "W", 63 | [ MAT.Y ] = "Y" 64 | } 65 | 66 | -- https://wiki.facepunch.com/gmod/Structures/SurfacePropertyData 67 | local garry2key = { 68 | hardnessFactor = "audiohardnessfactor", 69 | hardThreshold = "impactHardThreshold", 70 | hardVelocityThreshold = "audioHardMinVelocity", 71 | reflectivity = "audioreflectivity", 72 | roughnessFactor = "audioroughnessfactor", 73 | roughThreshold = "scrapeRoughThreshold", 74 | jumpFactor = "jumpfactor", 75 | maxSpeedFactor = "maxspeedfactor", 76 | breakSound = "break", 77 | bulletImpactSound = "bulletimpact", 78 | impactHardSound = "impacthard", 79 | impactSoftSound = "impactsoft", 80 | rollingSound = "roll", 81 | scrapeRoughSound = "scraperough", 82 | scrapeSmoothSound = "scrapesmooth", 83 | stepLeftSound = "stepleft", 84 | stepRightSound = "stepright", 85 | strainSound = "strain" 86 | } 87 | 88 | --- Adds surface properties to the game's physics environment. 89 | ---@param data SurfacePropertyData | table The surface data to be added. 90 | function surface.add( data ) 91 | local buffer, length = {}, 0 92 | 93 | for key, value in pairs( data ) do 94 | key = tostring( key ) 95 | 96 | if key ~= "name" then 97 | value = tostring( value ) 98 | 99 | if key == "material" then 100 | key, value = "gamematerial", mat2name[ value ] or value 101 | end 102 | 103 | length = length + 1 104 | buffer[ length ] = "\"" 105 | 106 | length = length + 1 107 | buffer[ length ] = garry2key[ key ] or key 108 | 109 | length = length + 1 110 | buffer[ length ] = "\"\t\"" 111 | 112 | length = length + 1 113 | buffer[ length ] = value 114 | 115 | length = length + 1 116 | buffer[ length ] = "\"\n" 117 | end 118 | end 119 | 120 | if length == 0 then 121 | error( "Invalid surface data", 2 ) 122 | else 123 | local name = data.name 124 | if isstring( name ) then 125 | physenv_AddSurfaceData( "\"" .. name .. "\"\n{\n" .. table_concat( buffer, "", 1, length ) .. "}" ) 126 | else 127 | error( "Invalid surface name", 2 ) 128 | end 129 | end 130 | end 131 | 132 | physics.surface = surface 133 | 134 | end 135 | 136 | do 137 | 138 | ---@class gpm.std.physics.settings 139 | ---@field max_ovw_time number Maximum amount of seconds to precalculate collisions with world. ( Default: 1 ) 140 | ---@field max_ovo_time number Maximum amount of seconds to precalculate collisions with objects. ( Default: 0.5 ) 141 | ---@field max_collisions_per_tick number Maximum collision checks per tick. 142 | --- Objects may penetrate after this many collision checks. ( Default: 50000 ) 143 | ---@field max_object_collisions_pre_tick number Maximum collision per object per tick. 144 | --- Object will be frozen after this many collisions (visual hitching vs. CPU cost). ( Default: 10 ) 145 | ---@field max_velocity number Maximum world-space speed of an object in inches per second. ( Default: 4000 ) 146 | ---@field max_angular_velocity number Maximum world-space rotational velocity in degrees per second. ( Default: 7200 ) 147 | ---@field min_friction_mass number Minimum mass of an object to be affected by friction. ( Default: 10 ) 148 | ---@field max_friction_mass number Maximum mass of an object to be affected by friction. ( Default: 2500 ) 149 | local settings = {} 150 | 151 | local physenv_GetPerformanceSettings, physenv_SetPerformanceSettings = physenv.GetPerformanceSettings, physenv.SetPerformanceSettings 152 | local physenv_GetAirDensity, physenv_SetAirDensity = physenv.GetAirDensity, physenv.SetAirDensity 153 | local physenv_GetGravity, physenv_SetGravity = physenv.GetGravity, physenv.SetGravity 154 | 155 | -- https://wiki.facepunch.com/gmod/Structures/PhysEnvPerformanceSettings 156 | local key2performance = { 157 | max_ovw_time = "LookAheadTimeObjectsVsWorld", 158 | max_ovo_time = "LookAheadTimeObjectsVsObject", 159 | 160 | max_collisions_per_tick = "MaxCollisionChecksPerTimestep", 161 | max_object_collisions_pre_tick = "MaxCollisionsPerObjectPerTimestep", 162 | 163 | max_velocity = "MaxVelocity", 164 | max_angular_velocity = "MaxAngularVelocity", 165 | 166 | min_friction_mass = "MinFrictionMass", 167 | max_friction_mass = "MaxFrictionMass" 168 | } 169 | 170 | setmetatable( settings, { 171 | __index = function( tbl, key ) 172 | local performanceKey = key2performance[ key ] 173 | if performanceKey ~= nil then 174 | return physenv_GetPerformanceSettings()[ performanceKey ] 175 | elseif key == "gravity" then 176 | return physenv_GetGravity() 177 | elseif key == "air_density" then 178 | return physenv_GetAirDensity() 179 | end 180 | end, 181 | __newindex = function( tbl, key, value ) 182 | local performanceKey = key2performance[ key ] 183 | if performanceKey ~= nil then 184 | local values = physenv_GetPerformanceSettings() 185 | values[ performanceKey ] = value 186 | physenv_SetPerformanceSettings( values ) 187 | elseif key == "gravity" then 188 | physenv_SetGravity( value ) 189 | elseif key == "air_density" then 190 | physenv_SetAirDensity( value ) 191 | end 192 | end 193 | } ) 194 | 195 | physics.settings = settings 196 | 197 | end 198 | 199 | return physics 200 | -------------------------------------------------------------------------------- /lua/gpm/std/player.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local std, glua_game = _G.gpm.std, _G.game 3 | 4 | local glua_player, NULL, Player = _G.player, _G.NULL, _G.Player 5 | local player_Iterator = glua_player.Iterator 6 | 7 | ---@class Player 8 | local PLAYER = std.debug.findmetatable( "Player" ) 9 | 10 | ---@class gpm.std.player 11 | local player = { 12 | getLimit = glua_game.MaxPlayers, 13 | iterator = player_Iterator 14 | } 15 | 16 | -- TODO: make player class 17 | 18 | -- TODO: bots, alive, dead, all iterator/s 19 | 20 | if std.SERVER then 21 | -- local PLAYER_IsListenServerHost = PLAYER.IsListenServerHost 22 | -- local player_CreateNextBot = glua_player.CreateNextBot 23 | 24 | -- function PLAYER.new( value ) 25 | -- if is_string( value ) then 26 | -- ---@diagnostic disable-next-line: param-type-mismatch 27 | -- return player_CreateNextBot( value ) 28 | -- elseif is_number( value ) then 29 | -- ---@diagnostic disable-next-line: param-type-mismatch 30 | -- return Player( value ) 31 | -- else 32 | -- for _, ply in player_Iterator() do 33 | -- if PLAYER_IsListenServerHost( ply ) then 34 | -- return ply 35 | -- end 36 | -- end 37 | 38 | -- return NULL 39 | -- end 40 | -- end 41 | elseif std.CLIENT then 42 | -- local LocalPlayer = _G.LocalPlayer 43 | 44 | -- function PLAYER.new( value ) 45 | -- if is_string( value ) then 46 | -- return std.error( "Client cannot create players.", 2 ) 47 | -- elseif is_number( value ) then 48 | -- ---@diagnostic disable-next-line: param-type-mismatch 49 | -- return Player( value ) 50 | -- else 51 | -- return LocalPlayer() 52 | -- end 53 | -- end 54 | 55 | do 56 | 57 | local command_run = std.console.Command.run 58 | local glua_chat = _G.chat 59 | 60 | ---@class gpm.std.player.chat 61 | local chat = { 62 | playSound = glua_chat.PlaySound, 63 | filterText = _G.util.FilterText 64 | } 65 | 66 | local key2key = { 67 | getPosition = "GetChatBoxPos", 68 | getSize = "GetChatBoxSize", 69 | addText = "AddText", 70 | close = "Close", 71 | open = "Open" 72 | } 73 | 74 | std.setmetatable( chat, { 75 | __index = function( _, key ) 76 | return glua_chat[ key2key[ key ] or -1 ] 77 | end 78 | } ) 79 | 80 | --- Sends a message to the player chat. 81 | ---@param text string The message's content. 82 | ---@param teamChat boolean? Whether the message should be sent as team chat. 83 | function chat.say( text, teamChat ) 84 | command_run( teamChat and "say_team" or "say", text ) 85 | end 86 | 87 | player.chat = chat 88 | 89 | end 90 | 91 | end 92 | 93 | -- TODO: https://wiki.facepunch.com/gmod/team 94 | ---@class gpm.std.player.team 95 | local team = {} 96 | 97 | player.team = team 98 | 99 | 100 | -- ---@class gpm.std.player 101 | -- player = std.class( "player", PLAYER, player ) 102 | -- return player 103 | -------------------------------------------------------------------------------- /lua/gpm/std/protobuf.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Protobuf class 2 | 3 | -- local cls = Protobuf( [[ 4 | -- string? name = "none"; 5 | -- int32 id = 0; 6 | -- ]] ) 7 | 8 | -- net_writer:serealize( cls( { 9 | -- name = "ass", 10 | -- id = 2 11 | -- } ) ) 12 | -------------------------------------------------------------------------------- /lua/gpm/std/render.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm.std 4 | local std = _G.gpm.std 5 | 6 | local glua_render = _G.render 7 | 8 | --- [CLIENT AND MENU] 9 | --- 10 | --- The game's render library. 11 | ---@class gpm.std.render 12 | local render = std.render or {} 13 | std.render = render 14 | 15 | -- TODO: add render/draw/surface functions 16 | -------------------------------------------------------------------------------- /lua/gpm/std/render.mesh.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | https://wiki.facepunch.com/gmod/Global.Mesh 4 | https://wiki.facepunch.com/gmod/IMesh 5 | 6 | ]] 7 | 8 | -- TODO: mesh class & object 9 | -------------------------------------------------------------------------------- /lua/gpm/std/render.surface.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -------------------------------------------------------------------------------- /lua/gpm/std/sqlite.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm.std 4 | local std = _G.gpm.std 5 | local string = std.string 6 | 7 | local glua_sql = _G.sql 8 | 9 | --- [SHARED AND MENU] 10 | --- 11 | --- The local SQLite library. 12 | --- 13 | ---@class gpm.std.sqlite 14 | local sqlite = std.sqlite or {} 15 | std.sqlite = sqlite 16 | 17 | --- [SHARED AND MENU] 18 | --- 19 | --- Returns the last error message from the last query. 20 | --- 21 | ---@return string | nil err_msg The last error message. 22 | function sqlite.getLastError() 23 | return glua_sql.m_strError 24 | end 25 | 26 | local escape 27 | do 28 | 29 | local string_replace, string_find, string_sub = string.replace, string.find, string.sub 30 | 31 | --- [SHARED AND MENU] 32 | --- 33 | --- Converts a string to a safe string for use in an SQL query. 34 | --- 35 | ---@param str string? The string to convert. 36 | ---@return string str The safe string. 37 | function escape( str, no_quotes ) 38 | if str == nil then 39 | return "null" 40 | end 41 | 42 | str = string_replace( str, "'", "''", false ) 43 | 44 | local null_chr = string_find( str, "\0", 1, false ) 45 | if null_chr then 46 | str = string_sub( str, 1, null_chr - 1 ) 47 | end 48 | 49 | return no_quotes and str or ( "'" .. str .. "'" ) 50 | end 51 | 52 | end 53 | 54 | sqlite.escape = escape 55 | 56 | local rawQuery 57 | do 58 | 59 | local sql_Query = glua_sql.Query 60 | local gpm_Logger = _G.gpm.Logger 61 | local getfenv = std.getfenv 62 | local type = std.type 63 | 64 | --- [SHARED AND MENU] 65 | --- 66 | --- Executes a raw SQL query. 67 | --- 68 | ---@param str string The SQL query to execute. 69 | ---@return table? result The result of the query. 70 | function rawQuery( str ) 71 | local fenv = getfenv( 2 ) 72 | if fenv == nil then 73 | gpm_Logger:debug( "Executing SQL query: " .. str ) 74 | else 75 | local logger = fenv.Logger 76 | if type( logger ) == "Logger" then 77 | ---@cast logger Logger 78 | logger:debug( "Executing SQL query: " .. str ) 79 | else 80 | gpm_Logger:debug( "Executing SQL query: " .. str ) 81 | end 82 | end 83 | 84 | local result = sql_Query( str ) 85 | if result == false then 86 | error( glua_sql.m_strError, 2 ) 87 | end 88 | 89 | return result 90 | end 91 | 92 | end 93 | 94 | sqlite.rawQuery = rawQuery 95 | 96 | --- [SHARED AND MENU] 97 | --- 98 | --- Checks if a table exists in the database. 99 | --- 100 | ---@param name string The name of the table to check. 101 | ---@return boolean exist `true` if the table exists, `false` otherwise. 102 | function sqlite.tableExists( name ) 103 | return rawQuery( "select name from sqlite_master where name=" .. escape( name ) .. " and type='table'" ) ~= nil 104 | end 105 | 106 | --- [SHARED AND MENU] 107 | --- 108 | --- Checks if an index exists in the database. 109 | --- 110 | ---@param name string The name of the index to check. 111 | ---@return boolean exist `true` if the index exists, `false` otherwise. 112 | function sqlite.indexExists( name ) 113 | return rawQuery( "select name from sqlite_master where name=" .. escape( name ) .. " and type='index'" ) ~= nil 114 | end 115 | 116 | local query 117 | do 118 | 119 | local string_gsub = string.gsub 120 | 121 | --- [SHARED AND MENU] 122 | --- 123 | --- Executes a SQL query with parameters. 124 | --- 125 | ---@param str string The SQL query to execute. 126 | ---@param ... string The parameters to use in the query. 127 | ---@return table? result The result of the query. 128 | function query( str, ... ) 129 | local args, counter = { ... }, 0 130 | 131 | str = string_gsub( str, "?", function() 132 | counter = counter + 1 133 | return escape( args[ counter ] ) 134 | end ) 135 | 136 | local result = rawQuery( str ) 137 | if result == nil then return nil end 138 | 139 | for j = 1, #result, 1 do 140 | local row = result[ j ] 141 | for key, value in pairs( row ) do 142 | if value == "NULL" then 143 | row[ key ] = nil 144 | end 145 | end 146 | end 147 | 148 | return result 149 | end 150 | 151 | sqlite.query = query 152 | 153 | end 154 | 155 | --- [SHARED AND MENU] 156 | --- 157 | --- Executes a SQL query and returns a specific row. 158 | --- 159 | ---@param str string The SQL query to execute. 160 | ---@param row number? The row to return. 161 | ---@param ... string? The parameters to use in the query. 162 | ---@return table? result The selected row of the result. 163 | local function queryRow( str, row, ... ) 164 | local result = query( str, ... ) 165 | if result == nil then 166 | return nil 167 | else 168 | return result[ row or 1 ] 169 | end 170 | end 171 | 172 | sqlite.queryRow = queryRow 173 | 174 | --- [SHARED AND MENU] 175 | --- 176 | --- Executes a SQL query and returns the first row. 177 | --- 178 | ---@param str string The SQL query to execute. 179 | ---@param ... string? The parameters to use in the query. 180 | ---@return table? result The first row of the result. 181 | local function queryOne( str, ... ) 182 | return queryRow( str, 1, ... ) 183 | end 184 | 185 | sqlite.queryOne = queryOne 186 | 187 | do 188 | 189 | local next = std.next 190 | 191 | --- [SHARED AND MENU] 192 | --- 193 | --- Executes a SQL query and returns the first value of the first row. 194 | --- 195 | ---@param str string The SQL query to execute. 196 | ---@param ... string? The parameters to use in the query. 197 | ---@return any value The first value of the first row of the result. 198 | function sqlite.queryValue( str, ... ) 199 | local result = queryOne( str, ... ) 200 | if result == nil then 201 | return nil 202 | else 203 | return next( result ) 204 | end 205 | end 206 | 207 | end 208 | 209 | do 210 | 211 | local pcall = std.pcall 212 | 213 | --- [SHARED AND MENU] 214 | --- 215 | --- Executes a transaction of SQL queries in one block. 216 | --- 217 | ---@param fn function The function to execute all SQL queries in one transaction. 218 | ---@return any value The result of function execution. 219 | function sqlite.transaction( fn ) 220 | rawQuery( "begin" ) 221 | 222 | local ok, result = pcall( fn, query ) 223 | if ok then 224 | rawQuery( "commit" ) 225 | return result 226 | end 227 | 228 | rawQuery( "rollback" ) 229 | return error( result, 2 ) 230 | end 231 | 232 | end 233 | -------------------------------------------------------------------------------- /lua/gpm/std/steam.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | 3 | ---@class gpm.std 4 | local std = _G.gpm.std 5 | 6 | --- [SHARED AND MENU] 7 | --- 8 | --- Steam API library. 9 | --- 10 | ---@class gpm.std.steam 11 | local steam = std.steam or {} 12 | std.steam = steam 13 | 14 | local glua_system = _G.system 15 | if glua_system ~= nil then 16 | 17 | steam.getAwayTime = steam.getAwayTime or glua_system.UpTime or function() return 0 end 18 | steam.getAppTime = steam.getAppTime or glua_system.AppTime or function() return 0 end 19 | steam.time = steam.time or glua_system.SteamTime or std.os.time 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lua/gpm/std/structures.lua: -------------------------------------------------------------------------------- 1 | ---@class gpm 2 | local gpm = _G.gpm 3 | 4 | ---@class gpm.std 5 | local std = gpm.std 6 | 7 | do 8 | 9 | --- [SHARED AND MENU] 10 | --- 11 | --- A stack is a last-in-first-out (LIFO) data structure object. 12 | --- 13 | ---@class gpm.std.Stack : gpm.std.Object 14 | ---@field __class gpm.std.StackClass 15 | local Stack = std.class.base( "Stack" ) 16 | 17 | ---@protected 18 | function Stack:__init() 19 | self[ 0 ] = 0 20 | end 21 | 22 | --- [SHARED AND MENU] 23 | --- 24 | --- Checks if the stack is empty. 25 | --- 26 | ---@return boolean 27 | function Stack:isEmpty() 28 | return self[ 0 ] == 0 29 | end 30 | 31 | --- [SHARED AND MENU] 32 | --- 33 | --- Pushes a value onto the stack. 34 | --- 35 | ---@param value any The value to push onto the stack. 36 | ---@return integer position The position of the value in the stack. 37 | function Stack:push( value ) 38 | local position = self[ 0 ] + 1 39 | self[ 0 ], self[ position ] = position, value 40 | return position 41 | end 42 | 43 | --- [SHARED AND MENU] 44 | --- 45 | --- Pops the value from the top of the stack. 46 | --- 47 | ---@return any value The value that was removed from the stack. 48 | function Stack:pop() 49 | local position = self[ 0 ] 50 | if position == 0 then 51 | return nil 52 | end 53 | 54 | self[ 0 ] = position - 1 55 | 56 | local value = self[ position ] 57 | self[ position ] = nil 58 | return value 59 | end 60 | 61 | --- [SHARED AND MENU] 62 | --- 63 | --- Returns the value at the top of the stack. 64 | --- 65 | ---@return any value The value at the top of the stack. 66 | function Stack:peek() 67 | return self[ self[ 0 ] ] 68 | end 69 | 70 | --- [SHARED AND MENU] 71 | --- 72 | --- Empties the stack. 73 | --- 74 | function Stack:empty() 75 | for i = 1, self[ 0 ], 1 do 76 | self[ i ] = nil 77 | end 78 | 79 | self[ 0 ] = 0 80 | end 81 | 82 | --- [SHARED AND MENU] 83 | --- 84 | --- Returns an iterator for the stack. 85 | --- 86 | ---@return function iterator The iterator function. 87 | ---@return Stack stack The stack being iterated over. 88 | function Stack:iterator() 89 | return self.pop, self 90 | end 91 | 92 | --- [SHARED AND MENU] 93 | --- 94 | --- A stack class. 95 | --- 96 | ---@class gpm.std.StackClass : gpm.std.Stack 97 | ---@field __base gpm.std.Stack 98 | ---@overload fun(): gpm.std.Stack 99 | local StackClass = std.class.create( Stack ) 100 | std.Stack = StackClass 101 | 102 | ---@diagnostic disable-next-line: duplicate-doc-alias 103 | ---@alias Stack gpm.std.Stack 104 | 105 | end 106 | 107 | do 108 | 109 | --- [SHARED AND MENU] 110 | --- 111 | --- A queue is a first-in-first-out (FIFO) data structure object. 112 | --- 113 | ---@class gpm.std.Queue : gpm.std.Object 114 | ---@field __class gpm.std.QueueClass 115 | local Queue = std.class.base( "Queue" ) 116 | 117 | ---@protected 118 | function Queue:__init() 119 | self.front = 0 120 | self.back = 0 121 | end 122 | 123 | --- [SHARED AND MENU] 124 | --- 125 | --- Returns the length of the queue. 126 | --- 127 | ---@return integer length The length of the queue. 128 | function Queue:getLength() 129 | return self.front - self.back 130 | end 131 | 132 | --- [SHARED AND MENU] 133 | --- 134 | --- Checks if the queue is empty. 135 | --- 136 | ---@return boolean isEmpty Returns true if the queue is empty. 137 | function Queue:isEmpty() 138 | return self.front == self.back 139 | end 140 | 141 | --- [SHARED AND MENU] 142 | --- 143 | --- Empties the queue. 144 | --- 145 | function Queue:empty() 146 | for i = self.back + 1, self.front, 1 do 147 | self[ i ] = nil 148 | end 149 | 150 | self.front = 0 151 | self.back = 0 152 | end 153 | 154 | --- [SHARED AND MENU] 155 | --- 156 | --- Returns the value at the front of the queue or the back if `fromBack` is `true`. 157 | --- 158 | ---@param fromBack? boolean If `true`, returns the value at the back of the queue. 159 | ---@return any value The value at the front of the queue. 160 | function Queue:peek( fromBack ) 161 | return self[ fromBack and ( self.back + 1 ) or self.front ] 162 | end 163 | 164 | --- [SHARED AND MENU] 165 | --- 166 | --- Appends a value to the end of the queue or the front if `toFront` is `true`. 167 | --- 168 | ---@param value any The value to append. 169 | ---@param toFront? boolean If `true`, appends the value to the front of the queue. 170 | function Queue:push( value, toFront ) 171 | if toFront then 172 | local back = self.back 173 | self[ back ] = value 174 | self.back = back - 1 175 | else 176 | local front = self.front + 1 177 | self[ front ] = value 178 | self.front = front 179 | end 180 | end 181 | 182 | --- [SHARED AND MENU] 183 | --- 184 | --- Removes and returns the value at the back of the queue or the front if `fromBack` is `true`. 185 | --- 186 | ---@param fromBack? boolean If `true`, removes and returns the value at the front of the queue. 187 | ---@return any value The value at the back of the queue or the front if `fromBack` is `true`. 188 | function Queue:pop( fromBack ) 189 | local back, front = self.back, self.front 190 | if back == front then return nil end 191 | 192 | local value 193 | 194 | if fromBack then 195 | 196 | value = self[ front ] 197 | self[ front ] = nil -- unreference the value 198 | 199 | front = front - 1 200 | self.front = front 201 | 202 | else 203 | 204 | back = back + 1 205 | self.back = back 206 | 207 | value = self[ back ] 208 | self[ back ] = nil -- unreference the value 209 | 210 | end 211 | 212 | -- reset pointers if the queue is empty 213 | if back == front then 214 | self.front = 0 215 | self.back = 0 216 | end 217 | 218 | return value 219 | end 220 | 221 | --- [SHARED AND MENU] 222 | --- 223 | --- Returns an iterator for the queue. 224 | --- 225 | ---@param fromBack? boolean If `true`, returns an iterator for the back of the queue. 226 | ---@return function iterator The iterator function. 227 | ---@return gpm.std.Queue queue The queue being iterated over. 228 | ---@return boolean fromBack `true` if the iterator is for the back of the queue. 229 | function Queue:iterator( fromBack ) 230 | return self.pop, self, fromBack == true 231 | end 232 | 233 | --- [SHARED AND MENU] 234 | --- 235 | --- A queue class. 236 | --- 237 | ---@class gpm.std.QueueClass : gpm.std.Queue 238 | ---@field __base gpm.std.Queue 239 | ---@overload fun(): gpm.std.Queue 240 | local QueueClass = std.class.create( Queue ) 241 | std.Queue = QueueClass 242 | 243 | ---@alias Queue gpm.std.Queue 244 | 245 | end 246 | 247 | -- symbol class 248 | do 249 | 250 | local debug = std.debug 251 | 252 | local debug_getmetatable = debug.getmetatable 253 | local string_format = std.string.format 254 | local debug_newproxy = debug.newproxy 255 | 256 | --- [SHARED AND MENU] 257 | --- 258 | --- A symbol. 259 | --- 260 | ---@class gpm.std.Symbol : userdata 261 | 262 | ---@alias Symbol gpm.std.Symbol 263 | 264 | local base = gpm.__symbol or debug_newproxy( true ) 265 | gpm.__symbol = base 266 | 267 | local metatable = debug_getmetatable( base ) 268 | if metatable == nil then 269 | error( "userdata metatable is missing, lol wtf" ) 270 | end 271 | 272 | metatable.__type = "Symbol" 273 | 274 | ---@type table 275 | local names = metatable.__names 276 | if names == nil then 277 | names = {} 278 | metatable.__names = names 279 | debug.gc.setTableRules( names, true, false ) 280 | end 281 | 282 | ---@private 283 | function metatable:__tostring() 284 | return names[ self ] 285 | end 286 | 287 | --- [SHARED AND MENU] 288 | --- 289 | --- Creates a new symbol. 290 | --- 291 | ---@param name string The name of the symbol. 292 | ---@return gpm.std.Symbol obj The new symbol. 293 | function std.Symbol( name ) 294 | local obj = debug_newproxy( base ) 295 | names[ obj ] = string_format( "%s Symbol: %p", name, obj ) 296 | return obj 297 | end 298 | 299 | --- [SHARED AND MENU] 300 | --- 301 | --- Checks if a value is a symbol. 302 | --- 303 | ---@param value any The value to check. 304 | function std.issymbol( value ) 305 | return debug_getmetatable( value ) == metatable 306 | end 307 | 308 | end 309 | -------------------------------------------------------------------------------- /lua/gpm/std/weapon.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Think about moving this into globals 2 | local weapon = { 3 | ["ammo"] = { 4 | -- https://wiki.facepunch.com/gmod/game.AddAmmoType 5 | -- https://wiki.facepunch.com/gmod/game.BuildAmmoTypes 6 | -- https://wiki.facepunch.com/gmod/game.GetAmmoDamageType 7 | -- ... 8 | -- https://wiki.facepunch.com/gmod/game.GetAmmoTypes 9 | } 10 | } 11 | 12 | --[[ 13 | 14 | https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/includes/extensions/game.lua#L46-L54 15 | 16 | https://wiki.facepunch.com/gmod/Structures/Bullet 17 | 18 | https://wiki.facepunch.com/gmod/game.AddAmmoType 19 | 20 | ]] 21 | 22 | return weapon 23 | -------------------------------------------------------------------------------- /lua/packages/b/async_module.yue: -------------------------------------------------------------------------------- 1 | sleep( 0 ) 2 | 3 | global abc = 123 4 | export data = "async data" 5 | -------------------------------------------------------------------------------- /lua/packages/b/init.yue: -------------------------------------------------------------------------------- 1 | print = ( ... ) -> 2 | tbl = {...} 3 | len = #tbl 4 | for i = 1, len 5 | tbl[ i ] = tostring( tbl[ i ] ) 6 | 7 | Logger::Info( table.concat(tbl, "\t" ) ) 8 | 9 | import data from include "./async_module.lua" 10 | print "data from async module: ", data 11 | print "me:", __package 12 | print "and:", __module 13 | 14 | -- import Fetch from "gpm.http" 15 | -- print Fetch 16 | 17 | -- print "realm is:", await gpm.Import "exports/gpmv2" 18 | 19 | -- print "Importing virtual package..." 20 | -- import "abc" 21 | 22 | -- gpm.Import "https://gist.githubusercontent.com/dankmolot/edbab85edb6281a72082f53b98232caf/raw/c0ff1c7287503ea5decdd91b2e0b2b2bfa8d2038/example.lua" 23 | 24 | -- gpm.Import( "package:package_v2@0.1.1" ) 25 | 26 | 27 | -- print "data:", data 28 | -- print "url:", __module.url.href 29 | -- print "pkg:", _PKG 30 | -- print "abc:", abc, "_G.abc:", _G.abc 31 | 32 | nil 33 | -------------------------------------------------------------------------------- /lua/packages/b/package.lua: -------------------------------------------------------------------------------- 1 | exports = "./init.lua" 2 | 3 | dependencies = { 4 | -- ["abc"] = "0.1.0", 5 | } 6 | 7 | send = { 8 | "init.lua", 9 | "async_module.lua" 10 | } 11 | -------------------------------------------------------------------------------- /lua/packages/exports/client.yue: -------------------------------------------------------------------------------- 1 | "client.yue" 2 | -------------------------------------------------------------------------------- /lua/packages/exports/example.yue: -------------------------------------------------------------------------------- 1 | print "hello" 2 | -------------------------------------------------------------------------------- /lua/packages/exports/main.yue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pika-Software/glua-package-manager/9ad02633ae56580fb8eef8e7322f0c28acb9c177/lua/packages/exports/main.yue -------------------------------------------------------------------------------- /lua/packages/exports/menu.yue: -------------------------------------------------------------------------------- 1 | "menu.yue" 2 | -------------------------------------------------------------------------------- /lua/packages/exports/package.yue: -------------------------------------------------------------------------------- 1 | global * 2 | 3 | exports = { 4 | ".": "./main.lua", 5 | "./example": "./example.lua", 6 | "./gpmv2": { 7 | "client": "./client.lua", 8 | "menu": "./menu.lua", 9 | "server": "./server.lua", 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lua/packages/exports/server.yue: -------------------------------------------------------------------------------- 1 | "server.yue" 2 | -------------------------------------------------------------------------------- /lua/packages/imports/package.lua: -------------------------------------------------------------------------------- 1 | imports = { 2 | ["#something"] = "exports/example" 3 | } 4 | -------------------------------------------------------------------------------- /lua/packages/package_v2/init.lua: -------------------------------------------------------------------------------- 1 | local function print( ... ) 2 | local tbl = {...} 3 | local len = #tbl 4 | for i = 1, len do 5 | tbl[ i ] = tostring( tbl[ i ] ) 6 | end 7 | Logger:Info( table.concat(tbl, "\t" ) ) 8 | end 9 | 10 | -- require "abc" 11 | 12 | -- print("Importing units...") 13 | -- require "units/init.lua" 14 | 15 | local submodule = include "submodule.lua" 16 | 17 | print("Got from submodule:", submodule) 18 | 19 | print("I am", __package ) 20 | require "b" 21 | 22 | 23 | return "this package is da best" 24 | -------------------------------------------------------------------------------- /lua/packages/package_v2/package.lua: -------------------------------------------------------------------------------- 1 | name = "package_v2" 2 | autorun = true 3 | 4 | dependencies = { 5 | -- ["abc"] = "^0.1.0", 6 | ["units"] = "github:Pika-Software/units" 7 | } 8 | 9 | -- exports = "./init.lua" 10 | exports = { 11 | ["."] = { 12 | ["server"] = "./init.lua" 13 | } 14 | } 15 | 16 | -- send = { 17 | -- "init.lua", 18 | -- "submodule.lua" 19 | -- } 20 | -------------------------------------------------------------------------------- /lua/packages/package_v2/submodule.lua: -------------------------------------------------------------------------------- 1 | return "hello from submodule" 2 | -------------------------------------------------------------------------------- /lua/tests/gpm/libs/file.yue: -------------------------------------------------------------------------------- 1 | from _G import gpm 2 | from gpm import file, path 3 | 4 | LUA_GAME_PATH = SERVER and "lsv" or CLIENT and "lcl" or MENU_DLL and "LuaMenu" or "LUA" 5 | 6 | return 7 | cases: 8 | * name: "NormalizeGamePath(...) must convert absolute path to correct format" 9 | func: -> 10 | from file import NormalizeGamePath 11 | 12 | test = ( input, filePath, gamePath ) -> 13 | resultPath, resultGamePath = NormalizeGamePath( input ) 14 | expect( resultPath ).to.equal( filePath ) 15 | expect( resultGamePath ).to.equal( gamePath ) 16 | return 17 | 18 | test( "/", "", "GAME" ) 19 | test( "/hello.txt", "hello.txt", "GAME" ) 20 | test( "/lua/foo/bar.lua", "foo/bar.lua", LUA_GAME_PATH ) 21 | test( "/lua", "lua", "GAME" ) 22 | test( "/data/", "", "DATA" ) 23 | test( "/data/hello.txt", "hello.txt", "DATA" ) 24 | test( "/data/foo/bar.lua", "foo/bar.lua", "DATA" ) 25 | test( "/materials/test.png", "materials/test.png", "GAME" ) 26 | test( "/foo/bar/xyz/123/ok.gz", "foo/bar/xyz/123/ok.gz", "GAME" ) 27 | 28 | test( "hello/world.mp3", "hello/world.mp3", "GAME" ) 29 | test( "lua/gpm/init.lua", "gpm/init.lua", LUA_GAME_PATH ) 30 | test( "data/hello.txt", "hello.txt", "DATA" ) 31 | 32 | 33 | * name: "NormalizeGamePath(...) must handle relative paths" 34 | func: -> 35 | from file import NormalizeGamePath 36 | 37 | root = string.sub( path.getCurrentDirectory(nil, true), 2 ) 38 | if root == "" 39 | -- if we are unable to get the current directory, NormalizeGamePath should fail 40 | expect( NormalizeGamePath, "./test.lua" ).to.err() 41 | return 42 | 43 | -- unfortunately, unable to test this with concommand 44 | filePath, gamePath = NormalizeGamePath( "./test.lua" ) 45 | expect( filePath ).to.equal( root .. "/test.lua" ) 46 | expect( gamePath ).to.equal( LUA_GAME_PATH ) 47 | 48 | filePath, gamePath = NormalizeGamePath( "./foo.bar", "DATA" ) 49 | expect( filePath ).to.equal( root .. "/foo.bar" ) 50 | expect( gamePath ).to.equal( LUA_GAME_PATH ) 51 | 52 | 53 | * name: "NormalizeGamePath(...) should not modify paths that are already in the correct format" 54 | func: -> 55 | from file import NormalizeGamePath 56 | 57 | test = ( filePath, gamePath ) -> 58 | resultPath, resultGamePath = NormalizeGamePath( filePath, gamePath ) 59 | expect( resultPath ).to.equal( filePath ) 60 | expect( resultGamePath ).to.equal( gamePath ) 61 | return 62 | 63 | test( "abc/def", "GAME" ) 64 | test( "abc/def", "DATA" ) 65 | test( "data/hello", "GAME" ) 66 | test( "data/hello", "DATA" ) 67 | test( "lua/gpm/init.lua", LUA_GAME_PATH ) 68 | test( "lua/gpm/init.lua", "GAME" ) 69 | test( "", "ABC" ) 70 | test( "hello/world.vtf", "FOO / BAR" ) 71 | return 72 | 73 | * name: "NormalizeGamePath(...) should handle URL objects (not strings)" 74 | func: -> 75 | from file import NormalizeGamePath 76 | from gpm import URL 77 | 78 | -- NormalizeGamePath should fail if the URL is not a file 79 | expect( NormalizeGamePath, URL( "http://example.com/test.lua" ) ).to.err() 80 | 81 | filePath, gamePath = NormalizeGamePath( URL("file:///hello/world.txt") ) 82 | expect( filePath ).to.equal( "hello/world.txt" ) 83 | expect( gamePath ).to.equal( "GAME" ) 84 | 85 | filePath, gamePath = NormalizeGamePath( URL("file:///lua/gpm/init.lua") ) 86 | expect( filePath ).to.equal( "gpm/init.lua" ) 87 | expect( gamePath ).to.equal( LUA_GAME_PATH ) 88 | 89 | filePath, gamePath = NormalizeGamePath( URL("file://abc/hello.txt") ) -- abc here is a host 90 | expect( filePath ).to.equal( "hello.txt" ) 91 | expect( gamePath ).to.equal( "GAME" ) 92 | -------------------------------------------------------------------------------- /lua/tests/gpm/util.yue: -------------------------------------------------------------------------------- 1 | from _G import gpm 2 | from gpm import Error 3 | 4 | return 5 | cases: 6 | * name: "error(...) must throw always string unless in coroutine" 7 | func: -> 8 | import throw from gpm 9 | 10 | -- expect we are in main thread 11 | expect( coroutine.running() ).to.beNil() 12 | 13 | expectThrowValue = (val, expected) -> 14 | if isstring(val) or isnumber(val) 15 | expect( throw, val ).to.errWith( expected or val ) 16 | else 17 | ok, err = pcall( throw, val ) 18 | expect( ok ).to.beFalse() 19 | expect( err ).to.equal( expected or val ) 20 | 21 | expectThrowString = ( val ) -> 22 | expectThrowValue( val, tostring(val) ) 23 | 24 | -- check throw works as vanilla error inside coroutine 25 | co = coroutine.create -> 26 | expectThrowValue( "foo bar" ) 27 | expectThrowValue( {} ) 28 | expectThrowValue( true ) 29 | expectThrowValue( nil ) 30 | expectThrowValue( newproxy() ) 31 | 32 | expectThrowString( 123 ) 33 | 34 | unless ok, err := coroutine.resume( co ) 35 | error( err ) 36 | 37 | -- check throw always throws value converted to string inside main thread 38 | expectThrowString( "foo bar" ) 39 | expectThrowString( {} ) 40 | expectThrowString( true ) 41 | expectThrowString( nil ) 42 | expectThrowString( newproxy() ) 43 | expectThrowString( 123 ) 44 | expectThrowString( Error( "hello world" ) ) 45 | 46 | return 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lua/tests/gpm/util/string.yue: -------------------------------------------------------------------------------- 1 | from _G import gpm 2 | from gpm import string, table 3 | 4 | return 5 | cases: 6 | * name: "string.slice(...) must return a slice of given string" 7 | func: -> 8 | test = ( str, startPos, endPos, expected ) -> 9 | result = string.slice(str, startPos, endPos) 10 | expect( result ).to.equal( expected ) 11 | 12 | test( "hello", 1, 5, "hello" ) 13 | test( "hello", 1, 1, "h" ) 14 | test( "hello", 5, 5, "o" ) 15 | test( "hello", 0, 0, "" ) 16 | return 17 | 18 | * name: "string.StartsWith(...) must return true if string starts with given string" 19 | func: -> 20 | test1 = ( str, startStr ) -> 21 | result = string.StartsWith(str, startStr) 22 | expect( result ).to.beTrue() 23 | 24 | test1( "hello", "hel" ) 25 | test1( "hello", "hello" ) 26 | test1( "hello", "hell" ) 27 | 28 | test2 = ( str, startStr ) -> 29 | result = string.StartsWith(str, startStr) 30 | expect( result ).to.beFalse() 31 | 32 | test2( "hello", "his" ) 33 | test2( "hello", "helll" ) 34 | test2( "hello", "helllll" ) 35 | return 36 | 37 | * name: "string.EndsWith(...) must return true if string ends with given string" 38 | func: -> 39 | test1 = ( str, endStr ) -> 40 | result = string.EndsWith(str, endStr) 41 | expect( result ).to.beTrue() 42 | 43 | test1( "hello", "lo" ) 44 | test1( "hello", "hello" ) 45 | test1( "hello", "ello" ) 46 | 47 | test2 = ( str, endStr ) -> 48 | result = string.EndsWith(str, endStr) 49 | expect( result ).to.beFalse() 50 | 51 | test2( "hello", "ll" ) 52 | test2( "hello", "helll" ) 53 | test2( "hello", "helllll" ) 54 | return 55 | 56 | * name: "string.concat(...) must concatenate given strings" 57 | func: -> 58 | test = ( expected, ... ) -> 59 | result = string.concat(...) 60 | expect( result ).to.equal( expected ) 61 | 62 | test( "hello world", "hello", " world" ) 63 | test( "oh hi mark", "oh ", "hi", " mark" ) 64 | test( "glua package manager", "glua", " ", "package", " ", "manager" ) 65 | return 66 | 67 | * name: "string.IndexOf(...) must return index of given string" 68 | func: -> 69 | 70 | paragraph = "I think Ruth's dog is cuter than your dog!" 71 | 72 | test = ( str, searchable, position, withPattern, expected ) -> 73 | expect( string.IndexOf(str, searchable, position, withPattern) ).to.equal( expected ) 74 | return 75 | 76 | test( paragraph, "dog", 1, false, 16 ) 77 | test( paragraph, "dog", 17, false, 39 ) 78 | test( paragraph, "%w+'s", 1, true, 9 ) 79 | return 80 | 81 | * name: "string.Split(...) must return split strings" 82 | func: -> 83 | from table import Equal 84 | 85 | test = ( str, pattern, withPattern, expected ) -> 86 | expect( Equal( string.Split(str, pattern, withPattern), expected ) ).to.beTrue() 87 | 88 | test( "hello world", " ", false, [ "hello", "world" ] ) 89 | test( "hello world", "%s+", true, [ "hello", "world" ] ) 90 | test( "hello user, can you help other users?", "user", false, [ "hello ", ", can you help other ", "s?" ] ) 91 | return 92 | 93 | * name: "string.Count(...) must return pattern repetition count" 94 | func: -> 95 | test = ( str, pattern, withPattern, expected ) -> 96 | expect( string.Count(str, pattern, withPattern) ).to.equal( expected ) 97 | 98 | test( "hello world", "l", false, 3 ) 99 | test( "hello world", "o", false, 2 ) 100 | test( "hello world", "x", false, 0 ) 101 | test( "visual studio code", "[ios]", true, 6 ) 102 | return 103 | 104 | * name: "string.ByteSplit(...) must return table with splited parts of given string by byte" 105 | func: -> 106 | from table import Equal 107 | 108 | test = ( str, byte, expected ) -> 109 | expect( Equal( string.ByteSplit(str, byte), expected ) ).to.beTrue() 110 | 111 | test( "hello world", 0x20, [ "hello", "world" ] ) 112 | test( "glua performance is really bad", 0x20, [ "glua", "performance", "is", "really", "bad" ] ) 113 | test( "more and more strings", 0x6F, [ "m", "re and m", "re strings"] ) 114 | return 115 | 116 | * name: "string.ByteCount(...) must return byte count of given string" 117 | func: -> 118 | test = ( str, byte, expected ) -> 119 | expect( string.ByteCount(str, byte) ).to.equal( expected ) 120 | 121 | test( "hello world", 0x20, 1 ) 122 | test( "hello again", 0x61, 2 ) 123 | test( "+++++++++++++", 0x2B, 13 ) 124 | return 125 | 126 | * name: "string.TrimByte(...) must return trimmed string" 127 | func: -> 128 | test = ( str, bytes, expected ) -> 129 | expect( string.TrimByte(str, bytes) ).to.equal( expected ) 130 | 131 | test( "hello world", 0x20, "hello world" ) 132 | test( "lllo worllll", 0x6C, "o wor" ) 133 | test( " hello world", 0x20, "hello world", 1 ) 134 | test( "hello world ", 0x20, "hello world", -1 ) 135 | return 136 | 137 | * name: "string.TrimBytes(...) must return trimmed string" 138 | func: -> 139 | test = ( str, bytes, expected ) -> 140 | expect( string.TrimBytes(str, bytes) ).to.equal( expected ) 141 | 142 | test( " hello world ", { 0x20 }, "hello world" ) 143 | test( "\t\t\t\thello world ", { 0x20, 0x09 }, "hello world" ) 144 | test( "lllllllllllllllllllooolllllllllllll\t\t\t\t\t ", { 0x20, 0x09, 0x6C }, "ooo" ) 145 | return 146 | 147 | * name: "string.PatternSafe(...) must return safe pattern" 148 | func: -> 149 | test = ( pattern, expected ) -> 150 | expect( string.PatternSafe(pattern) ).to.equal( expected ) 151 | 152 | test( "hello", "hello" ) 153 | test( "hello%world", "hello%%world" ) 154 | test( "(hello)[world]", "%(hello%)%[world%]" ) 155 | test( "[[\\$$]]", "%[%[\\%$%$%]%]" ) 156 | return 157 | 158 | * name: "string.Trim(...) must return trimmed string" 159 | func: -> 160 | test = ( str, pattern, expected ) -> 161 | expect( string.Trim(str, pattern) ).to.equal( expected ) 162 | 163 | test( "hello world", " ", "hello world" ) 164 | test( " hello world\t\t\t\t\t", "%s", "hello world", 0 ) 165 | test( " \t\t\tok,,,", "%s%p", "ok" ) 166 | test( "\n\n\n\t\t\t\t\rtest", "%c", "test", 1 ) 167 | test( "yep ", nil, "yep", -1 ) 168 | return 169 | 170 | * name: "string.IsURL(...) must return true if given string is URL" 171 | func: -> 172 | test = ( str, expected ) -> 173 | expect( string.IsURL(str) ).to.equal( expected ) 174 | 175 | test( "https://google.com", true ) 176 | test( "http://google.com", true ) 177 | test( "google.com", false ) 178 | test( "www.google.com", false ) 179 | test( "file://google.com", true ) 180 | test( "ftp://google.com:80", false ) 181 | return 182 | 183 | * name: "string.Extract(...) must return table with splited parts of given string" 184 | func: -> 185 | test = ( str, pattern, default, expected ) -> 186 | expect( string.Extract( str, pattern, default ) ).to.equal( expected ) 187 | 188 | test( "hello world", " ", nil, "helloworld" ) 189 | test( "hello world", "^%w+", nil, " world" ) 190 | test( "hello user, can you help other users?", "user", nil, "hello , can you help other users?" ) 191 | return 192 | 193 | * name: "string.Left(...) must return left part of given string" 194 | func: -> 195 | test = ( str, num, expected ) -> 196 | expect( string.Left(str, num) ).to.equal( expected ) 197 | 198 | test( "hello world", 5, "hello" ) 199 | test( "hello world", 0, "" ) 200 | test( "hello world", 10, "hello worl" ) 201 | return 202 | 203 | * name: "string.Right(...) must return right part of given string" 204 | func: -> 205 | test = ( str, num, expected ) -> 206 | expect( string.Right(str, num) ).to.equal( expected ) 207 | 208 | test( "hello world", 5, "world" ) 209 | test( "hello world", 0, "hello world" ) 210 | test( "hello world", 5, "world" ) 211 | return 212 | 213 | * name: "string.Replace(...) must return replaced string" 214 | func: -> 215 | test = ( str, searchable, replaceable, withPattern, expected ) -> 216 | expect( string.Replace(str, searchable, replaceable, withPattern) ).to.equal( expected ) 217 | 218 | test( "hello world", "hello", "hi", false, "hi world" ) 219 | test( "hello world", ".", "*", true, "***********" ) 220 | test( "my little message", " ", "_", true, "my_little_message" ) 221 | return 222 | --------------------------------------------------------------------------------