diff --git a/[gameplay]/glue/config/ShVehicleAttachConfig.lua b/[gameplay]/glue/config/ShVehicleAttachConfig.lua
new file mode 100644
index 000000000..2a55c43b2
--- /dev/null
+++ b/[gameplay]/glue/config/ShVehicleAttachConfig.lua
@@ -0,0 +1,90 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+IS_SERVER = (not triggerServerEvent) -- do not touch — constant; helper bool which checks whether it's a server or client environment
+
+GLUE_WEAPON_SLOTS = {} -- do not touch — constant; used to verify whether passed weapon slot is in valid range (0-12), this is used to restore currently held weapon, because MTA resets weapon slot on attach
+GLUE_CLIENT_ATTACH_DATA_SIZE = 6 -- do not touch, unless you modify data sent from client via triggerServerEvent (onServerVehicleAttachElement)
+GLUE_ELEMENT_TYPES_AND_EVENTS = { -- do not touch — constant; controls which events would be added for glue logic based on allowed element type
+ ["player"] = "onPlayerWasted",
+ ["vehicle"] = "onVehicleExplode",
+}
+
+GLUE_ALLOWED_ELEMENTS = {"player", "vehicle"} -- elements which could be attached to vehicle (supported: "player", "vehicle")
+GLUE_VEHICLE_TYPES = { -- only relevant if GLUE_ALLOWED_ELEMENTS contains "vehicle", specifies which vehicle types are allowed to glue
+ "Automobile",
+ --"Plane",
+ "Bike",
+ "Helicopter",
+ --"Boat",
+ "Train",
+ "Trailer",
+ "BMX",
+ "Monster Truck",
+ "Quad",
+}
+
+GLUE_VEHICLE_WHITELIST = { -- only relevant if GLUE_ALLOWED_ELEMENTS contains "vehicle", ignores GLUE_VEHICLE_TYPES; specifies which vehicle models are allowed to glue
+ 500, -- mesa
+ 411, -- infernus
+ 443, -- packer
+ 487, -- maverick
+}
+
+GLUE_ATTACH_OVER_VEHICLE = false -- if true, vehicles will attach over the top of target vehicle. if false, vehicles will attach but will maintain original position (seamless, precise glue'ing)
+GLUE_DETACH_ON_VEHICLE_EXPLOSION = true -- specifies whether attached elements would be automatically detached, when attachedTo vehicle explodes
+GLUE_ATTACH_ON_TOP_OFFSETS = {0, 0, 1.5} -- only relevant if GLUE_ATTACH_OVER_VEHICLE is set to true, specifies static position for attaching vehicle over vehicle
+GLUE_ATTACH_VEHICLE_MAX_DISTANCE = 5 -- specifies maximum distance for nearby vehicle to be glued (also serves anti-cheat purpose, so only nearby vehicle could be attached)
+GLUE_ATTACH_PLAYER_MAX_DISTANCE = 10 -- ditto
+
+GLUE_PREVENT_CONTROLS = false -- prevent players from shooting their guns while attached to vehicle
+GLUE_PREVENT_CONTROLS_LIST = {"fire", "action"} -- only relevant GLUE_PREVENT_CONTROLS is set to true, specifies which controls will be toggled on/off
+
+GLUE_SYNC_CORRECTION = true -- whether attached player positions should be sanity corrected by server, this exists to prevents positional desyncs (e.g: during vehicle teleporting on long distances), which causes player to behave like a ghost (roam freely around SA, while still appearing glued to vehicle for other players)
+GLUE_SYNC_CORRECTION_INTERVAL = 3500 -- only relevant if GLUE_SYNC_CORRECTION is set to true, how often glued player position should be corrected, do not set it too low, otherwise you will face weapon aiming interruption (this was constant issue when this variable was set to 1000 before)
+
+GLUE_MESSAGE_PREFIX = "[Glue]:" -- shown in all glue messages
+GLUE_MESSAGE_PREFIX_COLOR = "#c68ff8" -- color used by prefix
+GLUE_MESSAGE_HIGHLIGHT_COLOR = "#c68ff8" -- color used in message highlights
+GLUE_SHOW_ONE_TIME_HINT = true -- whether player should receive one-time (till resource restart) hint, regarding glue keybindings and settings, upon entering a vehicle
+
+GLUE_ALLOW_ATTACH_TOGGLING = true -- should players be able to control attach lock on their vehicle (as a driver)
+GLUE_ALLOW_DETACHING_ELEMENTS = true -- should players be able to detach already attached elements (as a driver)
+
+GLUE_ATTACH_DETACH_KEY = "X" -- used to attach/detach yourself/vehicle
+GLUE_ATTACH_TOGGLE_KEY = "C" -- only relevant if GLUE_ALLOW_ATTACH_TOGGLING is set to true; specifies whether players can disable attaching to vehicle which are driver of
+GLUE_DETACH_ELEMENTS_KEY = "B" -- only relevant if GLUE_ALLOW_DETACHING_ELEMENTS is set to true; controls whether vehicle driver is able to detach elements currently attached to vehicle
+
+GLUE_ATTACH_DETACH_DELAY = 300 -- how often player can attach/detach yourself/vehicle
+GLUE_ATTACH_TOGGLE_DELAY = 300 -- only relevant if GLUE_ALLOW_ATTACH_TOGGLING is set to true; how often player can toggle vehicle attach lock
+GLUE_DETACH_ELEMENTS_DELAY = 1000 -- only relevant if GLUE_ALLOW_DETACHING_ELEMENTS is set to true; how often player can detach all currently attached elements
+
+do
+ local vehicleModelsWhitelist = {}
+ local vehicleTypesWhitelist = {}
+
+ for vehicleModelID = 1, #GLUE_VEHICLE_WHITELIST do
+ local vehicleModel = GLUE_VEHICLE_WHITELIST[vehicleModelID]
+
+ vehicleModelsWhitelist[vehicleModel] = true
+ end
+
+ for vehicleTypeID = 1, #GLUE_VEHICLE_TYPES do
+ local vehicleType = GLUE_VEHICLE_TYPES[vehicleTypeID]
+
+ vehicleTypesWhitelist[vehicleType] = true
+ end
+
+ GLUE_VEHICLE_WHITELIST = vehicleModelsWhitelist
+ GLUE_VEHICLE_TYPES = vehicleTypesWhitelist
+
+ local weaponSlotMin = 0
+ local weaponSlotMax = 12
+
+ for weaponSlotID = weaponSlotMin, weaponSlotMax do
+ GLUE_WEAPON_SLOTS[weaponSlotID] = true
+ end
+end
\ No newline at end of file
diff --git a/[gameplay]/glue/logic/CVehicleAttach.lua b/[gameplay]/glue/logic/CVehicleAttach.lua
new file mode 100644
index 000000000..6ec832ab5
--- /dev/null
+++ b/[gameplay]/glue/logic/CVehicleAttach.lua
@@ -0,0 +1,218 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+local glueHintsDisplayed = false
+
+local function handleGlueAttachAndDetach()
+ local playerVehicle = getPedOccupiedVehicle(localPlayer)
+
+ if (playerVehicle) then
+ local playerVehicleToDetach = playerVehicle
+ local playerVehicleType = getVehicleType(playerVehicle)
+ local playerVehicleHelicopter = (playerVehicleType == "Helicopter")
+
+ if (playerVehicleHelicopter) then
+ local playerHelicopterAttachedVehicle = getAttachedVehicle(playerVehicle)
+
+ if (playerHelicopterAttachedVehicle) then
+ playerVehicleToDetach = playerHelicopterAttachedVehicle
+ end
+ end
+
+ if (playerVehicleToDetach) then
+ local playerVehicleCanDetach = canPlayerDetachElementFromVehicle(localPlayer, playerVehicleToDetach)
+
+ if (playerVehicleCanDetach) then
+ triggerServerEvent("onServerVehicleDetachElement", localPlayer, playerVehicleToDetach)
+
+ return true
+ end
+ end
+
+ local vehicleNearby = getNearestVehicleFromVehicle(playerVehicle)
+
+ if (not vehicleNearby) then
+ return false
+ end
+
+ local playerVehicleAttach = (playerVehicleHelicopter and vehicleNearby or playerVehicle)
+ local playerVehicleAttachTo = (playerVehicleHelicopter and playerVehicle or vehicleNearby)
+ local playerCanAttachVehicleToVehicle = canPlayerAttachElementToVehicle(localPlayer, playerVehicleAttach, playerVehicleAttachTo)
+
+ if (not playerCanAttachVehicleToVehicle) then
+ return false
+ end
+
+ local vehicleAttachX, vehicleAttachY, vehicleAttachZ, vehicleAttachRX, vehicleAttachRY, vehicleAttachRZ = getVehicleAttachData(playerVehicleAttach, playerVehicleAttachTo)
+
+ if (playerVehicleHelicopter) then
+ local helicopterAttachX, helicopterAttachY, helicopterAttachZ = 0, 0, -1.5
+ local helicopterAttachRX, helicopterAttachRY, helicopterAttachRZ = 0, 0, 0
+
+ vehicleAttachX, vehicleAttachY, vehicleAttachZ = helicopterAttachX, helicopterAttachY, helicopterAttachZ
+ vehicleAttachRX, vehicleAttachRY, vehicleAttachRZ = helicopterAttachRX, helicopterAttachRY, helicopterAttachRZ
+ end
+
+ local vehicleAttachData = {vehicleAttachX, vehicleAttachY, vehicleAttachZ, vehicleAttachRX, vehicleAttachRY, vehicleAttachRZ}
+
+ triggerServerEvent("onServerVehicleAttachElement", localPlayer, playerVehicleAttach, playerVehicleAttachTo, vehicleAttachData)
+
+ return false
+ end
+
+ if (not playerVehicle) then
+ local playerAttachedTo = getElementAttachedTo(localPlayer)
+
+ if (playerAttachedTo) then
+ local playerCanDetach = canPlayerDetachElementFromVehicle(localPlayer, localPlayer)
+
+ if (not playerCanDetach) then
+ return false
+ end
+
+ triggerServerEvent("onServerVehicleDetachElement", localPlayer, localPlayer)
+
+ return true
+ end
+
+ local playerContactElement = getPedContactElement(localPlayer)
+ local playerContactVehicle = isElementType(playerContactElement, "vehicle")
+
+ if (not playerContactVehicle) then
+ return false
+ end
+
+ local playerCanAttach = canPlayerAttachElementToVehicle(localPlayer, localPlayer, playerContactElement)
+
+ if (not playerCanAttach) then
+ return false
+ end
+
+ local playerX, playerY, playerZ = getElementPosition(localPlayer)
+ local playerVehicleMatrix = getElementMatrix(playerContactElement)
+ local playerPosition = {playerX, playerY, playerZ}
+ local playerAttachX, playerAttachY, playerAttachZ = getOffsetFromXYZ(playerVehicleMatrix, playerPosition)
+ local playerAttachRX, playerAttachRY, playerAttachRZ = 0, 0, 0
+ local playerAttachData = {playerAttachX, playerAttachY, playerAttachZ, playerAttachRX, playerAttachRY, playerAttachRZ}
+ local playerWeaponSlot = getPedWeaponSlot(localPlayer)
+
+ triggerServerEvent("onServerVehicleAttachElement", localPlayer, localPlayer, playerContactElement, playerAttachData, playerWeaponSlot)
+ end
+end
+bindKey(GLUE_ATTACH_DETACH_KEY, "down", handleGlueAttachAndDetach)
+
+local function handleGlueAttachLock()
+ local canToggleVehicleAttachLock = canPlayerToggleVehicleAttachLock(localPlayer)
+
+ if (canToggleVehicleAttachLock) then
+ triggerServerEvent("onServerVehicleToggleAttachLock", localPlayer)
+ end
+end
+if (GLUE_ALLOW_ATTACH_TOGGLING) then
+ bindKey(GLUE_ATTACH_TOGGLE_KEY, "down", handleGlueAttachLock)
+end
+
+local function handleGlueDetachElements()
+ local canDetachVehicleElements = canPlayerDetachElementsFromVehicle(localPlayer)
+
+ if (canDetachVehicleElements) then
+ triggerServerEvent("onServerVehicleDetachElements", localPlayer)
+ end
+end
+if (GLUE_ALLOW_DETACHING_ELEMENTS) then
+ bindKey(GLUE_DETACH_ELEMENTS_KEY, "down", handleGlueDetachElements)
+end
+
+local function toggleCombatControls(forcedState)
+ for controlID = 1, #GLUE_PREVENT_CONTROLS_LIST do
+ local controlName = GLUE_PREVENT_CONTROLS_LIST[controlID]
+ local controlState = isControlEnabled(controlName)
+ local controlStateNeedsUpdate = (controlState ~= forcedState)
+
+ if (controlStateNeedsUpdate) then
+ toggleControl(controlName, forcedState)
+ end
+ end
+
+ return true
+end
+
+local function restoreCombatControlsOnEvent(vehicleElement)
+ local playerAttachedTo = getElementAttachedTo(localPlayer)
+
+ if (not playerAttachedTo) then
+ return false
+ end
+
+ if (vehicleElement) then
+ local playerAttachedElementMatching = (playerAttachedTo == vehicleElement)
+
+ if (not playerAttachedElementMatching) then
+ return false
+ end
+ end
+
+ toggleCombatControls(true)
+
+ return true
+end
+
+local function restoreCombatControlsOnVehicleDestroy()
+ restoreCombatControlsOnEvent(source)
+end
+if (GLUE_PREVENT_CONTROLS) then
+ addEventHandler("onClientElementDestroy", root, restoreCombatControlsOnVehicleDestroy)
+end
+
+local function restoreCombatControlsOnResourceStop()
+ restoreCombatControlsOnEvent()
+end
+if (GLUE_PREVENT_CONTROLS) then
+ addEventHandler("onClientResourceStop", root, restoreCombatControlsOnResourceStop)
+end
+
+local function displayGlueHintsOnVehicleEnter()
+ if (glueHintsDisplayed) then
+ return false
+ end
+
+ local glueHintCanAttachVehicle = findInTable(GLUE_ALLOWED_ELEMENTS, "vehicle")
+ local glueHintCanAttachPlayer = findInTable(GLUE_ALLOWED_ELEMENTS, "player")
+
+ if (not glueHintCanAttachVehicle and not glueHintCanAttachPlayer) then
+ return false
+ end
+
+ local glueHintAttachVehicles = glueHintCanAttachVehicle and GLUE_MESSAGE_HIGHLIGHT_COLOR.."current/nearby vehicle#ffffff" or ""
+ local glueHintAttachYourself = glueHintCanAttachPlayer and GLUE_MESSAGE_HIGHLIGHT_COLOR.."yourself#ffffff"..(glueHintCanAttachVehicle and " or " or "") or ""
+ local glueHintAttachLock = (GLUE_ALLOW_ATTACH_TOGGLING and "#ffffff'"..GLUE_MESSAGE_HIGHLIGHT_COLOR..GLUE_ATTACH_TOGGLE_KEY.."#ffffff' is used to disable/enable attaching to your vehicle. " or "")
+ local glueHintDetachElements = (GLUE_ALLOW_DETACHING_ELEMENTS and "#ffffff'"..GLUE_MESSAGE_HIGHLIGHT_COLOR..GLUE_DETACH_ELEMENTS_KEY.."#ffffff' to detach all currently attached elements." or "")
+
+ local glueHintA = "Press '"..GLUE_MESSAGE_HIGHLIGHT_COLOR..GLUE_ATTACH_DETACH_KEY.."#ffffff' to attach "..glueHintAttachYourself..glueHintAttachVehicles.." to (nearby/current) vehicle."
+ local glueHintB = glueHintAttachLock
+ local glueHintC = glueHintDetachElements
+
+ sendGlueMessage(glueHintA)
+ sendGlueMessage(glueHintB, nil, "*")
+ sendGlueMessage(glueHintC, nil, "*")
+
+ glueHintsDisplayed = true
+end
+if (GLUE_SHOW_ONE_TIME_HINT) then
+ addEventHandler("onClientPlayerVehicleEnter", localPlayer, displayGlueHintsOnVehicleEnter)
+end
+
+function onClientAttachStateChanged(playerAttached)
+ if (not GLUE_PREVENT_CONTROLS) then
+ return false
+ end
+
+ local toggleControlState = (not playerAttached)
+
+ toggleCombatControls(toggleControlState)
+end
+addEvent("onClientAttachStateChanged", true)
+addEventHandler("onClientAttachStateChanged", localPlayer, onClientAttachStateChanged)
\ No newline at end of file
diff --git a/[gameplay]/glue/logic/SVehicleAttach.lua b/[gameplay]/glue/logic/SVehicleAttach.lua
new file mode 100644
index 000000000..f03facbe4
--- /dev/null
+++ b/[gameplay]/glue/logic/SVehicleAttach.lua
@@ -0,0 +1,205 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+local function performAttachDetachTasksForPlayer(playerElement, attachTo)
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ setGlueSyncCorrectionForPlayer(playerElement, attachTo)
+ triggerClientEvent(playerElement, "onClientAttachStateChanged", playerElement, attachTo)
+
+ return true
+end
+
+local function processAttachData(attachData)
+ local attachDataTable = typeCheck(attachData, "table")
+
+ if (not attachDataTable) then
+ return false
+ end
+
+ local attachDataSize = (#attachData)
+ local attachDataSizeMatching = (attachDataSize == GLUE_CLIENT_ATTACH_DATA_SIZE)
+
+ if (not attachDataSizeMatching) then
+ return false
+ end
+
+ for attachDataID = 1, attachDataSize do
+ local attachDataValue = attachData[attachDataID]
+ local attachDataNumber = typeCheck(attachDataValue, "number")
+
+ if (not attachDataNumber) then
+ return false
+ end
+ end
+
+ return true
+end
+
+local function adjustPlayerWeaponSlot(clientElement, attachElement, playerWeaponSlot)
+ local playerMatching = (clientElement == attachElement)
+
+ if (not playerMatching) then
+ return false
+ end
+
+ local playerWeaponSlotNumber = typeCheck(playerWeaponSlot, "number")
+
+ if (not playerWeaponSlotNumber) then
+ return false
+ end
+
+ local playerWeaponSlotValid = GLUE_WEAPON_SLOTS[playerWeaponSlot]
+
+ if (not playerWeaponSlotValid) then
+ return false
+ end
+
+ local playerWeaponSlotNow = getPedWeaponSlot(clientElement)
+ local playerWeaponSlotNeedsUpdate = (playerWeaponSlotNow ~= playerWeaponSlot)
+
+ if (playerWeaponSlotNeedsUpdate) then
+ setPedWeaponSlot(clientElement, playerWeaponSlot)
+
+ return true
+ end
+
+ return false
+end
+
+function detachAttachedVehicleElements(vehicleElement, skipGlueTasks)
+ local vehicleType = isElementType(vehicleElement, "vehicle")
+
+ if (not vehicleType) then
+ return false
+ end
+
+ local vehicleAttachedElements = getAttachedElements(vehicleElement)
+ local vehicleDetachedElementCount = false
+
+ for attachedElementID = 1, #vehicleAttachedElements do
+ local vehicleAttachedElement = vehicleAttachedElements[attachedElementID]
+ local vehicleAttachedElementShouldDetach = isElementType(vehicleAttachedElement, GLUE_ALLOWED_ELEMENTS)
+
+ if (vehicleAttachedElementShouldDetach) then
+ local vehicleElementDetached = detachElements(vehicleAttachedElement, vehicleElement)
+
+ if (vehicleElementDetached) then
+ local vehicleDetachedElementCountNew = (vehicleDetachedElementCount or 0) + 1
+
+ vehicleDetachedElementCount = vehicleDetachedElementCountNew
+
+ if (not skipGlueTasks) then
+ performAttachDetachTasksForPlayer(vehicleAttachedElement, false)
+ end
+ end
+ end
+ end
+
+ return vehicleDetachedElementCount
+end
+
+function onServerVehicleAttachElement(attachElement, attachToElement, attachData, playerWeaponSlot)
+ local canAttachElement = canPlayerAttachElementToVehicle(client, attachElement, attachToElement)
+
+ if (not canAttachElement) then
+ return false
+ end
+
+ local attachDataProcessed = processAttachData(attachData)
+
+ if (not attachDataProcessed) then
+ return false
+ end
+
+ local attachX, attachY, attachZ = attachData[1], attachData[2], attachData[3]
+ local attachRX, attachRY, attachRZ = attachData[4], attachData[5], attachData[6]
+ local attachedElement = attachElements(attachElement, attachToElement, attachX, attachY, attachZ, attachRX, attachRY, attachRZ)
+
+ if (attachedElement) then
+ adjustPlayerWeaponSlot(client, attachElement, playerWeaponSlot)
+ performAttachDetachTasksForPlayer(attachElement, attachToElement)
+ end
+end
+addEvent("onServerVehicleAttachElement", true)
+addEventHandler("onServerVehicleAttachElement", root, onServerVehicleAttachElement)
+
+function onServerVehicleDetachElement(detachElement)
+ local canDetachElement, detachFromElement = canPlayerDetachElementFromVehicle(client, detachElement)
+
+ if (not canDetachElement) then
+ return false
+ end
+
+ local detachedElement = detachElements(detachElement, detachFromElement)
+
+ if (detachedElement) then
+ performAttachDetachTasksForPlayer(detachElement, false)
+ end
+end
+addEvent("onServerVehicleDetachElement", true)
+addEventHandler("onServerVehicleDetachElement", root, onServerVehicleDetachElement)
+
+function onServerVehicleDetachElements()
+ local vehicleToDetachElements = canPlayerDetachElementsFromVehicle(client)
+
+ if (not vehicleToDetachElements) then
+ return false
+ end
+
+ detachAttachedVehicleElements(vehicleToDetachElements)
+end
+addEvent("onServerVehicleDetachElements", true)
+addEventHandler("onServerVehicleDetachElements", root, onServerVehicleDetachElements)
+
+local function detachElementsOnVehicleDestroy()
+ detachAttachedVehicleElements(source)
+end
+addEventHandler("onElementDestroy", root, detachElementsOnVehicleDestroy)
+
+local function detachElementsOnVehicleExplode()
+ detachAttachedVehicleElements(source)
+end
+if (GLUE_DETACH_ON_VEHICLE_EXPLOSION) then
+ addEventHandler("onVehicleExplode", root, detachElementsOnVehicleExplode)
+end
+
+local function detachElementsOnResourceStop()
+ local vehiclesTable = getElementsByType("vehicle")
+
+ for vehicleID = 1, #vehiclesTable do
+ local vehicleElement = vehiclesTable[vehicleID]
+
+ detachAttachedVehicleElements(vehicleElement, true)
+ end
+end
+addEventHandler("onResourceStop", resourceRoot, detachElementsOnResourceStop)
+
+local function detachElementsOnDeath()
+ local attachToElement = getElementAttachedTo(source)
+
+ if (not attachToElement) then
+ return false
+ end
+
+ detachElements(source, attachToElement)
+ performAttachDetachTasksForPlayer(source, false)
+end
+
+do
+ for glueElementTypeID = 1, #GLUE_ALLOWED_ELEMENTS do
+ local glueElementType = GLUE_ALLOWED_ELEMENTS[glueElementTypeID]
+ local glueDetachEvent = GLUE_ELEMENT_TYPES_AND_EVENTS[glueElementType]
+
+ if (glueDetachEvent) then
+ addEventHandler(glueDetachEvent, root, detachElementsOnDeath)
+ end
+ end
+end
\ No newline at end of file
diff --git a/[gameplay]/glue/logic/SVehicleAttachLock.lua b/[gameplay]/glue/logic/SVehicleAttachLock.lua
new file mode 100644
index 000000000..5c86d3c83
--- /dev/null
+++ b/[gameplay]/glue/logic/SVehicleAttachLock.lua
@@ -0,0 +1,72 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+local vehicleAttachLock = {}
+
+function isVehicleAttachLocked(vehicleElement)
+ local vehicleAttachLocked = vehicleAttachLock[vehicleElement]
+
+ return vehicleAttachLocked
+end
+
+function onServerVehicleToggleAttachLock()
+ local vehicleToToggleLock = canPlayerToggleVehicleAttachLock(client)
+
+ if (not vehicleToToggleLock) then
+ return false
+ end
+
+ local vehicleLockState = isVehicleAttachLocked(vehicleToToggleLock)
+ local vehicleLockStateNew = (not vehicleLockState)
+ local vehicleLockStateMessage = "You have toggled "..(vehicleLockStateNew and "#00ff00on" or "#ff0000off").."#ffffff vehicle attachment lock."
+
+ vehicleAttachLock[vehicleToToggleLock] = vehicleLockStateNew
+ sendGlueMessage(vehicleLockStateMessage, client, "**")
+end
+addEvent("onServerVehicleToggleAttachLock", true)
+addEventHandler("onServerVehicleToggleAttachLock", root, onServerVehicleToggleAttachLock)
+
+local function notifyAboutVehicleAttachLock(pPed)
+ local playerType = isElementType(pPed, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local vehicleDriver = getVehicleController(source)
+ local vehicleDriverMatching = (vehicleDriver == pPed)
+
+ if (not vehicleDriverMatching) then
+ return false
+ end
+
+ local vehicleAttachLocked = isVehicleAttachLocked(source)
+
+ if (not vehicleAttachLocked) then
+ return false
+ end
+
+ local vehicleAttachPlayerAllowed = findInTable(GLUE_ALLOWED_ELEMENTS, "player")
+ local vehicleAttachVehicleAllowed = findInTable(GLUE_ALLOWED_ELEMENTS, "vehicle")
+ local vehicleAttachPlayerHint = (vehicleAttachPlayerAllowed and "players can't glue to it" or "")
+ local vehicleAttachVehicleHint = (vehicleAttachVehicleAllowed and "can't be glued to other vehicles" or "")
+ local vehicleAttachSeparator = (vehicleAttachPlayerAllowed and vehicleAttachVehicleAllowed) and "/" or ""
+ local vehicleAttachDescription = (vehicleAttachPlayerAllowed or vehicleAttachVehicleAllowed) and " ("..vehicleAttachPlayerHint..vehicleAttachSeparator..vehicleAttachVehicleHint..")" or ""
+
+ local vehicleLockMessage = "Your vehicle is attach-locked"..vehicleAttachDescription..". Press '"..GLUE_MESSAGE_HIGHLIGHT_COLOR..GLUE_ATTACH_TOGGLE_KEY.."#ffffff' to unlock it."
+
+ sendGlueMessage(vehicleLockMessage, pPed, "**")
+end
+if (GLUE_ALLOW_ATTACH_TOGGLING) then
+ addEventHandler("onVehicleEnter", root, notifyAboutVehicleAttachLock)
+end
+
+local function clearVehicleAttachLock()
+ vehicleAttachLock[source] = nil
+end
+if (GLUE_ALLOW_ATTACH_TOGGLING) then
+ addEventHandler("onElementDestroy", root, clearVehicleAttachLock)
+end
\ No newline at end of file
diff --git a/[gameplay]/glue/logic/SVehicleAttachSync.lua b/[gameplay]/glue/logic/SVehicleAttachSync.lua
new file mode 100644
index 000000000..1ea8ea002
--- /dev/null
+++ b/[gameplay]/glue/logic/SVehicleAttachSync.lua
@@ -0,0 +1,102 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+local attachSyncTimer = false
+local attachSyncCorrection = {}
+
+local function correctAttachedPlayersPosition()
+ local attachVehiclesPosition = {}
+
+ for attachedPlayer, attachedToVehicle in pairs(attachSyncCorrection) do
+ local playerAttachedVehicleElement = isElement(attachedToVehicle)
+ local playerAttachedVehicleValid = false
+
+ if (playerAttachedVehicleElement) then
+ local playerAttachedToVehicle = getElementAttachedTo(attachedPlayer)
+ local playerAttachedVehicleMatching = (playerAttachedToVehicle and playerAttachedToVehicle == attachedToVehicle)
+
+ playerAttachedVehicleValid = playerAttachedVehicleMatching
+ end
+
+ if (playerAttachedVehicleValid) then
+ local attachVehiclePosition = attachVehiclesPosition[attachedToVehicle]
+
+ if (not attachVehiclePosition) then
+ local attachVehiclePosX, attachVehiclePosY, attachVehiclePosZ = getElementPosition(attachedToVehicle)
+ local attachVehiclePositionData = {
+ attachVehiclePosX,
+ attachVehiclePosY,
+ attachVehiclePosZ,
+ }
+
+ attachVehiclesPosition[attachedToVehicle] = attachVehiclePositionData
+ attachVehiclePosition = attachVehiclesPosition[attachedToVehicle]
+ end
+
+ local attachPositionWarp = false -- warp will reset player animations, we don't want that
+ local attachPosX, attachPosY, attachPosZ = attachVehiclePosition[1], attachVehiclePosition[2], attachVehiclePosition[3]
+
+ setElementPosition(attachedPlayer, attachPosX, attachPosY, attachPosZ, attachPositionWarp)
+ else
+ attachSyncCorrection[attachedPlayer] = nil
+ end
+ end
+
+ handleCorrectionSyncTimer()
+
+ return true
+end
+
+function handleCorrectionSyncTimer()
+ if (not GLUE_SYNC_CORRECTION) then
+ return false
+ end
+
+ local toggleOn = next(attachSyncCorrection)
+
+ if (toggleOn) then
+
+ if (attachSyncTimer) then
+ return false
+ end
+
+ attachSyncTimer = setTimer(correctAttachedPlayersPosition, GLUE_SYNC_CORRECTION_INTERVAL, 0)
+
+ return true
+ end
+
+ if (not toggleOn) then
+
+ if (not attachSyncTimer) then
+ return false
+ end
+
+ killTimer(attachSyncTimer)
+ attachSyncTimer = false
+
+ return true
+ end
+end
+
+function setGlueSyncCorrectionForPlayer(playerElement, attachVehicle)
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local attachSyncState = (attachVehicle or nil)
+
+ attachSyncCorrection[playerElement] = attachSyncState
+ handleCorrectionSyncTimer()
+
+ return true
+end
+
+local function clearSyncCorrectionData()
+ attachSyncCorrection[source] = nil
+end
+addEventHandler("onPlayerQuit", root, clearSyncCorrectionData)
\ No newline at end of file
diff --git a/[gameplay]/glue/logic/ShVehicleAttach.lua b/[gameplay]/glue/logic/ShVehicleAttach.lua
new file mode 100644
index 000000000..86f4f2311
--- /dev/null
+++ b/[gameplay]/glue/logic/ShVehicleAttach.lua
@@ -0,0 +1,397 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+function canPlayerAttachElementToVehicle(playerElement, attachElement, attachToElement)
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local playerDead = isPedDead(playerElement)
+
+ if (playerDead) then
+ return false
+ end
+
+ local allowedElementToAttach, allowedElementType = isElementType(attachElement, GLUE_ALLOWED_ELEMENTS)
+
+ if (not allowedElementToAttach) then
+ return false
+ end
+
+ local attachToElementVehicleType = isElementType(attachToElement, "vehicle")
+
+ if (not attachToElementVehicleType) then
+ return false
+ end
+
+ local attachToElementVehicleExploded = isVehicleBlown(attachToElement)
+
+ if (attachToElementVehicleExploded) then
+ return false
+ end
+
+ local attachElementPlayer = (allowedElementType == "player")
+ local attachElementVehicle = (allowedElementType == "vehicle")
+
+ if (attachElementPlayer) then
+ local attachPlayerSelf = (playerElement == attachElement)
+
+ if (not attachPlayerSelf) then
+ return false
+ end
+
+ local attachPlayerVehicle = getPedOccupiedVehicle(attachElement)
+
+ if (attachPlayerVehicle) then
+ return false
+ end
+
+ local attachPlayerPosX, attachPlayerPosY, attachPlayerPosZ = getElementPosition(attachElement)
+ local attachToVehiclePosX, attachToVehiclePosY, attachToVehiclePosZ = getElementPosition(attachToElement)
+ local attachPlayerDistance = getDistanceBetweenPoints3D(attachToVehiclePosX, attachToVehiclePosY, attachToVehiclePosZ, attachPlayerPosX, attachPlayerPosY, attachPlayerPosZ)
+ local attachPlayerCloseEnough = (attachPlayerDistance <= GLUE_ATTACH_PLAYER_MAX_DISTANCE)
+
+ if (not attachPlayerCloseEnough) then
+ return false
+ end
+
+ local attachPlayerInterior = getElementInterior(attachElement)
+ local attachPlayerDimension = getElementDimension(attachElement)
+ local attachToVehicleInterior = getElementInterior(attachToElement)
+ local attachToVehicleDimension = getElementDimension(attachToElement)
+ local attachPlayerWorldMatching = (attachPlayerInterior == attachToVehicleInterior) and (attachPlayerDimension == attachToVehicleDimension)
+
+ if (not attachPlayerWorldMatching) then
+ return false
+ end
+
+ if (IS_SERVER) then
+ local vehicleAttachLocked = isVehicleAttachLocked(attachToElement)
+
+ if (vehicleAttachLocked) then
+ local vehicleAttachLockMessage = "The vehicle you are trying to glue is attach locked."
+
+ sendGlueMessage(vehicleAttachLockMessage, playerElement, "**")
+
+ return false
+ end
+ end
+
+ return true
+ end
+
+ if (attachElementVehicle) then
+ local vehicleAttachDifferent = (attachElement ~= attachToElement)
+
+ if (not vehicleAttachDifferent) then
+ return false
+ end
+
+ local vehicleAttachElementExploded = isVehicleBlown(attachElement)
+
+ if (vehicleAttachElementExploded) then
+ return false
+ end
+
+ local vehicleAttachedElement = getAttachedVehicle(attachToElement)
+
+ if (vehicleAttachedElement) then
+ return false
+ end
+
+ local vehicleAttachToType = getVehicleType(attachToElement)
+ local vehicleAttachToHelicopter = (vehicleAttachToType == "Helicopter")
+
+ if (not vehicleAttachToHelicopter) then
+ local vehicleAttachElementController = getVehicleController(attachElement)
+ local vehicleAttachElementDriver = (playerElement == vehicleAttachElementController)
+
+ if (not vehicleAttachElementDriver) then
+ return false
+ end
+ end
+
+ local attachVehiclePosX, attachVehiclePosY, attachVehiclePosZ = getElementPosition(attachElement)
+ local attachToVehiclePosX, attachToVehiclePosY, attachToVehiclePosZ = getElementPosition(attachToElement)
+ local attachVehicleDistance = getDistanceBetweenPoints3D(attachToVehiclePosX, attachToVehiclePosY, attachToVehiclePosZ, attachVehiclePosX, attachVehiclePosY, attachVehiclePosZ)
+ local attachVehicleCloseEnough = (attachVehicleDistance <= GLUE_ATTACH_VEHICLE_MAX_DISTANCE)
+
+ if (not attachVehicleCloseEnough) then
+ return false
+ end
+
+ local attachVehicleInterior = getElementInterior(attachElement)
+ local attachVehicleDimension = getElementDimension(attachElement)
+ local attachToVehicleInterior = getElementInterior(attachToElement)
+ local attachToVehicleDimension = getElementDimension(attachToElement)
+ local attachVehicleWorldMatching = (attachVehicleInterior == attachToVehicleInterior) and (attachVehicleDimension == attachToVehicleDimension)
+
+ if (not attachVehicleWorldMatching) then
+ return false
+ end
+
+ local vehicleModel = getElementModel(attachElement)
+ local vehicleModelType = getVehicleType(attachElement)
+ local vehicleModelAllowed = GLUE_VEHICLE_TYPES[vehicleModelType] or GLUE_VEHICLE_WHITELIST[vehicleModel]
+
+ if (not vehicleModelAllowed) then
+ local vehicleModelMessage = "This vehicle model or type can't be attached."
+
+ sendGlueMessage(vehicleModelMessage, playerElement, "**")
+
+ return false
+ end
+
+ if (IS_SERVER) then
+ local vehicleAttachLocked = isVehicleAttachLocked(attachElement)
+ local vehicleAttachToLocked = isVehicleAttachLocked(attachToElement)
+
+ if (vehicleAttachLocked or vehicleAttachToLocked) then
+ local vehicleAttachLockMessage = "The vehicle you are in/nearby is attach locked and can't be glued."
+
+ sendGlueMessage(vehicleAttachLockMessage, playerElement, "**")
+
+ return false
+ end
+ end
+ end
+
+ return true
+end
+
+function canPlayerDetachElementFromVehicle(playerElement, detachElement)
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local playerDead = isPedDead(playerElement)
+
+ if (playerDead) then
+ return false
+ end
+
+ local allowedElement, allowedElementType = isElementType(detachElement, GLUE_ALLOWED_ELEMENTS)
+
+ if (not allowedElement) then
+ return false
+ end
+
+ local attachToElement = getElementAttachedTo(detachElement)
+
+ if (not attachToElement) then
+ return false
+ end
+
+ local detachElementPlayerType = (allowedElementType == "player")
+ local detachElementVehicleType = (allowedElementType == "vehicle")
+
+ if (detachElementPlayerType) then
+ local detachPlayerSelf = (playerElement == detachElement)
+
+ if (not detachPlayerSelf) then
+ return false
+ end
+ end
+
+ if (detachElementVehicleType) then
+ local detachVehicleExploded = isVehicleBlown(detachElement)
+
+ if (detachVehicleExploded) then
+ return false
+ end
+
+ local vehicleAttachToType = getVehicleType(attachToElement)
+ local vehicleAttachToHelicopter = (vehicleAttachToType == "Helicopter")
+
+ if (not vehicleAttachToHelicopter) then
+ local vehicleController = getVehicleController(detachElement)
+ local vehicleDetachToDriver = (playerElement == vehicleController)
+
+ if (not vehicleDetachToDriver) then
+ return false
+ end
+ end
+ end
+
+ local playerAttachDetachDelayPassed = getOrSetPlayerDelay(playerElement, "attachDetach", GLUE_ATTACH_DETACH_DELAY)
+
+ if (not playerAttachDetachDelayPassed) then
+ return false
+ end
+
+ return true, attachToElement
+end
+
+function canPlayerDetachElementsFromVehicle(playerElement)
+ if (not GLUE_ALLOW_DETACHING_ELEMENTS) then
+ return false
+ end
+
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local playerDead = isPedDead(playerElement)
+
+ if (playerDead) then
+ return false
+ end
+
+ local playerVehicle = getPedOccupiedVehicle(playerElement)
+
+ if (not playerVehicle) then
+ return false
+ end
+
+ local playerVehicleDriver = getVehicleController(playerVehicle)
+ local playerVehicleDriverMatching = (playerElement == playerVehicleDriver)
+
+ if (not playerVehicleDriverMatching) then
+ return false
+ end
+
+ local playerDetachAllDelayPassed = getOrSetPlayerDelay(playerElement, "detachAll", GLUE_DETACH_ELEMENTS_DELAY)
+
+ if (not playerDetachAllDelayPassed) then
+ return false
+ end
+
+ if (IS_SERVER) then
+ local detachedElementCount = detachAttachedVehicleElements(playerVehicle)
+
+ if (detachedElementCount) then
+ local detachAllMessage = "You have detached ("..GLUE_MESSAGE_HIGHLIGHT_COLOR..detachedElementCount.."#ffffff) elements attached to your vehicle."
+
+ sendGlueMessage(detachAllMessage, playerElement, "**")
+ end
+ end
+
+ return true
+end
+
+function canPlayerToggleVehicleAttachLock(playerElement)
+ if (not GLUE_ALLOW_ATTACH_TOGGLING) then
+ return false
+ end
+
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local playerDead = isPedDead(playerElement)
+
+ if (playerDead) then
+ return false
+ end
+
+ local playerVehicle = getPedOccupiedVehicle(playerElement)
+
+ if (not playerVehicle) then
+ return false
+ end
+
+ local playerVehicleDriver = getVehicleController(playerVehicle)
+ local playerVehicleDriverMatching = (playerElement == playerVehicleDriver)
+
+ if (not playerVehicleDriverMatching) then
+ return false
+ end
+
+ local playerAttachLockToggleDelayPassed = getOrSetPlayerDelay(playerElement, "attachLock", GLUE_ATTACH_TOGGLE_DELAY)
+
+ if (not playerAttachLockToggleDelayPassed) then
+ return false
+ end
+
+ return playerVehicle
+end
+
+function getAttachedVehicle(vehicleElement)
+ local vehicleType = isElementType(vehicleElement, "vehicle")
+
+ if (not vehicleType) then
+ return false
+ end
+
+ local attachedElements = getAttachedElements(vehicleElement)
+
+ for attachedElementID = 1, #attachedElements do
+ local attachedElement = attachedElements[attachedElementID]
+ local attachedElementVehicle = isElementType(attachedElement, "vehicle")
+
+ if (attachedElementVehicle) then
+ return attachedElement
+ end
+ end
+
+ return false
+end
+
+function getNearestVehicleFromVehicle(vehicleElement)
+ local vehicleType = isElementType(vehicleElement, "vehicle")
+
+ if (not vehicleType) then
+ return false
+ end
+
+ local vehicleX, vehicleY, vehicleZ = getElementPosition(vehicleElement)
+ local vehicleInterior = getElementInterior(vehicleElement)
+ local vehicleDimension = getElementDimension(vehicleElement)
+ local vehiclesInRange = getElementsWithinRange(vehicleX, vehicleY, vehicleZ, GLUE_ATTACH_VEHICLE_MAX_DISTANCE, "vehicle", vehicleInterior, vehicleDimension)
+
+ for vehicleID = 1, #vehiclesInRange do
+ local vehicleNearby = vehiclesInRange[vehicleID]
+ local vehicleDifferent = (vehicleElement ~= vehicleNearby)
+
+ if (vehicleDifferent) then
+ local vehicleExploded = isVehicleBlown(vehicleElement)
+
+ if (not vehicleExploded) then
+ return vehicleNearby
+ end
+ end
+ end
+
+ return false
+end
+
+function getVehicleAttachData(attachVehicle, attachToVehicle)
+ local attachVehicleType = isElementType(attachVehicle, "vehicle")
+ local attachToVehicleType = isElementType(attachToVehicle, "vehicle")
+
+ if (not attachVehicleType or not attachToVehicleType) then
+ return false
+ end
+
+ local vehicleStartRX, vehicleStartRY, vehicleStartRZ = getElementRotation(attachVehicle)
+ local vehicleTargetRX, vehicleTargetRY, vehicleTargetRZ = getElementRotation(attachToVehicle)
+ local vehicleRX = (vehicleStartRX - vehicleTargetRX)
+ local vehicleRY = (vehicleStartRY - vehicleTargetRY)
+ local vehicleRZ = (vehicleStartRZ - vehicleTargetRZ)
+
+ if (GLUE_ATTACH_OVER_VEHICLE) then
+ local vehicleOffsetTopX = GLUE_ATTACH_ON_TOP_OFFSETS[1]
+ local vehicleOffsetTopY = GLUE_ATTACH_ON_TOP_OFFSETS[2]
+ local vehicleOffsetTopZ = GLUE_ATTACH_ON_TOP_OFFSETS[3]
+
+ return vehicleOffsetTopX, vehicleOffsetTopY, vehicleOffsetTopZ, vehicleRX, vehicleRY, vehicleRZ
+ end
+
+ local vehicleMatrix = getElementMatrix(attachToVehicle)
+ local vehicleStartX, vehicleStartY, vehicleStartZ = getElementPosition(attachVehicle)
+ local vehiclePosition = {vehicleStartX, vehicleStartY, vehicleStartZ}
+ local vehicleAttachX, vehicleAttachY, vehicleAttachZ = getOffsetFromXYZ(vehicleMatrix, vehiclePosition)
+
+ return vehicleAttachX, vehicleAttachY, vehicleAttachZ, vehicleRX, vehicleRY, vehicleRZ
+end
\ No newline at end of file
diff --git a/[gameplay]/glue/meta.xml b/[gameplay]/glue/meta.xml
new file mode 100644
index 000000000..e5f666f8c
--- /dev/null
+++ b/[gameplay]/glue/meta.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/[gameplay]/glue/utility/CComplex.lua b/[gameplay]/glue/utility/CComplex.lua
new file mode 100644
index 000000000..ef08d8f13
--- /dev/null
+++ b/[gameplay]/glue/utility/CComplex.lua
@@ -0,0 +1,385 @@
+-- complex 0.3.0
+-- Lua 5.1
+
+-- 'complex' provides common tasks with complex numbers
+
+-- function complex.to( arg ); complex( arg )
+-- returns a complex number on success, nil on failure
+-- arg := number or { number,number } or ( "(-)" and/or "(+/-)i" )
+-- e.g. 5; {2,3}; "2", "2+i", "-2i", "2^2*3+1/3i"
+-- note: 'i' is always in the numerator, spaces are not allowed
+
+-- a complex number is defined as carthesic complex number
+-- complex number := { real_part, imaginary_part }
+-- this gives fast access to both parts of the number for calculation
+-- the access is faster than in a hash table
+-- the metatable is just a add on, when it comes to speed, one is faster using a direct function call
+
+-- https://luaforge.net/projects/LuaMatrix
+-- https://lua-users.org/wiki/ComplexNumbers
+
+-- Licensed under the same terms as Lua itself.
+
+---MODIFIED TO SUIT THE MTA SCRIPTING SYSTEM
+
+--/////////////--
+--// complex //--
+--/////////////--
+
+-- link to complex table
+complex = {}
+
+-- link to complex metatable
+local complex_meta = {}
+
+-- complex.to( arg )
+-- return a complex number on success
+-- return nil on failure
+local _retone = function() return 1 end
+local _retminusone = function() return -1 end
+function complex.to( num )
+ -- check for table type
+ if type( num ) == "table" then
+ -- check for a complex number
+ if getmetatable( num ) == complex_meta then
+ return num
+ end
+ local real,imag = tonumber( num[1] ),tonumber( num[2] )
+ if real and imag then
+ return setmetatable( { real,imag }, complex_meta )
+ end
+ return
+ end
+ -- check for number
+ local isnum = tonumber( num )
+ if isnum then
+ return setmetatable( { isnum,0 }, complex_meta )
+ end
+ if type( num ) == "string" then
+ -- check for real and complex
+ -- number chars [%-%+%*%^%d%./Ee]
+ local real,sign,imag = string.match( num, "^([%-%+%*%^%d%./Ee]*%d)([%+%-])([%-%+%*%^%d%./Ee]*)i$" )
+ if real then
+ if string.lower(string.sub(real,1,1)) == "e"
+ or string.lower(string.sub(imag,1,1)) == "e" then
+ return
+ end
+ if imag == "" then
+ if sign == "+" then
+ imag = _retone
+ else
+ imag = _retminusone
+ end
+ elseif sign == "+" then
+ imag = tonumber(imag)
+ else
+ imag = tonumber(sign..imag)
+ end
+ real = tonumber(real)
+ if real and imag then
+ return setmetatable( { real(),imag() }, complex_meta )
+ end
+ return
+ end
+ -- check for complex
+ imag = string.match( num,"^([%-%+%*%^%d%./Ee]*)i$" )
+ if imag then
+ if imag == "" then
+ return setmetatable( { 0,1 }, complex_meta )
+ elseif imag == "-" then
+ return setmetatable( { 0,-1 }, complex_meta )
+ end
+ if string.lower(string.sub(imag,1,1)) ~= "e" then
+ imag = tonumber(imag)
+ if imag then
+ return setmetatable( { 0,imag() }, complex_meta )
+ end
+ end
+ return
+ end
+ -- should be real
+ real = string.match( num,"^(%-*[%d%.][%-%+%*%^%d%./Ee]*)$" )
+ if real then
+ real = tonumber(real)
+ if real then
+ return setmetatable( { real(),0 }, complex_meta )
+ end
+ end
+ end
+end
+
+-- complex( arg )
+-- same as complex.to( arg )
+-- set __call behaviour of complex
+setmetatable( complex, { __call = function( _,num ) return complex.to( num ) end } )
+
+-- complex.new( real, complex )
+-- fast function to get a complex number, not invoking any checks
+function complex.new( ... )
+ return setmetatable( { ... }, complex_meta )
+end
+
+-- complex.type( arg )
+-- is argument of type complex
+function complex.type( arg )
+ if getmetatable( arg ) == complex_meta then
+ return "complex"
+ end
+end
+
+-- complex.convpolar( r, phi )
+-- convert polar coordinates ( r*e^(i*phi) ) to carthesic complex number
+-- r (radius) is a number
+-- phi (angle) must be in radians; e.g. [0 - 2pi]
+function complex.convpolar( radius, phi )
+ return setmetatable( { radius * math.cos( phi ), radius * math.sin( phi ) }, complex_meta )
+end
+
+-- complex.convpolardeg( r, phi )
+-- convert polar coordinates ( r*e^(i*phi) ) to carthesic complex number
+-- r (radius) is a number
+-- phi must be in degrees; e.g. [0° - 360°]
+function complex.convpolardeg( radius, phi )
+ phi = phi/180 * math.pi
+ return setmetatable( { radius * math.cos( phi ), radius * math.sin( phi ) }, complex_meta )
+end
+
+--// complex number functions only
+
+-- complex.tostring( cx [, formatstr] )
+-- to string or real number
+-- takes a complex number and returns its string value or real number value
+function complex.tostring( cx,formatstr )
+ local real,imag = cx[1],cx[2]
+ if formatstr then
+ if imag == 0 then
+ return string.format( formatstr, real )
+ elseif real == 0 then
+ return string.format( formatstr, imag ).."i"
+ elseif imag > 0 then
+ return string.format( formatstr, real ).."+"..string.format( formatstr, imag ).."i"
+ end
+ return string.format( formatstr, real )..string.format( formatstr, imag ).."i"
+ end
+ if imag == 0 then
+ return real
+ elseif real == 0 then
+ return ((imag==1 and "") or (imag==-1 and "-") or imag).."i"
+ elseif imag > 0 then
+ return real.."+"..(imag==1 and "" or imag).."i"
+ end
+ return real..(imag==-1 and "-" or imag).."i"
+end
+
+-- complex.print( cx [, formatstr] )
+-- print a complex number
+function complex.print( ... )
+ print( complex.tostring( ... ) )
+end
+
+-- complex.polar( cx )
+-- from complex number to polar coordinates
+-- output in radians; [-pi,+pi]
+-- returns r (radius), phi (angle)
+function complex.polar( cx )
+ return math.sqrt( cx[1]^2 + cx[2]^2 ), math.atan2( cx[2], cx[1] )
+end
+
+-- complex.polardeg( cx )
+-- from complex number to polar coordinates
+-- output in degrees; [-180°,180°]
+-- returns r (radius), phi (angle)
+function complex.polardeg( cx )
+ return math.sqrt( cx[1]^2 + cx[2]^2 ), math.atan2( cx[2], cx[1] ) / math.pi * 180
+end
+
+-- complex.mulconjugate( cx )
+-- multiply with conjugate, function returning a number
+function complex.mulconjugate( cx )
+ return cx[1]^2 + cx[2]^2
+end
+
+-- complex.abs( cx )
+-- get the absolute value of a complex number
+function complex.abs( cx )
+ return math.sqrt( cx[1]^2 + cx[2]^2 )
+end
+
+-- complex.get( cx )
+-- returns real_part, imaginary_part
+function complex.get( cx )
+ return cx[1],cx[2]
+end
+
+-- complex.set( cx, real, imag )
+-- sets real_part = real and imaginary_part = imag
+function complex.set( cx,real,imag )
+ cx[1],cx[2] = real,imag
+end
+
+-- complex.is( cx, real, imag )
+-- returns true if, real_part = real and imaginary_part = imag
+-- else returns false
+function complex.is( cx,real,imag )
+ if cx[1] == real and cx[2] == imag then
+ return true
+ end
+ return false
+end
+
+--// functions returning a new complex number
+
+-- complex.copy( cx )
+-- copy complex number
+function complex.copy( cx )
+ return setmetatable( { cx[1],cx[2] }, complex_meta )
+end
+
+-- complex.add( cx1, cx2 )
+-- add two numbers; cx1 + cx2
+function complex.add( cx1,cx2 )
+ return setmetatable( { cx1[1]+cx2[1], cx1[2]+cx2[2] }, complex_meta )
+end
+
+-- complex.sub( cx1, cx2 )
+-- subtract two numbers; cx1 - cx2
+function complex.sub( cx1,cx2 )
+ return setmetatable( { cx1[1]-cx2[1], cx1[2]-cx2[2] }, complex_meta )
+end
+
+-- complex.mul( cx1, cx2 )
+-- multiply two numbers; cx1 * cx2
+function complex.mul( cx1,cx2 )
+ return setmetatable( { cx1[1]*cx2[1] - cx1[2]*cx2[2],cx1[1]*cx2[2] + cx1[2]*cx2[1] }, complex_meta )
+end
+
+-- complex.mulnum( cx, num )
+-- multiply complex with number; cx1 * num
+function complex.mulnum( cx,num )
+ return setmetatable( { cx[1]*num,cx[2]*num }, complex_meta )
+end
+
+-- complex.div( cx1, cx2 )
+-- divide 2 numbers; cx1 / cx2
+function complex.div( cx1,cx2 )
+ -- get complex value
+ local val = cx2[1]^2 + cx2[2]^2
+ -- multiply cx1 with conjugate complex of cx2 and divide through val
+ return setmetatable( { (cx1[1]*cx2[1]+cx1[2]*cx2[2])/val,(cx1[2]*cx2[1]-cx1[1]*cx2[2])/val }, complex_meta )
+end
+
+-- complex.divnum( cx, num )
+-- divide through a number
+function complex.divnum( cx,num )
+ return setmetatable( { cx[1]/num,cx[2]/num }, complex_meta )
+end
+
+-- complex.pow( cx, num )
+-- get the power of a complex number
+function complex.pow( cx,num )
+ if math.floor( num ) == num then
+ if num < 0 then
+ local val = cx[1]^2 + cx[2]^2
+ cx = { cx[1]/val,-cx[2]/val }
+ num = -num
+ end
+ local real,imag = cx[1],cx[2]
+ for i = 2,num do
+ real,imag = real*cx[1] - imag*cx[2],real*cx[2] + imag*cx[1]
+ end
+ return setmetatable( { real,imag }, complex_meta )
+ end
+ -- we calculate the polar complex number now
+ -- since then we have the versatility to calc any potenz of the complex number
+ -- then we convert it back to a carthesic complex number, we loose precision here
+ local length,phi = math.sqrt( cx[1]^2 + cx[2]^2 )^num, math.atan2( cx[2], cx[1] )*num
+ return setmetatable( { length * math.cos( phi ), length * math.sin( phi ) }, complex_meta )
+end
+
+-- complex.sqrt( cx )
+-- get the first squareroot of a complex number, more accurate than cx^.5
+function complex.sqrt( cx )
+ local len = math.sqrt( cx[1]^2+cx[2]^2 )
+ local sign = (cx[2]<0 and -1) or 1
+ return setmetatable( { math.sqrt((cx[1]+len)/2), sign*math.sqrt((len-cx[1])/2) }, complex_meta )
+end
+
+-- complex.ln( cx )
+-- natural logarithm of cx
+function complex.ln( cx )
+ return setmetatable( { math.log(math.sqrt( cx[1]^2 + cx[2]^2 )),
+ math.atan2( cx[2], cx[1] ) }, complex_meta )
+end
+
+-- complex.exp( cx )
+-- exponent of cx (e^cx)
+function complex.exp( cx )
+ local expreal = math.exp(cx[1])
+ return setmetatable( { expreal*math.cos(cx[2]), expreal*math.sin(cx[2]) }, complex_meta )
+end
+
+-- complex.conjugate( cx )
+-- get conjugate complex of number
+function complex.conjugate( cx )
+ return setmetatable( { cx[1], -cx[2] }, complex_meta )
+end
+
+-- complex.round( cx [,idp] )
+-- round complex numbers, by default to 0 decimal points
+function complex.round( cx,idp )
+ local mult = 10^( idp or 0 )
+ return setmetatable( { math.floor( cx[1] * mult + 0.5 ) / mult,
+ math.floor( cx[2] * mult + 0.5 ) / mult }, complex_meta )
+end
+
+--// metatable functions
+
+complex_meta.__add = function( cx1,cx2 )
+ cx1,cx2 = complex.to( cx1 ),complex.to( cx2 )
+ return complex.add( cx1,cx2 )
+end
+complex_meta.__sub = function( cx1,cx2 )
+ cx1,cx2 = complex.to( cx1 ),complex.to( cx2 )
+ return complex.sub( cx1,cx2 )
+end
+complex_meta.__mul = function( cx1,cx2 )
+ cx1,cx2 = complex.to( cx1 ),complex.to( cx2 )
+ return complex.mul( cx1,cx2 )
+end
+complex_meta.__div = function( cx1,cx2 )
+ cx1,cx2 = complex.to( cx1 ),complex.to( cx2 )
+ return complex.div( cx1,cx2 )
+end
+complex_meta.__pow = function( cx,num )
+ if num == "*" then
+ return complex.conjugate( cx )
+ end
+ return complex.pow( cx,num )
+end
+complex_meta.__unm = function( cx )
+ return setmetatable( { -cx[1], -cx[2] }, complex_meta )
+end
+complex_meta.__eq = function( cx1,cx2 )
+ if cx1[1] == cx2[1] and cx1[2] == cx2[2] then
+ return true
+ end
+ return false
+end
+complex_meta.__tostring = function( cx )
+ return tostring( complex.tostring( cx ) )
+end
+complex_meta.__concat = function( cx,cx2 )
+ return tostring(cx)..tostring(cx2)
+end
+-- cx( cx, formatstr )
+complex_meta.__call = function( ... )
+ print( complex.tostring( ... ) )
+end
+complex_meta.__index = {}
+for k,v in pairs( complex ) do
+ complex_meta.__index[k] = v
+end
+
+--///////////////--
+--// chillcode //--
+--///////////////--
\ No newline at end of file
diff --git a/[gameplay]/glue/utility/CMatrix.lua b/[gameplay]/glue/utility/CMatrix.lua
new file mode 100644
index 000000000..f3fe4c1c0
--- /dev/null
+++ b/[gameplay]/glue/utility/CMatrix.lua
@@ -0,0 +1,1276 @@
+--[[
+ matrix v 0.2.8
+
+ Lua 5.1 compatible
+
+ 'matrix' provides a good selection of matrix functions.
+
+ With simple matrices this script is quite useful, though for more
+ exact calculations, one would probably use a program like Matlab instead.
+ Matrices of size 100x100 can still be handled very well.
+ The error for the determinant and the inverted matrix is around 10^-9
+ with a 100x100 matrix and an element range from -100 to 100.
+
+ Characteristics:
+
+ - functions called via matrix. should be able to handle
+ any table matrix of structure t[i][j] = value
+ - can handle a type of complex matrix
+ - can handle symbolic matrices. (Symbolic matrices cannot be
+ used with complex matrices.)
+ - arithmetic functions do not change the matrix itself
+ but build and return a new matrix
+ - functions are intended to be light on checks
+ since one gets a Lua error on incorrect use anyways
+ - uses mainly Gauss-Jordan elimination
+ - for Lua tables optimised determinant calculation (fast)
+ but not invoking any checks for special types of matrices
+ - vectors can be set up via vec1 = matrix{{ 1,2,3 }}^'T' or matrix{1,2,3}
+ - vectors can be multiplied scalar via num = vec1^'T' * vec2
+ where num will be a matrix with the result in mtx[1][1],
+ or use num = vec1:scalar( vec2 ), where num is a number
+
+ Sites:
+ https://luaforge.net/projects/LuaMatrix
+ https://lua-users.org/wiki/SimpleMatrix
+
+ Licensed under the same terms as Lua itself.
+
+ Developers:
+ Michael Lutz (chillcode)
+ David Manura https://lua-users.org/wiki/DavidManura
+
+ MODIFIED TO SUIT THE MTA SCRIPTING SYSTEM
+]]--
+
+--////////////
+--// matrix //
+--////////////
+
+matrix = {}
+
+-- access to the metatable we set at the end of the file
+local matrix_meta = {}
+
+-- access to the symbolic metatable
+local symbol_meta = {}; symbol_meta.__index = symbol_meta
+-- set up a symbol type
+local function newsymbol(o)
+ return setmetatable({tostring(o)}, symbol_meta)
+end
+
+--/////////////////////////////
+--// Get 'new' matrix object //
+--/////////////////////////////
+
+--// matrix:new ( rows [, comlumns [, value]] )
+-- if rows is a table then sets rows as matrix
+-- if rows is a table of structure {1,2,3} then it sets it as a vector matrix
+-- if rows and columns are given and are numbers, returns a matrix with size rowsxcolumns
+-- if num is given then returns a matrix with given size and all values set to num
+-- if rows is given as number and columns is "I", will return an identity matrix of size rowsxrows
+function matrix:new( rows, columns, value )
+ -- check for given matrix
+ if type( rows ) == "table" then
+ -- check for vector
+ if type(rows[1]) ~= "table" then -- expect a vector
+ return setmetatable( {{rows[1]},{rows[2]},{rows[3]}},matrix_meta )
+ end
+ return setmetatable( rows,matrix_meta )
+ end
+ -- get matrix table
+ local mtx = {}
+ value = value or 0
+ -- build identity matrix of given rows
+ if columns == "I" then
+ for i = 1,rows do
+ mtx[i] = {}
+ for j = 1,rows do
+ if i == j then
+ mtx[i][j] = 1
+ else
+ mtx[i][j] = 0
+ end
+ end
+ end
+ -- build new matrix
+ else
+ for i = 1,rows do
+ mtx[i] = {}
+ for j = 1,columns do
+ mtx[i][j] = value
+ end
+ end
+ end
+ -- return matrix with shared metatable
+ return setmetatable( mtx,matrix_meta )
+end
+
+--// matrix ( rows [, comlumns [, value]] )
+-- set __call behaviour of matrix
+-- for matrix( ... ) as matrix.new( ... )
+setmetatable( matrix, { __call = function( ... ) return matrix.new( ... ) end } )
+
+
+-- functions are designed to be light on checks
+-- so we get Lua errors instead on wrong input
+-- matrix. should handle any table of structure t[i][j] = value
+-- we always return a matrix with scripts metatable
+-- cause its faster than setmetatable( mtx, getmetatable( input matrix ) )
+
+--///////////////////////////////
+--// matrix 'matrix' functions //
+--///////////////////////////////
+
+--// for real, complx and symbolic matrices //--
+
+-- note: real and complex matrices may be added, subtracted, etc.
+-- real and symbolic matrices may also be added, subtracted, etc.
+-- but one should avoid using symbolic matrices with complex ones
+-- since it is not clear which metatable then is used
+
+--// matrix.add ( m1, m2 )
+-- Add 2 matrices; m2 may be of bigger size than m1
+function matrix.add( m1, m2 )
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = m1[i][j] + m2[i][j]
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.sub ( m1 ,m2 )
+-- Subtract 2 matrices; m2 may be of bigger size than m1
+function matrix.sub( m1, m2 )
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = m1[i][j] - m2[i][j]
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.mul ( m1, m2 )
+-- Multiply 2 matrices; m1 columns must be equal to m2 rows
+-- e.g. #m1[1] == #m2
+function matrix.mul( m1, m2 )
+ -- multiply rows with columns
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m2[1] do
+ local num = m1[i][1] * m2[1][j]
+ for n = 2,#m1[1] do
+ num = num + m1[i][n] * m2[n][j]
+ end
+ mtx[i][j] = num
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.div ( m1, m2 )
+-- Divide 2 matrices; m1 columns must be equal to m2 rows
+-- m2 must be square, to be inverted,
+-- if that fails returns the rank of m2 as second argument
+-- e.g. #m1[1] == #m2; #m2 == #m2[1]
+function matrix.div( m1, m2 )
+ local rank; m2,rank = matrix.invert( m2 )
+ if not m2 then return m2, rank end -- singular
+ return matrix.mul( m1, m2 )
+end
+
+--// matrix.mulnum ( m1, num )
+-- Multiply matrix with a number
+-- num may be of type 'number','complex number' or 'string'
+-- strings get converted to complex number, if that fails then to symbol
+function matrix.mulnum( m1, num )
+ if type(num) == "string" then
+ num = complex.to(num) or newsymbol(num)
+ end
+ local mtx = {}
+ -- multiply elements with number
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = m1[i][j] * num
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.divnum ( m1, num )
+-- Divide matrix by a number
+-- num may be of type 'number','complex number' or 'string'
+-- strings get converted to complex number, if that fails then to symbol
+function matrix.divnum( m1, num )
+ if type(num) == "string" then
+ num = complex.to(num) or newsymbol(num)
+ end
+ local mtx = {}
+ -- divide elements by number
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = m1[i][j] / num
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+
+--// for real and complex matrices only //--
+
+--// matrix.pow ( m1, num )
+-- Power of matrix; mtx^(num)
+-- num is an integer and may be negative
+-- m1 has to be square
+-- if num is negative and inverting m1 fails
+-- returns the rank of matrix m1 as second argument
+function matrix.pow( m1, num )
+ assert(num == math.floor(num), "exponent not an integer")
+ if num == 0 then
+ return matrix:new( #m1,"I" )
+ end
+ if num < 0 then
+ local rank; m1,rank = matrix.invert( m1 )
+ if not m1 then return m1, rank end -- singular
+ num = -num
+ end
+ local mtx = matrix.copy( m1 )
+ for i = 2,num do
+ mtx = matrix.mul( mtx,m1 )
+ end
+ return mtx
+end
+
+--// matrix.det ( m1 )
+-- Calculate the determinant of a matrix
+-- m1 needs to be square
+-- Can calc the det for symbolic matrices up to 3x3 too
+-- The function to calculate matrices bigger 3x3
+-- is quite fast and for matrices of medium size ~(100x100)
+-- and average values quite accurate
+-- here we try to get the nearest element to |1|, (smallest pivot element)
+-- os that usually we have |mtx[i][j]/subdet| > 1 or mtx[i][j];
+-- with complex matrices we use the complex.abs function to check if it is bigger or smaller
+local fiszerocomplex = function( cx ) return complex.is(cx,0,0) end
+local fiszeronumber = function( num ) return num == 0 end
+function matrix.det( m1 )
+
+ -- check if matrix is quadratic
+ assert(#m1 == #m1[1], "matrix not square")
+
+ local size = #m1
+
+ if size == 1 then
+ return m1[1][1]
+ end
+
+ if size == 2 then
+ return m1[1][1]*m1[2][2] - m1[2][1]*m1[1][2]
+ end
+
+ if size == 3 then
+ return ( m1[1][1]*m1[2][2]*m1[3][3] + m1[1][2]*m1[2][3]*m1[3][1] + m1[1][3]*m1[2][1]*m1[3][2]
+ - m1[1][3]*m1[2][2]*m1[3][1] - m1[1][1]*m1[2][3]*m1[3][2] - m1[1][2]*m1[2][1]*m1[3][3] )
+ end
+
+ --// no symbolic matrix supported below here
+
+ local fiszero, abs
+ if matrix.type( m1 ) == "complex" then
+ fiszero = fiszerocomplex
+ abs = complex.mulconjugate
+ else
+ fiszero = fiszeronumber
+ abs = math.abs
+ end
+
+ --// matrix is bigger than 3x3
+ -- get determinant
+ -- using Gauss elimination and Laplace
+ -- start eliminating from below better for removals
+ -- get copy of matrix, set initial determinant
+ local mtx = matrix.copy( m1 )
+ local det = 1
+ -- get det up to the last element
+ for j = 1,#mtx[1] do
+ -- get smallest element so that |factor| > 1
+ -- and set it as last element
+ local rows = #mtx
+ local subdet,xrow
+ for i = 1,rows do
+ -- get element
+ local e = mtx[i][j]
+ -- if no subdet has been found
+ if not subdet then
+ -- check if element it is not zero
+ if not fiszero(e) then
+ -- use element as new subdet
+ subdet,xrow = e,i
+ end
+ -- check for elements nearest to 1 or -1
+ elseif (not fiszero(e)) and math.abs(abs(e)-1) < math.abs(abs(subdet)-1) then
+ subdet,xrow = e,i
+ end
+ end
+ -- only cary on if subdet is found
+ if subdet then
+ -- check if xrow is the last row,
+ -- else switch lines and multiply det by -1
+ if xrow ~= rows then
+ mtx[rows],mtx[xrow] = mtx[xrow],mtx[rows]
+ det = -det
+ end
+ -- traverse all fields setting element to zero
+ -- we don't set to zero cause we don't use that column anymore then anyways
+ for i = 1,rows-1 do
+ -- factor is the dividor of the first element
+ -- if element is not already zero
+ if not fiszero( mtx[i][j] ) then
+ local factor = mtx[i][j]/subdet
+ -- update all remaining fields of the matrix, with value from xrow
+ for n = j+1,#mtx[1] do
+ mtx[i][n] = mtx[i][n] - factor * mtx[rows][n]
+ end
+ end
+ end
+ -- update determinant and remove row
+ if math.fmod( rows,2 ) == 0 then
+ det = -det
+ end
+ det = det * subdet
+ table.remove( mtx )
+ else
+ -- break here table det is 0
+ return det * 0
+ end
+ end
+ -- det ready to return
+ return det
+end
+
+--// matrix.dogauss ( mtx )
+-- Gauss elimination, Gauss-Jordan Method
+-- this function changes the matrix itself
+-- returns on success: true,
+-- returns on failure: false,'rank of matrix'
+
+-- locals
+-- checking here for the nearest element to 1 or -1; (smallest pivot element)
+-- this way the factor of the evolving number division should be > 1 or the divided number itself,
+-- what gives better results
+local setelementtosmallest = function( mtx,i,j,fiszero,fisone,abs )
+ -- check if element is one
+ if fisone(mtx[i][j]) then return true end
+ -- check for lowest value
+ local _ilow
+ for _i = i,#mtx do
+ local e = mtx[_i][j]
+ if fisone(e) then
+ break
+ end
+ if not _ilow then
+ if not fiszero(e) then
+ _ilow = _i
+ end
+ elseif (not fiszero(e)) and math.abs(abs(e)-1) < math.abs(abs(mtx[_ilow][j])-1) then
+ _ilow = _i
+ end
+ end
+ if _ilow then
+ -- switch lines if not input line
+ -- legal operation
+ if _ilow ~= i then
+ mtx[i],mtx[_ilow] = mtx[_ilow],mtx[i]
+ end
+ return true
+ end
+end
+local cxfiszero = function( cx ) return complex.is(cx,0,0) end
+local cxfsetzero = function( mtx,i,j ) complex.set(mtx[i][j],0,0) end
+local cxfisone = function( cx ) return complex.abs(cx) == 1 end
+local cxfsetone = function( mtx,i,j ) complex.set(mtx[i][j],1,0) end
+local numfiszero = function( num ) return num == 0 end
+local numfsetzero = function( mtx,i,j ) mtx[i][j] = 0 end
+local numfisone = function( num ) return math.abs(num) == 1 end
+local numfsetone = function( mtx,i,j ) mtx[i][j] = 1 end
+-- note: in --// ... //-- we have a way that does no divison,
+-- however with big number and matrices we get problems since we do no reducing
+function matrix.dogauss( mtx )
+ local fiszero,fsetzero,fisone,fsetone,abs
+ if matrix.type( mtx ) == "complex" then
+ fiszero = cxfiszero
+ fsetzero = cxfsetzero
+ fisone = cxfisone
+ fsetone = cxfsetone
+ abs = complex.mulconjugate
+ else
+ fiszero = numfiszero
+ fsetzero = numfsetzero
+ fisone = numfisone
+ fsetone = numfsetone
+ abs = math.abs
+ end
+ local rows,columns = #mtx,#mtx[1]
+ -- stairs left -> right
+ for j = 1,rows do
+ -- check if element can be setted to one
+ if setelementtosmallest( mtx,j,j,fiszero,fisone,abs ) then
+ -- start parsing rows
+ for i = j+1,rows do
+ -- check if element is not already zero
+ if not fiszero(mtx[i][j]) then
+ -- we may add x*otherline row, to set element to zero
+ -- tozero - x*mtx[j][j] = 0; x = tozero/mtx[j][j]
+ local factor = mtx[i][j]/mtx[j][j]
+ --// this should not be used although it does no division,
+ -- yet with big matrices (since we do no reducing and other things)
+ -- we get too big numbers
+ --local factor1,factor2 = mtx[i][j],mtx[j][j] //--
+ fsetzero(mtx,i,j)
+ for _j = j+1,columns do
+ --// mtx[i][_j] = mtx[i][_j] * factor2 - factor1 * mtx[j][_j] //--
+ mtx[i][_j] = mtx[i][_j] - factor * mtx[j][_j]
+ end
+ end
+ end
+ else
+ -- return false and the rank of the matrix
+ return false,j-1
+ end
+ end
+ -- stairs right <- left
+ for j = rows,1,-1 do
+ -- set element to one
+ -- do division here
+ local div = mtx[j][j]
+ for _j = j+1,columns do
+ mtx[j][_j] = mtx[j][_j] / div
+ end
+ -- start parsing rows
+ for i = j-1,1,-1 do
+ -- check if element is not already zero
+ if not fiszero(mtx[i][j]) then
+ local factor = mtx[i][j]
+ for _j = j+1,columns do
+ mtx[i][_j] = mtx[i][_j] - factor * mtx[j][_j]
+ end
+ fsetzero(mtx,i,j)
+ end
+ end
+ fsetone(mtx,j,j)
+ end
+ return true
+end
+
+--// matrix.invert ( m1 )
+-- Get the inverted matrix or m1
+-- matrix must be square and not singular
+-- on success: returns inverted matrix
+-- on failure: returns nil,'rank of matrix'
+function matrix.invert( m1 )
+ assert(#m1 == #m1[1], "matrix not square")
+ local mtx = matrix.copy( m1 )
+ local ident = setmetatable( {},matrix_meta )
+ if matrix.type( mtx ) == "complex" then
+ for i = 1,#m1 do
+ ident[i] = {}
+ for j = 1,#m1 do
+ if i == j then
+ ident[i][j] = complex.new( 1,0 )
+ else
+ ident[i][j] = complex.new( 0,0 )
+ end
+ end
+ end
+ else
+ for i = 1,#m1 do
+ ident[i] = {}
+ for j = 1,#m1 do
+ if i == j then
+ ident[i][j] = 1
+ else
+ ident[i][j] = 0
+ end
+ end
+ end
+ end
+ mtx = matrix.concath( mtx,ident )
+ local done,rank = matrix.dogauss( mtx )
+ if done then
+ return matrix.subm( mtx, 1,(#mtx[1]/2)+1,#mtx,#mtx[1] )
+ else
+ return nil,rank
+ end
+end
+
+--// matrix.sqrt ( m1 [,iters] )
+-- calculate the square root of a matrix using "DenmanBeavers square root iteration"
+-- condition: matrix rows == matrix columns; must have a invers matrix and a square root
+-- if called without additional arguments, the function finds the first nearest square root to
+-- input matrix, there are others but the error between them is very small
+-- if called with agument iters, the function will return the matrix by number of iterations
+-- the script returns:
+-- as first argument, matrix^.5
+-- as second argument, matrix^-.5
+-- as third argument, the average error between (matrix^.5)^2-inputmatrix
+-- you have to determin for yourself if the result is sufficent enough for you
+-- local average error
+local function get_abs_avg( m1, m2 )
+ local dist = 0
+ local abs = matrix.type(m1) == "complex" and complex.abs or math.abs
+ for i=1,#m1 do
+ for j=1,#m1[1] do
+ dist = dist + abs(m1[i][j]-m2[i][j])
+ end
+ end
+ -- norm by numbers of entries
+ return dist/(#m1*2)
+end
+-- square root function
+function matrix.sqrt( m1, iters )
+ assert(#m1 == #m1[1], "matrix not square")
+ iters = iters or math.huge
+ local y = matrix.copy( m1 )
+ local z = matrix(#y, 'I')
+ local dist = math.huge
+ -- iterate, and get the average error
+ for n=1,iters do
+ local lasty,lastz = y,z
+ -- calc square root
+ -- y, z = (1/2)*(y + z^-1), (1/2)*(z + y^-1)
+ y, z = matrix.divnum((matrix.add(y,matrix.invert(z))),2),
+ matrix.divnum((matrix.add(z,matrix.invert(y))),2)
+ local dist1 = get_abs_avg(y,lasty)
+ if iters == math.huge then
+ if dist1 >= dist then
+ return lasty,lastz,get_abs_avg(matrix.mul(lasty,lasty),m1)
+ end
+ end
+ dist = dist1
+ end
+ return y,z,get_abs_avg(matrix.mul(y,y),m1)
+end
+
+--// matrix.root ( m1, root [,iters] )
+-- calculate any root of a matrix
+-- source: https://www.dm.unipi.it/~cortona04/slides/bruno.pdf
+-- m1 and root have to be given;(m1 = matrix, root = number)
+-- conditions same as matrix.sqrt
+-- returns same values as matrix.sqrt
+function matrix.root( m1, root, iters )
+ assert(#m1 == #m1[1], "matrix not square")
+ iters = iters or math.huge
+ local mx = matrix.copy( m1 )
+ local my = matrix.mul(mx:invert(),mx:pow(root-1))
+ local dist = math.huge
+ -- iterate, and get the average error
+ for n=1,iters do
+ local lastx,lasty = mx,my
+ -- calc root of matrix
+ --mx,my = ((p-1)*mx + my^-1)/p,
+ -- ((((p-1)*my + mx^-1)/p)*my^-1)^(p-2) *
+ -- ((p-1)*my + mx^-1)/p
+ mx,my = mx:mulnum(root-1):add(my:invert()):divnum(root),
+ my:mulnum(root-1):add(mx:invert()):divnum(root)
+ :mul(my:invert():pow(root-2)):mul(my:mulnum(root-1)
+ :add(mx:invert())):divnum(root)
+ local dist1 = get_abs_avg(mx,lastx)
+ if iters == math.huge then
+ if dist1 >= dist then
+ return lastx,lasty,get_abs_avg(matrix.pow(lastx,root),m1)
+ end
+ end
+ dist = dist1
+ end
+ return mx,my,get_abs_avg(matrix.pow(mx,root),m1)
+end
+
+
+--// Norm functions //--
+
+--// matrix.normf ( mtx )
+-- calculates the Frobenius norm of the matrix.
+-- ||mtx||_F = sqrt(SUM_{i,j} |a_{i,j}|^2)
+-- https://en.wikipedia.org/wiki/Frobenius_norm#Frobenius_norm
+function matrix.normf(mtx)
+ local mtype = matrix.type(mtx)
+ local result = 0
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ local e = mtx[i][j]
+ if mtype ~= "number" then e = e:abs() end
+ result = result + e^2
+ end
+ end
+ local sqrt = (type(result) == "number") and math.sqrt or result.sqrt
+ return sqrt(result)
+end
+
+--// matrix.normmax ( mtx )
+-- calculates the max norm of the matrix.
+-- ||mtx||_{max} = max{|a_{i,j}|}
+-- Does not work with symbolic matrices
+-- https://en.wikipedia.org/wiki/Frobenius_norm#Max_norm
+function matrix.normmax(mtx)
+ local abs = (matrix.type(mtx) == "number") and math.abs or mtx[1][1].abs
+ local result = 0
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ local e = abs(mtx[i][j])
+ if e > result then result = e end
+ end
+ end
+ return result
+end
+
+
+--// only for number and complex type //--
+-- Functions changing the matrix itself
+
+--// matrix.round ( mtx [, idp] )
+-- perform round on elements
+local numround = function( num,mult )
+ return math.floor( num * mult + 0.5 ) / mult
+end
+local tround = function( t,mult )
+ for i,v in ipairs(t) do
+ t[i] = math.floor( v * mult + 0.5 ) / mult
+ end
+ return t
+end
+function matrix.round( mtx, idp )
+ local mult = 10^( idp or 0 )
+ local fround = matrix.type( mtx ) == "number" and numround or tround
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ mtx[i][j] = fround(mtx[i][j],mult)
+ end
+ end
+ return mtx
+end
+
+--// matrix.random( mtx [,start] [, stop] [, idip] )
+-- fillmatrix with random values
+local numfill = function( _,start,stop,idp )
+ return math.random( start,stop ) / idp
+end
+local tfill = function( t,start,stop,idp )
+ for i in ipairs(t) do
+ t[i] = math.random( start,stop ) / idp
+ end
+ return t
+end
+function matrix.random( mtx,start,stop,idp )
+ start, stop, idp = start or -10, stop or 10, idp or 1
+ local ffill = matrix.type( mtx ) == "number" and numfill or tfill
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ mtx[i][j] = ffill( mtx[i][j], start, stop, idp )
+ end
+ end
+ return mtx
+end
+
+
+--//////////////////////////////
+--// Object Utility Functions //
+--//////////////////////////////
+
+--// for all types and matrices //--
+
+--// matrix.type ( mtx )
+-- get type of matrix, normal/complex/symbol or tensor
+function matrix.type( mtx )
+ if type(mtx[1][1]) == "table" then
+ if complex.type(mtx[1][1]) then
+ return "complex"
+ end
+ if getmetatable(mtx[1][1]) == symbol_meta then
+ return "symbol"
+ end
+ return "tensor"
+ end
+ return "number"
+end
+
+-- local functions to copy matrix values
+local num_copy = function( num )
+ return num
+end
+local t_copy = function( t )
+ local newt = setmetatable( {}, getmetatable( t ) )
+ for i,v in ipairs( t ) do
+ newt[i] = v
+ end
+ return newt
+end
+
+--// matrix.copy ( m1 )
+-- Copy a matrix
+-- simple copy, one can write other functions oneself
+function matrix.copy( m1 )
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ local mtx = {}
+ for i = 1,#m1[1] do
+ mtx[i] = {}
+ for j = 1,#m1 do
+ mtx[i][j] = docopy( m1[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.transpose ( m1 )
+-- Transpose a matrix
+-- switch rows and columns
+function matrix.transpose( m1 )
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ local mtx = {}
+ for i = 1,#m1[1] do
+ mtx[i] = {}
+ for j = 1,#m1 do
+ mtx[i][j] = docopy( m1[j][i] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.subm ( m1, i1, j1, i2, j2 )
+-- Submatrix out of a matrix
+-- input: i1,j1,i2,j2
+-- i1,j1 are the start element
+-- i2,j2 are the end element
+-- condition: i1,j1,i2,j2 are elements of the matrix
+function matrix.subm( m1,i1,j1,i2,j2 )
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ local mtx = {}
+ for i = i1,i2 do
+ local _i = i-i1+1
+ mtx[_i] = {}
+ for j = j1,j2 do
+ local _j = j-j1+1
+ mtx[_i][_j] = docopy( m1[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.concath( m1, m2 )
+-- Concatenate 2 matrices, horizontal
+-- will return m1m2; rows have to be the same
+-- e.g.: #m1 == #m2
+function matrix.concath( m1,m2 )
+ assert(#m1 == #m2, "matrix size mismatch")
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ local mtx = {}
+ local offset = #m1[1]
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,offset do
+ mtx[i][j] = docopy( m1[i][j] )
+ end
+ for j = 1,#m2[1] do
+ mtx[i][j+offset] = docopy( m2[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.concatv ( m1, m2 )
+-- Concatenate 2 matrices, vertical
+-- will return m1
+-- m2
+-- columns have to be the same; e.g.: #m1[1] == #m2[1]
+function matrix.concatv( m1,m2 )
+ assert(#m1[1] == #m2[1], "matrix size mismatch")
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = docopy( m1[i][j] )
+ end
+ end
+ local offset = #mtx
+ for i = 1,#m2 do
+ local _i = i + offset
+ mtx[_i] = {}
+ for j = 1,#m2[1] do
+ mtx[_i][j] = docopy( m2[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.rotl ( m1 )
+-- Rotate Left, 90 degrees
+function matrix.rotl( m1 )
+ local mtx = matrix:new( #m1[1],#m1 )
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ for i = 1,#m1 do
+ for j = 1,#m1[1] do
+ mtx[#m1[1]-j+1][i] = docopy( m1[i][j] )
+ end
+ end
+ return mtx
+end
+
+--// matrix.rotr ( m1 )
+-- Rotate Right, 90 degrees
+function matrix.rotr( m1 )
+ local mtx = matrix:new( #m1[1],#m1 )
+ local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy
+ for i = 1,#m1 do
+ for j = 1,#m1[1] do
+ mtx[j][#m1-i+1] = docopy( m1[i][j] )
+ end
+ end
+ return mtx
+end
+
+-- local get_elemnts in string
+local get_tstr = function( t )
+ return "["..table.concat(t,",").."]"
+end
+local get_str = function( e )
+ return tostring(e)
+end
+-- local get_elemnts in string and formated
+local getf_tstr = function( t,fstr )
+ local tval = {}
+ for i,v in ipairs( t ) do
+ tval[i] = string.format( fstr,v )
+ end
+ return "["..table.concat(tval,",").."]"
+end
+local getf_cxstr = function( e,fstr )
+ return complex.tostring( e,fstr )
+end
+local getf_symstr = function( e,fstr )
+ return string.format( fstr,e[1] )
+end
+local getf_str = function( e,fstr )
+ return string.format( fstr,e )
+end
+
+--// matrix.tostring ( mtx, formatstr )
+-- tostring function
+function matrix.tostring( mtx, formatstr )
+ local ts = {}
+ local getstr
+ if formatstr then -- get str formatted
+ local mtype = matrix.type( mtx )
+ if mtype == "tensor" then getstr = getf_tstr
+ elseif mtype == "complex" then getstr = getf_cxstr
+ elseif mtype == "symbol" then getstr = getf_symstr
+ else getstr = getf_str end
+ -- iteratr
+ for i = 1,#mtx do
+ local tstr = {}
+ for j = 1,#mtx[1] do
+ tstr[j] = getstr(mtx[i][j],formatstr)
+ end
+ ts[i] = table.concat(tstr, "\t")
+ end
+ else
+ getstr = matrix.type( mtx ) == "tensor" and get_tstr or get_str
+ for i = 1,#mtx do
+ local tstr = {}
+ for j = 1,#mtx[1] do
+ tstr[j] = getstr(mtx[i][j])
+ end
+ ts[i] = table.concat(tstr, "\t")
+ end
+ end
+ return table.concat(ts, "\n")
+end
+
+--// matrix.print ( mtx [, formatstr] )
+-- print out the matrix, just calls tostring
+function matrix.print( ... )
+ print( matrix.tostring( ... ) )
+end
+
+--// matrix.latex ( mtx [, align] )
+-- LaTeX output
+function matrix.latex( mtx, align )
+ -- align : option to align the elements
+ -- c = center; l = left; r = right
+ -- \usepackage{dcolumn}; D{.}{,}{-1}; aligns number by . replaces it with ,
+ align = align or "c"
+ local str = "$\\left( \\begin{array}{"..string.rep( align, #mtx[1] ).."}\n"
+ local getstr = matrix.type( mtx ) == "tensor" and get_tstr or get_str
+ for i = 1,#mtx do
+ str = str.."\t"..getstr(mtx[i][1])
+ for j = 2,#mtx[1] do
+ str = str.." & "..getstr(mtx[i][j])
+ end
+ -- close line
+ if i == #mtx then
+ str = str.."\n"
+ else
+ str = str.." \\\\\n"
+ end
+ end
+ return str.."\\end{array} \\right)$"
+end
+
+
+--// Functions not changing the matrix
+
+--// matrix.rows ( mtx )
+-- return number of rows
+function matrix.rows( mtx )
+ return #mtx
+end
+
+--// matrix.columns ( mtx )
+-- return number of columns
+function matrix.columns( mtx )
+ return #mtx[1]
+end
+
+--// matrix.size ( mtx )
+-- get matrix size as string rows,columns
+function matrix.size( mtx )
+ if matrix.type( mtx ) == "tensor" then
+ return #mtx,#mtx[1],#mtx[1][1]
+ end
+ return #mtx,#mtx[1]
+end
+
+--// matrix.getelement ( mtx, i, j )
+-- return specific element ( row,column )
+-- returns element on success and nil on failure
+function matrix.getelement( mtx,i,j )
+ if mtx[i] and mtx[i][j] then
+ return mtx[i][j]
+ end
+end
+
+--// matrix.setelement( mtx, i, j, value )
+-- set an element ( i, j, value )
+-- returns 1 on success and nil on failure
+function matrix.setelement( mtx,i,j,value )
+ if matrix.getelement( mtx,i,j ) then
+ -- check if value type is number
+ mtx[i][j] = value
+ return 1
+ end
+end
+
+--// matrix.ipairs ( mtx )
+-- iteration, same for complex
+function matrix.ipairs( mtx )
+ local i,j,rows,columns = 1,0,#mtx,#mtx[1]
+ local function iter()
+ j = j + 1
+ if j > columns then -- return first element from next row
+ i,j = i + 1,1
+ end
+ if i <= rows then
+ return i,j
+ end
+ end
+ return iter
+end
+
+--///////////////////////////////
+--// matrix 'vector' functions //
+--///////////////////////////////
+
+-- a vector is defined as a 3x1 matrix
+-- get a vector; vec = matrix{{ 1,2,3 }}^'T'
+
+--// matrix.scalar ( m1, m2 )
+-- returns the Scalar Product of two 3x1 matrices (vectors)
+function matrix.scalar( m1, m2 )
+ return m1[1][1]*m2[1][1] + m1[2][1]*m2[2][1] + m1[3][1]*m2[3][1]
+end
+
+--// matrix.cross ( m1, m2 )
+-- returns the Cross Product of two 3x1 matrices (vectors)
+function matrix.cross( m1, m2 )
+ local mtx = {}
+ mtx[1] = { m1[2][1]*m2[3][1] - m1[3][1]*m2[2][1] }
+ mtx[2] = { m1[3][1]*m2[1][1] - m1[1][1]*m2[3][1] }
+ mtx[3] = { m1[1][1]*m2[2][1] - m1[2][1]*m2[1][1] }
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.len ( m1 )
+-- returns the Length of a 3x1 matrix (vector)
+function matrix.len( m1 )
+ return math.sqrt( m1[1][1]^2 + m1[2][1]^2 + m1[3][1]^2 )
+end
+
+--////////////////////////////////
+--// matrix 'complex' functions //
+--////////////////////////////////
+
+--// matrix.tocomplex ( mtx )
+-- we set now all elements to a complex number
+-- also set the metatable
+function matrix.tocomplex( mtx )
+ assert( matrix.type(mtx) == "number", "matrix not of type 'number'" )
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ mtx[i][j] = complex.to( mtx[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.remcomplex ( mtx )
+-- set the matrix elements to a number or complex number string
+function matrix.remcomplex( mtx )
+ assert( matrix.type(mtx) == "complex", "matrix not of type 'complex'" )
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ mtx[i][j] = complex.tostring( mtx[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.conjugate ( m1 )
+-- get the conjugate complex matrix
+function matrix.conjugate( m1 )
+ assert( matrix.type(m1) == "complex", "matrix not of type 'complex'" )
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = complex.conjugate( m1[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--/////////////////////////////////
+--// matrix 'symbol' functions //
+--/////////////////////////////////
+
+--// matrix.tosymbol ( mtx )
+-- set the matrix elements to symbolic values
+function matrix.tosymbol( mtx )
+ assert( matrix.type( mtx ) ~= "tensor", "cannot convert type 'tensor' to 'symbol'" )
+ for i = 1,#mtx do
+ for j = 1,#mtx[1] do
+ mtx[i][j] = newsymbol( mtx[i][j] )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.gsub( m1, from, to )
+-- perform gsub on all elements
+function matrix.gsub( m1,from,to )
+ assert( matrix.type( m1 ) == "symbol", "matrix not of type 'symbol'" )
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = newsymbol( string.gsub( m1[i][j][1],from,to ) )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.replace ( m1, ... )
+-- replace one letter by something else
+-- replace( "a",4,"b",7, ... ) will replace a with 4 and b with 7
+function matrix.replace( m1,... )
+ assert( matrix.type( m1 ) == "symbol", "matrix not of type 'symbol'" )
+ local tosub,args = {},{...}
+ for i = 1,#args,2 do
+ tosub[args[i]] = args[i+1]
+ end
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = newsymbol( string.gsub( m1[i][j][1], "%a", function( a ) return tosub[a] or a end ) )
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+--// matrix.solve ( m1 )
+-- solve; tries to solve a symbolic matrix to a number
+function matrix.solve( m1 )
+ assert( matrix.type( m1 ) == "symbol", "matrix not of type 'symbol'" )
+ local mtx = {}
+ for i = 1,#m1 do
+ mtx[i] = {}
+ for j = 1,#m1[1] do
+ mtx[i][j] = tonumber(m1[i][j][1]())
+ end
+ end
+ return setmetatable( mtx, matrix_meta )
+end
+
+function symbol_meta.__add(a,b)
+ return newsymbol(a .. "+" .. b)
+end
+
+function symbol_meta.__sub(a,b)
+ return newsymbol(a .. "-" .. b)
+end
+
+function symbol_meta.__mul(a,b)
+ return newsymbol("(" .. a .. ")*(" .. b .. ")")
+end
+
+function symbol_meta.__div(a,b)
+ return newsymbol("(" .. a .. ")/(" .. b .. ")")
+end
+
+function symbol_meta.__pow(a,b)
+ return newsymbol("(" .. a .. ")^(" .. b .. ")")
+end
+
+function symbol_meta.__eq(a,b)
+ return a[1] == b[1]
+end
+
+function symbol_meta.__tostring(a)
+ return a[1]
+end
+
+function symbol_meta.__concat(a,b)
+ return tostring(a) .. tostring(b)
+end
+
+function symbol_meta.abs(a)
+ return newsymbol("(" .. a[1] .. "):abs()")
+end
+
+function symbol_meta.sqrt(a)
+ return newsymbol("(" .. a[1] .. "):sqrt()")
+end
+
+--////////////////////////--
+--// METATABLE HANDLING //--
+--////////////////////////--
+
+--// MetaTable
+-- as we declaired on top of the page
+-- local/shared metatable
+-- matrix_meta
+
+-- note '...' is always faster than 'arg1,arg2,...' if it can be used
+
+-- Set add "+" behaviour
+matrix_meta.__add = function( ... )
+ return matrix.add( ... )
+end
+
+-- Set subtract "-" behaviour
+matrix_meta.__sub = function( ... )
+ return matrix.sub( ... )
+end
+
+-- Set multiply "*" behaviour
+matrix_meta.__mul = function( m1,m2 )
+ if getmetatable( m1 ) ~= matrix_meta then
+ return matrix.mulnum( m2,m1 )
+ elseif getmetatable( m2 ) ~= matrix_meta then
+ return matrix.mulnum( m1,m2 )
+ end
+ return matrix.mul( m1,m2 )
+end
+
+-- Set division "/" behaviour
+matrix_meta.__div = function( m1,m2 )
+ if getmetatable( m1 ) ~= matrix_meta then
+ return matrix.mulnum( matrix.invert(m2),m1 )
+ elseif getmetatable( m2 ) ~= matrix_meta then
+ return matrix.divnum( m1,m2 )
+ end
+ return matrix.div( m1,m2 )
+end
+
+-- Set unary minus "-" behavior
+matrix_meta.__unm = function( mtx )
+ return matrix.mulnum( mtx,-1 )
+end
+
+-- Set power "^" behaviour
+-- if opt is any integer number will do mtx^opt
+-- (returning nil if answer doesn't exist)
+-- if opt is 'T' then it will return the transpose matrix
+-- only for complex:
+-- if opt is '*' then it returns the complex conjugate matrix
+ local option = {
+ -- only for complex
+ ["*"] = function( m1 ) return matrix.conjugate( m1 ) end,
+ -- for both
+ ["T"] = function( m1 ) return matrix.transpose( m1 ) end,
+ }
+matrix_meta.__pow = function( m1, opt )
+ return option[opt] and option[opt]( m1 ) or matrix.pow( m1,opt )
+end
+
+-- Set equal "==" behaviour
+matrix_meta.__eq = function( m1, m2 )
+ -- check same type
+ if matrix.type( m1 ) ~= matrix.type( m2 ) then
+ return false
+ end
+ -- check same size
+ if #m1 ~= #m2 or #m1[1] ~= #m2[1] then
+ return false
+ end
+ -- check normal,complex and symbolic
+ for i = 1,#m1 do
+ for j = 1,#m1[1] do
+ if m1[i][j] ~= m2[i][j] then
+ return false
+ end
+ end
+ end
+ return true
+end
+
+-- Set tostring "tostring( mtx )" behaviour
+matrix_meta.__tostring = function( ... )
+ return matrix.tostring( ... )
+end
+
+-- set __call "mtx( [formatstr] )" behaviour, mtx [, formatstr]
+matrix_meta.__call = function( ... )
+ matrix.print( ... )
+end
+
+--// __index handling
+matrix_meta.__index = {}
+for k,v in pairs( matrix ) do
+ matrix_meta.__index[k] = v
+end
+
+--///////////////--
+--// chillcode //--
+--///////////////--
+
+function getOffsetFromXYZ(mat, vec)
+ mat[1][4] = 0
+ mat[2][4] = 0
+ mat[3][4] = 0
+ mat[4][4] = 1
+ mat = matrix.invert(mat)
+
+ local offX = vec[1] * mat[1][1] + vec[2] * mat[2][1] + vec[3] * mat[3][1] + mat[4][1]
+ local offY = vec[1] * mat[1][2] + vec[2] * mat[2][2] + vec[3] * mat[3][2] + mat[4][2]
+ local offZ = vec[1] * mat[1][3] + vec[2] * mat[2][3] + vec[3] * mat[3][3] + mat[4][3]
+
+ return offX, offY, offZ
+end
\ No newline at end of file
diff --git a/[gameplay]/glue/utility/ShPlayerDelays.lua b/[gameplay]/glue/utility/ShPlayerDelays.lua
new file mode 100644
index 000000000..ba7ed57dd
--- /dev/null
+++ b/[gameplay]/glue/utility/ShPlayerDelays.lua
@@ -0,0 +1,44 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+local playerDelays = {}
+
+function getOrSetPlayerDelay(playerElement, delayID, delayTime)
+ local playerType = isElementType(playerElement, "player")
+
+ if (not playerType) then
+ return false
+ end
+
+ local playerDelayData = playerDelays[playerElement]
+
+ if (not playerDelayData) then
+ playerDelays[playerElement] = {}
+ playerDelayData = playerDelays[playerElement]
+ end
+
+ local timeNow = getTickCount()
+ local playerActiveDelay = playerDelayData[delayID]
+
+ if (playerActiveDelay) then
+ local playerDelayPassed = (timeNow > playerActiveDelay)
+
+ if (not playerDelayPassed) then
+ return false
+ end
+ end
+
+ local playerDelayNewTime = (timeNow + delayTime)
+
+ playerDelayData[delayID] = playerDelayNewTime
+
+ return true
+end
+
+local function clearPlayersDelay()
+ playerDelays[source] = nil
+end
+addEventHandler(IS_SERVER and "onPlayerQuit" or "onClientPlayerQuit", root, clearPlayersDelay)
\ No newline at end of file
diff --git a/[gameplay]/glue/utility/ShUtility.lua b/[gameplay]/glue/utility/ShUtility.lua
new file mode 100644
index 000000000..d9555cb44
--- /dev/null
+++ b/[gameplay]/glue/utility/ShUtility.lua
@@ -0,0 +1,83 @@
+-- #######################################
+-- ## Project: Glue ##
+-- ## Author: MTA contributors ##
+-- ## Version: 1.3.1 ##
+-- #######################################
+
+function typeCheck(pData, ...)
+ local dataType = type(pData)
+
+ if (not ...) then
+ return dataType
+ end
+
+ local dataTypes = {...}
+
+ for typeID = 1, #dataTypes do
+ local allowedType = dataTypes[typeID]
+ local matchingType = (dataType == allowedType)
+
+ if (matchingType) then
+ return true
+ end
+ end
+
+ return false, dataType
+end
+
+function isElementType(pElement, ...)
+ local validElement = isElement(pElement)
+
+ if (not validElement) then
+ return false
+ end
+
+ local elementType = getElementType(pElement)
+ local elementsType = typeCheck(...)
+ local elementTable = (elementsType == "table")
+ local elementTypes = (elementTable and ... or {...})
+
+ for elementTypeID = 1, #elementTypes do
+ local allowedElementType = elementTypes[elementTypeID]
+ local matchingElementType = (elementType == allowedElementType)
+
+ if (matchingElementType) then
+ return true, elementType
+ end
+ end
+
+ return false, elementType
+end
+
+function findInTable(luaTable, searchFor)
+ for dataID = 1, #luaTable do
+ local tableData = luaTable[dataID]
+ local tableDataMatching = (tableData == searchFor)
+
+ if (tableDataMatching) then
+ return true
+ end
+ end
+
+ return false
+end
+
+function sendGlueMessage(glueMessage, glueMessageReceiver, glueMessagePrefix)
+ local glueMessageHasText = (glueMessage and glueMessage ~= "")
+
+ if (not glueMessageHasText) then
+ return false
+ end
+
+ local glueMessageFormatted = GLUE_MESSAGE_PREFIX_COLOR..(glueMessagePrefix or GLUE_MESSAGE_PREFIX).." #ffffff"..glueMessage
+
+ if (IS_SERVER) then
+ outputChatBox(glueMessageFormatted, glueMessageReceiver, 255, 255, 255, true)
+
+ return true
+ end
+
+ outputChatBox(glueMessageFormatted, 255, 255, 255, true)
+
+ return true
+end
\ No newline at end of file