├── LICENSE ├── README.md ├── async.lua ├── classlib.lua └── ooplib.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 sbx320 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua_utils 2 | ========= 3 | 4 | A collection of utility Lua scripts usable in conjunction with MTA San Andreas. 5 | 6 | 7 | ### classlib.lua 8 | This script helps out on the usage of classes and OOP in Lua. Additionally it allows an easily accessable per-element storage for MTA's elements similar to the following. 9 | ```lua 10 | local player = getPlayerFromName("sbx320"); 11 | player.someVariableIWantToStore = "Hello World!"; 12 | ``` 13 | 14 | Documentation for classlib.lua can be found here: https://github.com/sbx320/lua_utils/wiki/classlib 15 | 16 | ### async.lua 17 | Async.lua is a wrapper around coroutines which allows coroutines to be paused and resumed a lot easier. In conjunction with MTA's dbQuery and dbPoll functions asyncronous SQL queries get very easy to handle. 18 | -------------------------------------------------------------------------------- /async.lua: -------------------------------------------------------------------------------- 1 | -- Developer: sbx320 2 | -- License: MIT 3 | -- Github Repos: https://github.com/sbx320/lua_utils 4 | Async = { id = false; threads = {}} 5 | 6 | function Async.create(func) 7 | local t = setmetatable({}, { __index = Async, __call = Async.__call }) 8 | 9 | t:constructor(func) 10 | return function(...) return t:continue(...) end 11 | end 12 | 13 | function Async.constructor(self, func) 14 | self.m_Fn = func 15 | self.m_Id = #Async.threads+1 16 | Async.threads[self.m_Id] = self 17 | self.m_IsRunning = false 18 | end 19 | 20 | function Async.__call(self, ...) 21 | self:continue(...) 22 | end 23 | 24 | function Async.wait() 25 | Async.id = false 26 | coroutine.yield() 27 | return unpack(Async.threads[Async.id].m_Args) 28 | end 29 | 30 | function Async.waitFor(element) 31 | assert(Async.id, "Not within async execution, cannot wait") 32 | Async.threads[Async.id].m_Element = element 33 | local id = Async.id 34 | return function(...) return Async.continueAsync(id, ...) end 35 | end 36 | 37 | function Async.continueAsync(id, ...) 38 | return Async.threads[id]:continue(...) 39 | end 40 | 41 | function Async:continue(...) 42 | Async.id = self.m_Id 43 | 44 | if not self.m_IsRunning then 45 | self.m_Coroutine = coroutine.create(self.m_Fn) 46 | self.m_IsRunning = true 47 | assert(coroutine.resume(self.m_Coroutine, ...)) 48 | return 49 | else 50 | self.m_Args = {...} 51 | if self.m_Element then 52 | if not self.m_Element then 53 | -- abandon the coroutine so the gc can clear it 54 | Async.threads[Async.id] = nil 55 | self.m_Coroutine = nil 56 | return 57 | end 58 | self.m_Element = nil 59 | end 60 | end 61 | assert(coroutine.resume(self.m_Coroutine)) 62 | end 63 | -------------------------------------------------------------------------------- /classlib.lua: -------------------------------------------------------------------------------- 1 | -- Developer: sbx320 2 | -- License: MIT 3 | -- Github Repos: https://github.com/sbx320/lua_utils 4 | 5 | --// classlib 6 | --|| A library providing several tools to enhance OOP with MTA and Lua 7 | --\\ 8 | SERVER = triggerServerEvent == nil 9 | CLIENT = not SERVER 10 | DEBUG = DEBUG or false 11 | 12 | function enew(element, class, ...) 13 | -- DEBUG: Validate that we are not instantiating a class with pure virtual methods 14 | if DEBUG then 15 | for k, v in pairs(class) do 16 | assert(v ~= pure_virtual, "Attempted to instanciate a class with an unimplemented pure virtual method ("..tostring(k)..")") 17 | end 18 | end 19 | 20 | local instance = setmetatable( { element = element }, 21 | { 22 | __index = class; 23 | __class = class; 24 | __newindex = class.__newindex; 25 | __call = class.__call; 26 | __len = class.__len; 27 | __unm = class.__unm; 28 | __add = class.__add; 29 | __sub = class.__sub; 30 | __mul = class.__mul; 31 | __div = class.__div; 32 | __pow = class.__pow; 33 | __concat = class.__concat; 34 | }) 35 | 36 | oop.elementInfo[element] = instance 37 | 38 | local callDerivedConstructor; 39 | callDerivedConstructor = function(parentClasses, instance, ...) 40 | for k, v in pairs(parentClasses) do 41 | if rawget(v, "virtual_constructor") then 42 | rawget(v, "virtual_constructor")(instance, ...) 43 | end 44 | local s = superMultiple(v) 45 | callDerivedConstructor(s, instance, ...) 46 | end 47 | end 48 | 49 | callDerivedConstructor(superMultiple(class), element, ...) 50 | 51 | -- Call constructor 52 | if rawget(class, "constructor") then 53 | rawget(class, "constructor")(element, ...) 54 | end 55 | element.constructor = false 56 | 57 | -- Add the destruction handler 58 | if isElement(element) then 59 | addEventHandler( 60 | triggerClientEvent ~= nil and 61 | "onElementDestroy" or 62 | "onClientElementDestroy", element, __removeElementIndex, false, "low-999999") 63 | end 64 | return element 65 | end 66 | 67 | function new(class, ...) 68 | assert(type(class) == "table", "first argument provided to new is not a table") 69 | 70 | -- DEBUG: Validate that we are not instantiating a class with pure virtual methods 71 | if DEBUG then 72 | for k, v in pairs(class) do 73 | assert(v ~= pure_virtual, "Attempted to instanciate a class with an unimplemented pure virtual method ("..tostring(k)..")") 74 | end 75 | end 76 | 77 | local instance = setmetatable( { }, 78 | { 79 | __index = class; 80 | __class = class; 81 | __newindex = class.__newindex; 82 | __call = class.__call; 83 | __len = class.__len; 84 | __unm = class.__unm; 85 | __add = class.__add; 86 | __sub = class.__sub; 87 | __mul = class.__mul; 88 | __div = class.__div; 89 | __pow = class.__pow; 90 | __concat = class.__concat; 91 | }) 92 | 93 | -- Call derived constructors 94 | local callDerivedConstructor; 95 | callDerivedConstructor = function(self, instance, ...) 96 | for k, v in pairs(self) do 97 | if rawget(v, "virtual_constructor") then 98 | rawget(v, "virtual_constructor")(instance, ...) 99 | end 100 | local s = superMultiple(v) 101 | callDerivedConstructor(s, instance, ...) 102 | end 103 | end 104 | 105 | callDerivedConstructor(superMultiple(class), instance, ...) 106 | 107 | -- Call constructor 108 | if rawget(class, "constructor") then 109 | rawget(class, "constructor")(instance, ...) 110 | end 111 | instance.constructor = false 112 | 113 | return instance 114 | end 115 | 116 | function delete(self, ...) 117 | if self.destructor then --if rawget(self, "destructor") then 118 | self:destructor(...) 119 | end 120 | 121 | -- Prevent the destructor to be called twice 122 | self.destructor = false 123 | 124 | local callDerivedDestructor; 125 | callDerivedDestructor = function(parentClasses, instance, ...) 126 | for k, v in pairs(parentClasses) do 127 | if rawget(v, "virtual_destructor") then 128 | rawget(v, "virtual_destructor")(instance, ...) 129 | end 130 | local s = superMultiple(v) 131 | callDerivedDestructor(s, instance, ...) 132 | end 133 | end 134 | callDerivedDestructor(superMultiple(self), self, ...) 135 | end 136 | 137 | function superMultiple(self) 138 | if isElement(self) then 139 | assert(oop.elementInfo[self], "Cannot get the superclass of this element") -- at least: not yet 140 | self = oop.elementInfo[self] 141 | end 142 | 143 | local metatable = getmetatable(self) 144 | if not metatable then 145 | return {} 146 | end 147 | 148 | if metatable.__class then -- we're dealing with a class object 149 | return superMultiple(metatable.__class) 150 | end 151 | 152 | if metatable.__super then -- we're dealing with a class 153 | return metatable.__super or {} 154 | end 155 | end 156 | 157 | function super(self) 158 | return superMultiple(self)[1] 159 | end 160 | 161 | function classof(self) 162 | if isElement(self) then 163 | assert(oop.elementInfo[self], "Cannot get the class of this element") -- at least: not yet 164 | self = oop.elementInfo[self] 165 | end 166 | 167 | local metatable = getmetatable(self) 168 | if metatable then 169 | return metatable.__class 170 | end 171 | return {} 172 | end 173 | 174 | function inherit(from, what) 175 | assert(from, "Attempt to inherit a nil table value") 176 | if not what then 177 | local classt = setmetatable({}, { __index = _inheritIndex, __super = { from } }) 178 | if from.onInherit then 179 | from.onInherit(classt) 180 | end 181 | return classt 182 | end 183 | 184 | local metatable = getmetatable(what) or {} 185 | local oldsuper = metatable and metatable.__super or {} 186 | table.insert(oldsuper, 1, from) 187 | metatable.__super = oldsuper 188 | metatable.__index = _inheritIndex 189 | 190 | -- Inherit __call 191 | for k, v in ipairs(metatable.__super) do 192 | if v.__call then 193 | metatable.__call = v.__call 194 | break 195 | end 196 | end 197 | 198 | return setmetatable(what, metatable) 199 | end 200 | 201 | function _inheritIndex(self, key) 202 | for k, v in pairs(superMultiple(self)) do 203 | if v[key] then return v[key] end 204 | end 205 | return nil 206 | end 207 | 208 | ---// __removeElementIndex() 209 | ---|| @desc: This function calls delete on the hidden source parameter to invoke the destructor 210 | ---|| !!! Avoid calling this function manually unless you know what you're doing! !!! 211 | ---\\ 212 | function __removeElementIndex() 213 | delete(source) 214 | end 215 | 216 | function instanceof(self, class, direct) 217 | if direct then 218 | return classof(self) == class 219 | end 220 | 221 | for k, v in pairs(superMultiple(self)) do 222 | if v == class then return true end 223 | end 224 | 225 | local check = false 226 | -- Check if any of 'self's base classes is inheriting from 'class' 227 | for k, v in pairs(superMultiple(self)) do 228 | check = instanceof(v, class, false) 229 | if check then 230 | break 231 | end 232 | end 233 | return check 234 | end 235 | 236 | function pure_virtual() 237 | outputDebug(debug.traceback()) 238 | error("Function implementation missing") 239 | end 240 | 241 | function bind(func, ...) 242 | if not func then 243 | if DEBUG then 244 | outputConsole(debug.traceback()) 245 | outputServerLog(debug.traceback()) 246 | end 247 | error("Bad function pointer @ bind. See console for more details") 248 | end 249 | 250 | local boundParams = {...} 251 | return 252 | function(...) 253 | local params = {} 254 | local boundParamSize = select("#", unpack(boundParams)) 255 | for i = 1, boundParamSize do 256 | params[i] = boundParams[i] 257 | end 258 | 259 | local funcParams = {...} 260 | for i = 1, select("#", ...) do 261 | params[boundParamSize + i] = funcParams[i] 262 | end 263 | return func(unpack(params)) 264 | end 265 | end 266 | 267 | function load(class, ...) 268 | assert(type(class) == "table", "first argument provided to load is not a table") 269 | local instance = setmetatable( { }, 270 | { 271 | __index = class; 272 | __class = class; 273 | __newindex = class.__newindex; 274 | __call = class.__call; 275 | }) 276 | 277 | -- Call load 278 | if rawget(class, "load") then 279 | rawget(class, "load")(instance, ...) 280 | end 281 | instance.load = false 282 | 283 | return instance 284 | end 285 | 286 | -- Magic to allow MTA elements to be used as data storage 287 | -- e.g. localPlayer.foo = 12 288 | oop = {} 289 | oop.elementInfo = setmetatable({}, { __mode = "k" }) 290 | oop.elementClasses = {} 291 | 292 | oop.prepareClass = function(name) 293 | local mt = debug.getregistry().mt[name] 294 | 295 | if not mt then 296 | outputDebugString("No such class mt "..tostring(name)) 297 | return 298 | end 299 | 300 | -- Store MTA's metafunctions 301 | local __mtaindex = mt.__index 302 | local __mtanewindex = mt.__newindex 303 | local __set= mt.__set 304 | 305 | mt.__index = function(self, key) 306 | if not oop.handled then 307 | if not oop.elementInfo[self] and isElement(self) then 308 | enew(self, oop.elementClasses[getElementType(self)] or {}) 309 | end 310 | if oop.elementInfo[self] and oop.elementInfo[self][key] ~= nil then 311 | oop.handled = false 312 | return oop.elementInfo[self][key] 313 | end 314 | oop.handled = true 315 | end 316 | local value = __mtaindex(self, key) 317 | oop.handled = false 318 | return value 319 | end 320 | 321 | 322 | mt.__newindex = function(self, key, value) 323 | if __set[key] ~= nil then 324 | __mtanewindex(self, key, value) 325 | return 326 | end 327 | 328 | if not oop.elementInfo[self] and isElement(self) then 329 | enew(self, oop.elementClasses[getElementType(self)] or {}) 330 | end 331 | 332 | oop.elementInfo[self][key] = value 333 | end 334 | end 335 | 336 | function registerElementClass(name, class) 337 | assert(type(name) == "string", "Bad argument #1 for registerElementClass") 338 | assert(type(class) == "table", "Bad argument #2 for registerElementClass") 339 | oop.elementClasses[name] = class 340 | end 341 | 342 | oop.initClasses = function() 343 | -- this has to match 344 | -- (Server) MTA10_Server\mods\deathmatch\logic\lua\CLuaMain.cpp 345 | -- (Client) MTA10\mods\shared_logic\lua\CLuaMain.cpp 346 | if SERVER then 347 | oop.prepareClass("ACL") 348 | oop.prepareClass("ACLGroup") 349 | oop.prepareClass("Account") 350 | oop.prepareClass("Ban") 351 | oop.prepareClass("Connection") 352 | oop.prepareClass("QueryHandle") 353 | oop.prepareClass("TextDisplay") 354 | oop.prepareClass("TextItem") 355 | elseif CLIENT then 356 | oop.prepareClass("Browser") 357 | oop.prepareClass("Camera") 358 | oop.prepareClass("Light") 359 | oop.prepareClass("Projectile") 360 | oop.prepareClass("SearchLight") 361 | oop.prepareClass("Sound") 362 | oop.prepareClass("Sound3D") 363 | oop.prepareClass("Weapon") 364 | oop.prepareClass("Effect") 365 | oop.prepareClass("GuiElement") 366 | oop.prepareClass("GuiWindow") 367 | oop.prepareClass("GuiButton") 368 | oop.prepareClass("GuiEdit") 369 | oop.prepareClass("GuiLabel") 370 | oop.prepareClass("GuiMemo") 371 | oop.prepareClass("GuiStaticImage") 372 | oop.prepareClass("GuiComboBox") 373 | oop.prepareClass("GuiCheckBox") 374 | oop.prepareClass("GuiRadioButton") 375 | oop.prepareClass("GuiScrollPane") 376 | oop.prepareClass("GuiScrollBar") 377 | oop.prepareClass("GuiProgressBar") 378 | oop.prepareClass("GuiGridList") 379 | oop.prepareClass("GuiTabPanel") 380 | oop.prepareClass("GuiTab") 381 | oop.prepareClass("GuiFont") 382 | oop.prepareClass("GuiBrowser") 383 | oop.prepareClass("EngineCOL") 384 | oop.prepareClass("EngineTXD") 385 | oop.prepareClass("EngineDFF") 386 | oop.prepareClass("DxMaterial") 387 | oop.prepareClass("DxTexture") 388 | oop.prepareClass("DxFont") 389 | oop.prepareClass("DxShader") 390 | oop.prepareClass("DxScreenSource") 391 | oop.prepareClass("DxRenderTarget") 392 | oop.prepareClass("Weapon") 393 | end 394 | 395 | oop.prepareClass("Object") 396 | oop.prepareClass("Ped") 397 | oop.prepareClass("Pickup") 398 | oop.prepareClass("Player") 399 | oop.prepareClass("RadarArea") 400 | --oop.prepareClass("Vector2") 401 | --oop.prepareClass("Vector3") 402 | --oop.prepareClass("Vector4") 403 | --oop.prepareClass("Matrix") 404 | oop.prepareClass("Element") 405 | oop.prepareClass("Blip") 406 | oop.prepareClass("ColShape") 407 | oop.prepareClass("File") 408 | oop.prepareClass("Marker") 409 | oop.prepareClass("Vehicle") 410 | oop.prepareClass("Water") 411 | oop.prepareClass("XML") 412 | oop.prepareClass("Timer") 413 | oop.prepareClass("Team") 414 | oop.prepareClass("Resource") 415 | end 416 | oop.initClasses() 417 | -------------------------------------------------------------------------------- /ooplib.lua: -------------------------------------------------------------------------------- 1 | local __CLASSNAME__ 2 | local __BASECLASSES__ 3 | local __CLASSES__ = {} 4 | local __MEMBERS__ 5 | local __IS_STATIC = false 6 | 7 | function static_class(name) 8 | __IS_STATIC = true 9 | return class(name) 10 | end 11 | 12 | function maybeExtends(name) 13 | if name == extends then 14 | return extends 15 | else 16 | return buildClass(name) 17 | end 18 | end 19 | 20 | local __MEMBERNAME__ 21 | function buildMember(data) 22 | __MEMBERS__[__MEMBERNAME__] = data 23 | end 24 | 25 | 26 | function buildClass(definition) 27 | __CLASSES__[__CLASSNAME__] = definition 28 | _G[__CLASSNAME__] = definition 29 | definition.__CLASSNAME__ = __CLASSNAME__ 30 | definition.__members__ = __MEMBERS__ 31 | local parents = {} 32 | for k, v in pairs(__BASECLASSES__) do 33 | parents[k] = __CLASSES__[v] 34 | end 35 | 36 | -- Prepare parent members 37 | local defaults = {} 38 | for k, class in pairs(parents) do 39 | for name, member in pairs(class.__members__) do 40 | defaults[name] = member.default 41 | end 42 | end 43 | 44 | for k, v in pairs(__MEMBERS__) do 45 | defaults[k] = v.default 46 | end 47 | 48 | setmetatable(definition, 49 | { 50 | __index = function(self, key) 51 | for k, v in pairs(parents) do 52 | if v[key] then 53 | return v[key] 54 | end 55 | end 56 | end; 57 | 58 | __call = function(...) 59 | local member = defaults 60 | local instance = setmetatable({ __members__ = member, __class__ = definition }, 61 | { 62 | __index = function(self, key) 63 | if definition.__members__[key] then 64 | if definition.__members__[key].get then 65 | return definition.__members__[key].get(self) 66 | end 67 | return self.__members__[key] 68 | end 69 | 70 | return definition[key] 71 | end; 72 | -- Todo: Other metamethods 73 | 74 | __newindex = function(self, key, value) 75 | if definition.__members__[key] then 76 | if definition.__members__[key].set then 77 | if not definition.__members__[key].set(self, value) then 78 | return 79 | end 80 | end 81 | self.__members__[key] = value 82 | end 83 | 84 | -- Implicit member creation 85 | -- If you want, replace this by an error 86 | -- and make sure to add this line above 87 | -- to ensure proper setting for non-setter 88 | -- members 89 | self.__members__[key] = value 90 | end 91 | }) 92 | 93 | return instance 94 | end; 95 | }) 96 | 97 | if __IS_STATIC then 98 | if definition.constructor then 99 | definition:constructor() 100 | end 101 | end 102 | __IS_STATIC = false 103 | end 104 | 105 | 106 | function class(name) 107 | __CLASSNAME__ = name 108 | __BASECLASSES__ = {} 109 | __MEMBERS__ = {} 110 | return maybeExtends 111 | end 112 | 113 | function extends(name) 114 | if type(name) == "string" then 115 | -- Handle base classes 116 | __BASECLASSES__[#__BASECLASSES__+1] = name 117 | return extends 118 | else 119 | -- Handle class definition 120 | return buildClass(name) 121 | end 122 | end 123 | 124 | function member(name) 125 | __MEMBERNAME__ = name 126 | __MEMBERS__[name] = {} 127 | return buildMember 128 | end 129 | --------------------------------------------------------------------------------