diff --git a/CHANGELOG.md b/CHANGELOG.md index 850b4289b..76ce6a1ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,24 +3,59 @@ The dates are in European standard format where date is presented as **YYYY-MM-DD**. TombEngine releases are located in this repository (alongside with Tomb Editor): https://github.com/TombEngine/TombEditorReleases -## Version 1.7.1 - xxxx-xx-xx +## [Version 1.7.X] (link to release) - yyyy-mm-dd + +### Bug fixes +* Fixed bridges moving the player when the player is underwater. +* Fixed trigger triggerer not working. +* Fixed display pickup numeric string not being interpolated in high framerate mode. +* Fixed two block platform room portal traversal failing in some cases. +* Fixed incorrect handling of dynamic light shadows. +* Fixed ricochet flashes after using explosive weapons. +* Fixed incorrect flare draw in crawl state. +* Fixed starfield remaining active in the next level if next level does not have a starfield specified. +* Fixed wetness player attribute not being preserved in savegames. +* Fixed invisible HK ammo in the inventory. +* Fixed flickering rat emitter. +* Fixed player model submerging into the floor while swimming underwater. +* Fixed custom shatter sounds with custom sound IDs not playing correctly. +* Fixed crashes with sound samples larger than 2 megabytes. + +### New Features +* Added multithreading and an option for it to flow system settings. +* Added ability to use keys and puzzle items underwater. + - You must update your Lara object: https://github.com/TombEngine/Resources/raw/main/Wad2%20Objects/Lara/TEN_Lara.wad2 +* Added a particle based waterfall emitter object and associated sprite slots. + - You must use this version: https://github.com/TombEngine/Resources/raw/refs/heads/main/Wad2%20Objects/Interactables/TEN_Waterfall_Emitter.wad2 + +### Lua API changes +* Added Lerp() function to the Rotation object to allow linear interpolation between rotations. +* Added diary module. +* Added Effects.EmitAirBubble() function to spawn air bubbles. +* Added additional arguments for Sprite object slot and starting rotation value for EmitParticle function. +* Added various Translate() methods to Vec2 and Vec3 script objects. + +## [Version 1.7.1] (https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.7.4) - 2025-04-01 ### Bug fixes * Fixed static meshes with dynamic light mode not accepting room lights. -* Fixed silent crashes if no Visual C++ runtimes are installed, and provide a dialog box to download them instead. +* Fixed silent crashes if no Visual C++ runtimes are installed and provide a dialog box to download them instead. * Fixed issues with launching the engine from directories with non-Western characters in the path. * Fixed rare case of not being able to start a new game or exit game from the main menu on very slow GPUs. * Fixed occasional crashes with creatures stuck in a sector with no pathfinding set. * Fixed occasional cases of underwater switch animation not playing, if player spams jump key while pulling the switch. -* Fixed Lara's blob shadows not rendering on moveables and static meshes. +* Fixed player's blob shadows not rendering on moveables and static meshes. * Fixed antialiasing quality not changing after changing it in display settings. * Fixed endless explosion effect for Puna. * Fixed diary pick-up item inventory state not preserved in the savegame. +* Fixed gravity being applied underwater when exiting the fly cheat. +* Fixed gravity being applied when vaulting on the same frame as the player lands. ### New Features * Added realtime shader reloading in debug mode by pressing F9 key. * Added load, save, stopwatch and compass as a functional pick-up items with ability to add or remove them from inventory. * Increased particle limit from 1024 to 4096. +* Added ability for the player to more reliably stop at an edge when running at it while holding Walk. ### Lua API changes * Fixed Flow.FreezeMode.FULL drawing incorrect background. diff --git a/Documentation/config.ld b/Documentation/config.ld index 2e9fba1b7..bae3388cd 100644 --- a/Documentation/config.ld +++ b/Documentation/config.ld @@ -12,7 +12,7 @@ new_type("luautil", "5 Lua utility modules", true) not_luadoc = true -local version = "1.7.1" +local version = "1.7.2 (Developer)" project = " TombEngine" title = "TombEngine " .. version .. " Lua API" description = "TombEngine " .. version .. " scripting interface" diff --git a/Documentation/doc/1 modules/Effects.html b/Documentation/doc/1 modules/Effects.html index bcc6bc3a7..78a787994 100644 --- a/Documentation/doc/1 modules/Effects.html +++ b/Documentation/doc/1 modules/Effects.html @@ -3,7 +3,7 @@ - TombEngine 1.7.1 Lua API + TombEngine 1.7.2 (Developer) Lua API @@ -82,6 +82,7 @@
  • Flow.GameStatus
  • Input.ActionID
  • Objects.AmmoType
  • +
  • Objects.HandStatus
  • Objects.WeaponType
  • Objects.MoveableStatus
  • Objects.ObjID
  • @@ -97,6 +98,7 @@

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    - -
    - - CalculateDistance(posA, posB) -
    -
    - Calculate the distance between two positions. - - - -

    Parameters:

    - - -

    Returns:

    -
      - - float - Distance between two positions. -
    - - - -
    diff --git a/Documentation/doc/1 modules/View.html b/Documentation/doc/1 modules/View.html index 1a5ed5ccc..b50cb2f77 100644 --- a/Documentation/doc/1 modules/View.html +++ b/Documentation/doc/1 modules/View.html @@ -3,7 +3,7 @@ - TombEngine 1.7.1 Lua API + TombEngine 1.7.2 (Developer) Lua API @@ -82,6 +82,7 @@
  • Flow.GameStatus
  • Input.ActionID
  • Objects.AmmoType
  • +
  • Objects.HandStatus
  • Objects.WeaponType
  • Objects.MoveableStatus
  • Objects.ObjID
  • @@ -97,6 +98,7 @@

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    - -
    - - Moveable:Explode() -
    -
    - Explode item. This also kills and disables item. - - - - - - - - -
    -
    - - Moveable:Shatter() -
    -
    - Shatter item. This also kills and disables item. - - - - - - - - -
    -
    - - Moveable:SetEffect(effect[, timeout]) -
    -
    - Set effect to moveable - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:SetCustomEffect(Color1, Color2[, timeout]) -
    -
    - Set custom colored burn effect to moveable - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:GetEffect() -
    -
    - Get current moveable effect - - - - -

    Returns:

    -
      - - EffectID - effect type currently assigned to moveable. -
    - - - - -
    -
    - - Moveable:GetStatus() -
    -
    - Get the moveable's status. () - - - - -

    Returns:

    -
      - - MoveableStatus - The moveable's status. -
    - - - - -
    -
    - - Moveable:SetStatus(status) -
    -
    - Set the moveable's status. () - - - -

    Parameters:

    - - - - - -
    @@ -690,65 +541,20 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)
    - - Moveable:GetState() + + Moveable:SetOnHit(callback)
    - Retrieve the index of the current state. - This corresponds to the number shown in the item's state ID field in WadTool. - - - - -

    Returns:

    -
      - - int - the index of the active state -
    - - - - -
    -
    - - Moveable:GetTargetState() -
    -
    - Retrieve the index of the target state. - This corresponds to the state the object is trying to get into, which is sometimes different from the active state. - - - - -

    Returns:

    -
      - - int - the index of the target state -
    - - - - -
    -
    - - Moveable:SetState(index) -
    -
    - Set the object's state to the one specified by the given index. - Performs no bounds checking. Ensure the number given is correct, else - object may end up in corrupted animation state. + Set the name of the function to be called when the moveable is shot by Lara. + Note that this will be triggered twice when shot with both pistols at once.

    Parameters:

    @@ -758,12 +564,103 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)
    - - Moveable:GetAnim() + + Moveable:SetOnKilled(callback)
    - Retrieve the index of the current animation. - This corresponds to the number shown in the item's animation list in WadTool. + Set the name of the function to be called when the moveable is destroyed/killed + Note that enemy death often occurs at the end of an animation, and not at the exact moment + the enemy's HP becomes zero. + + + +

    Parameters:

    + + + + + +

    Usage:

    + + +
    +
    + + Moveable:SetOnCollidedWithObject(func) +
    +
    + Set the function to be called when this moveable collides with another moveable + + + +

    Parameters:

    + + + + + +

    Usage:

    + + +
    +
    + + Moveable:SetOnCollidedWithRoom(func) +
    +
    + Set the function called when this moveable collides with room geometry (e.g. a wall or floor). This function can take an argument that holds the Moveable that collided with geometry. + + + +

    Parameters:

    + + + + + +

    Usage:

    + + +
    +
    + + Moveable:GetName() +
    +
    + Get the moveable's name (its unique string identifier) + e.g. "door_back_room" or "cracked_greek_statue" + This corresponds with the "Lua Name" field in an object's properties in Tomb Editor. @@ -771,8 +668,8 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)

    Returns:

      - int - the index of the active animation + string + the moveable's name
    @@ -780,25 +677,77 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)
    - - Moveable:SetAnim(index[, slot]) + + Moveable:SetName(name)
    - Set the object's animation to the one specified by the given index. - Performs no bounds checking. Ensure the number given is correct, else - object may end up in corrupted animation state. + Set the moveable's name (its unique string identifier) + e.g. "door_back_room" or "cracked_greek_statue" + It cannot be blank and cannot share a name with any existing object.

    Parameters:

    + +

    Returns:

    +
      + + bool + true if we successfully set the name, false otherwise (e.g. if another object has the name already) +
    + + + + +
    +
    + + Moveable:GetPosition() +
    +
    + Get the object's position + + + + +

    Returns:

    +
      + + Vec3 + a copy of the moveable's position +
    + + + + +
    +
    + + Moveable:SetPosition(position[, updateRoom]) +
    +
    + Set the moveable's position + If you are moving a moveable whose behaviour involves knowledge of room geometry, + (e.g. a BADDY1, which uses it for pathfinding), then the second argument should + be true (or omitted, as true is the default). Otherwise, said moveable will not behave correctly. + + + +

    Parameters:

    + @@ -809,144 +758,108 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)
    - - Moveable:GetAnimSlot() + + Moveable:GetJointPosition(jointID[, offset])
    - Retrieve the slot ID of the animation. - In certain cases, moveable may play animations from another object slot. Use this - function when you need to identify such cases. - - - - -

    Returns:

    -
      - - int - animation slot ID -
    - - - - -
    -
    - - Moveable:GetFrame() -
    -
    - Retrieve frame number. - This is the current frame of the object's active animation. - - - - -

    Returns:

    -
      - - int - the current frame of the active animation -
    - - - - -
    -
    - - Moveable:SetFrame(frame) -
    -
    - Set frame number. - This will move the animation to the given frame. - The number of frames in an animation can be seen under the heading "End frame" in - the WadTool animation editor. If the animation has no frames, the only valid argument - is -1. + Get the moveable's joint position with an optional relative offset.

    Parameters:

    - - - - - -
    -
    - - Moveable:GetEndFrame() -
    -
    - Get the end frame number of the moveable's active animation. - This is the "End Frame" set in WADTool for the animation.() - - - - -

    Returns:

    -
      - - int - End frame number of the active animation. -
    - - - - -
    -
    - - Moveable:SetVelocity(velocity) -
    -
    - Set the object's velocity to specified value. - In most cases, only Z and Y components are used as forward and vertical velocity. - In some cases, primarily NPCs, X component is used as side velocity. - - - -

    Parameters:

    - - - - - -
    -
    - - Moveable:GetVelocity() -
    -
    - Get the object's velocity. - In most cases, only Z and Y components are used as forward and vertical velocity. - In some cases, primarily NPCs, X component is used as side velocity. - - - -

    Returns:

      Vec3 - current object velocity + pos World position.
    +
    +
    + + Moveable:GetJointRotation(index) +
    +
    + Get the object's joint rotation + + + +

    Parameters:

    + + +

    Returns:

    +
      + + Rotation + a calculated copy of the moveable's joint rotation +
    + + + + +
    +
    + + Moveable:GetRotation() +
    +
    + Get the moveable's rotation + + + + +

    Returns:

    +
      + + Rotation + a copy of the moveable's rotation +
    + + + + +
    +
    + + Moveable:SetRotation(rotation) +
    +
    + Set the moveable's rotation + + + +

    Parameters:

    + + + + + +
    @@ -1055,6 +968,85 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM) + +
    + + Moveable:SetEffect(effect[, timeout]) +
    +
    + Set the effect for this moveable. + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:SetCustomEffect(Color1, Color2[, timeout]) +
    +
    + Set custom colored burn effect to moveable + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:GetEffect() +
    +
    + Get current moveable effect + + + + +

    Returns:

    +
      + + EffectID + Sffect type currently assigned. +
    + + + +
    @@ -1109,508 +1101,6 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM) - -
    - - Moveable:GetColor() -
    -
    - Get the moveable's color - - - - -

    Returns:

    -
      - - Color - a copy of the moveable's color -
    - - - - -
    -
    - - Moveable:SetColor(color) -
    -
    - Set the moveable's color - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:GetHitStatus() -
    -
    - Get the hit status of the object - - - - -

    Returns:

    -
      - - bool - true if the moveable was hit by something in the last gameplay frame, false otherwise -
    - - - - -
    -
    - - Moveable:GetActive() -
    -
    - Determine whether the moveable is active or not - - - - -

    Returns:

    -
      - - bool - true if the moveable is active -
    - - - - -
    -
    - - Moveable:GetJointPosition(index[, offset]) -
    -
    - Get the object's joint position - - - -

    Parameters:

    - - -

    Returns:

    -
      - - Vec3 - a copy of the moveable's joint position -
    - - - - -
    -
    - - Moveable:GetJointRotation(index) -
    -
    - Get the object's joint rotation - - - -

    Parameters:

    - - -

    Returns:

    -
      - - Rotation - a calculated copy of the moveable's joint rotation -
    - - - - -
    -
    - - Moveable:GetRotation() -
    -
    - Get the moveable's rotation - - - - -

    Returns:

    -
      - - Rotation - a copy of the moveable's rotation -
    - - - - -
    -
    - - Moveable:SetRotation(rotation) -
    -
    - Set the moveable's rotation - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:GetName() -
    -
    - Get the moveable's name (its unique string identifier) - e.g. "door_back_room" or "cracked_greek_statue" - This corresponds with the "Lua Name" field in an object's properties in Tomb Editor. - - - - -

    Returns:

    -
      - - string - the moveable's name -
    - - - - -
    -
    - - Moveable:SetName(name) -
    -
    - Set the moveable's name (its unique string identifier) - e.g. "door_back_room" or "cracked_greek_statue" - It cannot be blank and cannot share a name with any existing object. - - - -

    Parameters:

    - - -

    Returns:

    -
      - - bool - true if we successfully set the name, false otherwise (e.g. if another object has the name already) -
    - - - - -
    -
    - - Moveable:GetValid() -
    -
    - Test if the object is in a valid state (i.e. has not been destroyed through Lua or killed by Lara). - - - - -

    Returns:

    -
      - - bool - valid true if the object is still not destroyed -
    - - - - -
    -
    - - Moveable:Destroy() -
    -
    - Destroy the moveable. This will mean it can no longer be used, except to re-initialize it with another object. - - - - - - - - -
    -
    - - Moveable:AttachObjCamera(mesh, target, mesh) -
    -
    - Attach camera to an object. - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:AnimFromObject(ObjectID, animNumber, stateID) -
    -
    - Borrow animation from an object - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:SetOnHit(callback) -
    -
    - Set the name of the function to be called when the moveable is shot by Lara. - Note that this will be triggered twice when shot with both pistols at once. - - - -

    Parameters:

    - - - - - - -
    -
    - - Moveable:SetOnCollidedWithObject(func) -
    -
    - Set the function to be called when this moveable collides with another moveable - - - -

    Parameters:

    - - - - - -

    Usage:

    - - -
    -
    - - Moveable:SetOnCollidedWithRoom(func) -
    -
    - Set the function called when this moveable collides with room geometry (e.g. a wall or floor). This function can take an argument that holds the Moveable that collided with geometry. - - - -

    Parameters:

    - - - - - -

    Usage:

    - - -
    -
    - - Moveable:SetOnKilled(callback) -
    -
    - Set the name of the function to be called when the moveable is destroyed/killed - Note that enemy death often occurs at the end of an animation, and not at the exact moment - the enemy's HP becomes zero. - - - -

    Parameters:

    - - - - - -

    Usage:

    - - -
    -
    - - Moveable:GetPosition() -
    -
    - Get the object's position - - - - -

    Returns:

    -
      - - Vec3 - a copy of the moveable's position -
    - - - - -
    -
    - - Moveable:SetPosition(position[, updateRoom]) -
    -
    - Set the moveable's position - If you are moving a moveable whose behaviour involves knowledge of room geometry, - (e.g. a BADDY1, which uses it for pathfinding), then the second argument should - be true (or omitted, as true is the default). Otherwise, said moveable will not behave correctly. - - - -

    Parameters:

    - - - - - -
    @@ -1654,6 +1144,49 @@ baddy:SetOnKilled(LevelFuncs.baddyKilled) + +
    + + Moveable:GetColor() +
    +
    + Get the moveable's color + + + + +

    Returns:

    +
      + + Color + a copy of the moveable's color +
    + + + + +
    +
    + + Moveable:SetColor(color) +
    +
    + Set the moveable's color + + + +

    Parameters:

    + + + + + +
    @@ -1715,6 +1248,307 @@ baddy:SetOnKilled(LevelFuncs.baddyKilled) sas:SetAIBits({1, 0, 0, 0, 0, 0}) + +
    + + Moveable:GetState() +
    +
    + Retrieve the index of the current state. + This corresponds to the number shown in the item's state ID field in WadTool. + + + + +

    Returns:

    +
      + + int + the index of the active state +
    + + + + +
    +
    + + Moveable:GetTargetState() +
    +
    + Retrieve the index of the target state. + This corresponds to the state the object is trying to get into, which is sometimes different from the active state. + + + + +

    Returns:

    +
      + + int + the index of the target state +
    + + + + +
    +
    + + Moveable:SetState(index) +
    +
    + Set the object's state to the one specified by the given index. + Performs no bounds checking. Ensure the number given is correct, else + object may end up in corrupted animation state. + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:GetAnimSlot() +
    +
    + Retrieve the slot ID of the animation. + In certain cases, moveable may play animations from another object slot. Use this + function when you need to identify such cases. + + + + +

    Returns:

    +
      + + int + animation slot ID +
    + + + + +
    +
    + + Moveable:GetAnim() +
    +
    + Retrieve the index of the current animation. + This corresponds to the number shown in the item's animation list in WadTool. + + + + +

    Returns:

    +
      + + int + the index of the active animation +
    + + + + +
    +
    + + Moveable:SetAnim(index[, slot]) +
    +
    + Set the object's animation to the one specified by the given index. + Performs no bounds checking. Ensure the number given is correct, else + object may end up in corrupted animation state. + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:GetFrame() +
    +
    + Retrieve frame number. + This is the current frame of the object's active animation. + + + + +

    Returns:

    +
      + + int + the current frame of the active animation +
    + + + + +
    +
    + + Moveable:GetVelocity() +
    +
    + Get the object's velocity. + In most cases, only Z and Y components are used as forward and vertical velocity. + In some cases, primarily NPCs, X component is used as side velocity. + + + + +

    Returns:

    +
      + + Vec3 + current object velocity +
    + + + + +
    +
    + + Moveable:SetVelocity(velocity) +
    +
    + Set the object's velocity to specified value. + In most cases, only Z and Y components are used as forward and vertical velocity. + In some cases, primarily NPCs, X component is used as side velocity. + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:SetFrame(frame) +
    +
    + Set frame number. + This will move the animation to the given frame. + The number of frames in an animation can be seen under the heading "End frame" in + the WadTool animation editor. If the animation has no frames, the only valid argument + is -1. + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:GetEndFrame() +
    +
    + Get the end frame number of the moveable's active animation. + This is the "End Frame" set in WADTool for the animation.() + + + + +

    Returns:

    +
      + + int + End frame number of the active animation. +
    + + + + +
    +
    + + Moveable:GetActive() +
    +
    + Determine whether the moveable is active or not + + + + +

    Returns:

    +
      + + bool + true if the moveable is active +
    + + + + +
    +
    + + Moveable:GetHitStatus() +
    +
    + Get the hit status of the moveable. + + + + +

    Returns:

    +
      + + bool + true if the moveable was hit by something in the last gameplay frame, false otherwise +
    + + + +
    @@ -1786,6 +1620,49 @@ sas:SetRoomNumber(newRoomID) sas:SetPosition(newPos, false) + +
    + + Moveable:GetStatus() +
    +
    + Get the moveable's status. () + + + + +

    Returns:

    +
      + + MoveableStatus + Status. +
    + + + + +
    +
    + + Moveable:SetStatus(status) +
    +
    + Set the moveable's status. () + + + +

    Parameters:

    + + + + + +
    @@ -2011,6 +1888,36 @@ sas:SetPosition(newPos, false) + +
    + + Moveable:Explode() +
    +
    + Explode this moveable. Also kills and disables it. + + + + + + + + +
    +
    + + Moveable:Shatter() +
    +
    + Shatter this moveable. Also kills and disables it. + + + + + + + +
    @@ -2091,6 +1998,102 @@ sas:SetPosition(newPos, false) + +
    + + Moveable:GetValid() +
    +
    + Test if the object is in a valid state (i.e. has not been destroyed through Lua or killed by Lara). + + + + +

    Returns:

    +
      + + bool + valid true if the object is still not destroyed +
    + + + + +
    +
    + + Moveable:Destroy() +
    +
    + Destroy the moveable. This will mean it can no longer be used, except to re-initialize it with another object. + + + + + + + + +
    +
    + + Moveable:AttachObjCamera(mesh, target, mesh) +
    +
    + Attach camera to an object. + + + +

    Parameters:

    + + + + + + +
    +
    + + Moveable:AnimFromObject(ObjectID, animNumber, stateID) +
    +
    + Borrow animation from an object + + + +

    Parameters:

    + + + + + +
    diff --git a/Documentation/doc/2 classes/Objects.Room.html b/Documentation/doc/2 classes/Objects.Room.html index 478993e2a..f71d00b90 100644 --- a/Documentation/doc/2 classes/Objects.Room.html +++ b/Documentation/doc/2 classes/Objects.Room.html @@ -3,7 +3,7 @@ - TombEngine 1.7.1 Lua API + TombEngine 1.7.2 (Developer) Lua API @@ -82,6 +82,7 @@
  • Flow.GameStatus
  • Input.ActionID
  • Objects.AmmoType
  • +
  • Objects.HandStatus
  • Objects.WeaponType
  • Objects.MoveableStatus
  • Objects.ObjID
  • @@ -97,6 +98,7 @@

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules

    5 Lua utility modules