diff --git a/init.lua b/init.lua index 533b065..f3c7b6e 100644 --- a/init.lua +++ b/init.lua @@ -103,10 +103,11 @@ Bridge.RegisterModule("Menu", Menu) Bridge.RegisterModule("Utility", cLib.Utility) Bridge.RegisterModule("Placeable", cLib.Placeable) --new --- Bridge.RegisterModule("Gizmo", cLib.Gizmo) --- Bridge.RegisterModule("Scaleform", cLib.Scaleform) --- Bridge.RegisterModule("PlaceableObject", cLib.PlaceableObject) --- +Bridge.RegisterModule("Gizmo", cLib.Gizmo) +Bridge.RegisterModule("Scaleform", cLib.Scaleform) +Bridge.RegisterModule("Raycast", cLib.Raycast) +Bridge.RegisterModule("PlaceableObject", cLib.PlaceableObject) + -- Fill the bridge tables with player data. diff --git a/lib/client/dataview.lua b/lib/client/dataview.lua new file mode 100644 index 0000000..df99803 --- /dev/null +++ b/lib/client/dataview.lua @@ -0,0 +1,154 @@ +local dataView = setmetatable({ + EndBig = ">", + EndLittle = "<", + Types = { + Int8 = { code = "i1" }, + Uint8 = { code = "I1" }, + Int16 = { code = "i2" }, + Uint16 = { code = "I2" }, + Int32 = { code = "i4" }, + Uint32 = { code = "I4" }, + Int64 = { code = "i8" }, + Uint64 = { code = "I8" }, + Float32 = { code = "f", size = 4 }, -- a float (native size) + Float64 = { code = "d", size = 8 }, -- a double (native size) + + LuaInt = { code = "j" }, -- a lua_Integer + UluaInt = { code = "J" }, -- a lua_Unsigned + LuaNum = { code = "n" }, -- a lua_Number + String = { code = "z", size = -1, }, -- zero terminated string + }, + + FixedTypes = { + String = { code = "c" }, -- a fixed-sized string with n bytes + Int = { code = "i" }, -- a signed int with n bytes + Uint = { code = "I" }, -- an unsigned int with n bytes + }, +}, { + __call = function(_, length) + return dataView.ArrayBuffer(length) + end +}) +dataView.__index = dataView + +--[[ Create an ArrayBuffer with a size in bytes --]] +function dataView.ArrayBuffer(length) + return setmetatable({ + blob = string.blob(length), + length = length, + offset = 1, + cangrow = true, + }, dataView) +end + +--[[ Wrap a non-internalized string --]] +function dataView.Wrap(blob) + return setmetatable({ + blob = blob, + length = blob:len(), + offset = 1, + cangrow = true, + }, dataView) +end + +--[[ Return the underlying bytebuffer --]] +function dataView:Buffer() return self.blob end +function dataView:ByteLength() return self.length end +function dataView:ByteOffset() return self.offset end +function dataView:SubView(offset, length) + return setmetatable({ + blob = self.blob, + length = length or self.length, + offset = 1 + offset, + cangrow = false, + }, dataView) +end + +--[[ Return the Endianness format character --]] +local function ef(big) return (big and dataView.EndBig) or dataView.EndLittle end + +--[[ Helper function for setting fixed datatypes within a buffer --]] +local function packblob(self, offset, value, code) + -- If cangrow is false the dataview represents a subview, i.e., a subset + -- of some other string view. Ensure the references are the same before + -- updating the subview + local packed = self.blob:blob_pack(offset, code, value) + if self.cangrow or packed == self.blob then + self.blob = packed + self.length = packed:len() + return true + else + return false + end +end + +--[[ + Create the API by using dataView.Types +--]] +for label,datatype in pairs(dataView.Types) do + if not datatype.size then -- cache fixed encoding size + datatype.size = string.packsize(datatype.code) + elseif datatype.size >= 0 and string.packsize(datatype.code) ~= datatype.size then + local msg = "Pack size of %s (%d) does not match cached length: (%d)" + error(msg:format(label, string.packsize(datatype.code), datatype.size)) + return nil + end + + dataView["Get" .. label] = function(self, offset, endian) + offset = offset or 0 + if offset >= 0 then + local o = self.offset + offset + local v,_ = self.blob:blob_unpack(o, ef(endian) .. datatype.code) + return v + end + return nil + end + + dataView["Set" .. label] = function(self, offset, value, endian) + if offset >= 0 and value then + local o = self.offset + offset + local v_size = (datatype.size < 0 and value:len()) or datatype.size + if self.cangrow or ((o + (v_size - 1)) <= self.length) then + if not packblob(self, o, value, ef(endian) .. datatype.code) then + error("cannot grow subview") + end + else + error("cannot grow dataview") + end + end + return self + end +end + +for label,datatype in pairs(dataView.FixedTypes) do + datatype.size = -1 -- Ensure cached encoding size is invalidated + + dataView["GetFixed" .. label] = function(self, offset, typelen, endian) + if offset >= 0 then + local o = self.offset + offset + if (o + (typelen - 1)) <= self.length then + local code = ef(endian) .. "c" .. tostring(typelen) + local v,_ = self.blob:blob_unpack(o, code) + return v + end + end + return nil -- Out of bounds + end + + dataView["SetFixed" .. label] = function(self, offset, typelen, value, endian) + if offset >= 0 and value then + local o = self.offset + offset + if self.cangrow or ((o + (typelen - 1)) <= self.length) then + local code = ef(endian) .. "c" .. tostring(typelen) + if not packblob(self, o, value, code) then + error("cannot grow subview") + end + else + error("cannot grow dataview") + end + end + return self + end +end + +return dataView \ No newline at end of file diff --git a/lib/client/wip_gizmo.lua b/lib/client/gizmo.lua similarity index 61% rename from lib/client/wip_gizmo.lua rename to lib/client/gizmo.lua index 2348b1c..b4612a2 100644 --- a/lib/client/wip_gizmo.lua +++ b/lib/client/gizmo.lua @@ -1,162 +1,8 @@ --- Credit: https://github.com/citizenfx/lua/blob/luaglm-dev/cfx/libs/scripts/examples/dataview.lua -local dataView = setmetatable({ - EndBig = ">", - EndLittle = "<", - Types = { - Int8 = { code = "i1" }, - Uint8 = { code = "I1" }, - Int16 = { code = "i2" }, - Uint16 = { code = "I2" }, - Int32 = { code = "i4" }, - Uint32 = { code = "I4" }, - Int64 = { code = "i8" }, - Uint64 = { code = "I8" }, - Float32 = { code = "f", size = 4 }, -- a float (native size) - Float64 = { code = "d", size = 8 }, -- a double (native size) - - LuaInt = { code = "j" }, -- a lua_Integer - UluaInt = { code = "J" }, -- a lua_Unsigned - LuaNum = { code = "n" }, -- a lua_Number - String = { code = "z", size = -1, }, -- zero terminated string - }, - - FixedTypes = { - String = { code = "c" }, -- a fixed-sized string with n bytes - Int = { code = "i" }, -- a signed int with n bytes - Uint = { code = "I" }, -- an unsigned int with n bytes - }, -}, { - __call = function(_, length) - return dataView.ArrayBuffer(length) - end -}) -dataView.__index = dataView - ---[[ Create an ArrayBuffer with a size in bytes --]] -function dataView.ArrayBuffer(length) - return setmetatable({ - blob = string.blob(length), - length = length, - offset = 1, - cangrow = true, - }, dataView) -end - ---[[ Wrap a non-internalized string --]] -function dataView.Wrap(blob) - return setmetatable({ - blob = blob, - length = blob:len(), - offset = 1, - cangrow = true, - }, dataView) -end - ---[[ Return the underlying bytebuffer --]] -function dataView:Buffer() return self.blob end -function dataView:ByteLength() return self.length end -function dataView:ByteOffset() return self.offset end -function dataView:SubView(offset, length) - return setmetatable({ - blob = self.blob, - length = length or self.length, - offset = 1 + offset, - cangrow = false, - }, dataView) -end - ---[[ Return the Endianness format character --]] -local function ef(big) return (big and dataView.EndBig) or dataView.EndLittle end - ---[[ Helper function for setting fixed datatypes within a buffer --]] -local function packblob(self, offset, value, code) - -- If cangrow is false the dataview represents a subview, i.e., a subset - -- of some other string view. Ensure the references are the same before - -- updating the subview - local packed = self.blob:blob_pack(offset, code, value) - if self.cangrow or packed == self.blob then - self.blob = packed - self.length = packed:len() - return true - else - return false - end -end - ---[[ - Create the API by using dataView.Types ---]] -for label,datatype in pairs(dataView.Types) do - if not datatype.size then -- cache fixed encoding size - datatype.size = string.packsize(datatype.code) - elseif datatype.size >= 0 and string.packsize(datatype.code) ~= datatype.size then - local msg = "Pack size of %s (%d) does not match cached length: (%d)" - error(msg:format(label, string.packsize(datatype.code), datatype.size)) - return nil - end - - dataView["Get" .. label] = function(self, offset, endian) - offset = offset or 0 - if offset >= 0 then - local o = self.offset + offset - local v,_ = self.blob:blob_unpack(o, ef(endian) .. datatype.code) - return v - end - return nil - end - - dataView["Set" .. label] = function(self, offset, value, endian) - if offset >= 0 and value then - local o = self.offset + offset - local v_size = (datatype.size < 0 and value:len()) or datatype.size - if self.cangrow or ((o + (v_size - 1)) <= self.length) then - if not packblob(self, o, value, ef(endian) .. datatype.code) then - error("cannot grow subview") - end - else - error("cannot grow dataview") - end - end - return self - end -end - -for label,datatype in pairs(dataView.FixedTypes) do - datatype.size = -1 -- Ensure cached encoding size is invalidated - - dataView["GetFixed" .. label] = function(self, offset, typelen, endian) - if offset >= 0 then - local o = self.offset + offset - if (o + (typelen - 1)) <= self.length then - local code = ef(endian) .. "c" .. tostring(typelen) - local v,_ = self.blob:blob_unpack(o, code) - return v - end - end - return nil -- Out of bounds - end - - dataView["SetFixed" .. label] = function(self, offset, typelen, value, endian) - if offset >= 0 and value then - local o = self.offset + offset - if self.cangrow or ((o + (typelen - 1)) <= self.length) then - local code = ef(endian) .. "c" .. tostring(typelen) - if not packblob(self, o, value, code) then - error("cannot grow subview") - end - else - error("cannot grow dataview") - end - end - return self - end -end - -- CREDITS -- Andyyy7666: https://github.com/overextended/ox_lib/pull/453 -- AvarianKnight: https://forum.cfx.re/t/allow-drawgizmo-to-be-used-outside-of-fxdk/5091845/8?u=demi-automatic -local dataview = dataView +local dataview = Require("lib/client/dataview.lua") local enableScale = false -- allow scaling mode. doesnt scale collisions and resets when physics are applied it seems local gizmoEnabled = false local currentMode = 'translate' @@ -165,6 +11,7 @@ local currentEntity -- FUNCTIONS + local function normalize(x, y, z) local length = math.sqrt(x * x + y * y + z * z) if length == 0 then @@ -173,11 +20,6 @@ local function normalize(x, y, z) return x / length, y / length, z / length end -local identityMatrix = { - vector3(1, 0, 0), - vector3(0, 1, 0), - vector3(0, 0, 1) -} local function makeEntityMatrix(entity) local f, r, u, a = GetEntityMatrix(entity) local view = dataview.ArrayBuffer(60) @@ -222,6 +64,14 @@ local function applyEntityMatrix(entity, view) ) end +function InBoundary(pos, boundary) + if not boundary then return false end + local x, y, z = table.unpack(pos) + local minX, minY, minZ = table.unpack(boundary.min) + local maxX, maxY, maxZ = table.unpack(boundary.max) + return x >= minX and x <= maxX and y >= minY and y <= maxY and z >= minZ and z <= maxZ +end + -- LOOPS function RegisterButtonHandler(button, onPressed, onReleased) @@ -234,14 +84,38 @@ function RegisterButtonHandler(button, onPressed, onReleased) end -local function gizmoLoop(entity, onConfirm, onUpdate, onCancel) +local function gizmoLoop(entity, onConfirm, onUpdate, onCancel, settings) + assert(entity, 'entity is required') entity = tonumber(entity) EnterCursorMode() - + settings = settings or {} + local allowedMats = settings.allowedMats + local maxDepth = settings.maxDepth or 10 + local outLine = false + if IsEntityAPed(entity) then SetEntityAlpha(entity, 200) - else - SetEntityDrawOutline(entity, true) + end + + if allowedMats or boundary then + CreateThread(function() + while gizmoEnabled do + local coords = GetEntityCoords(entity) + local destination = coords - vector3(0, 0, maxDepth) + local hit, _, _, _, materialHash = Raycast.ToCoords(coords, destination, 1, 4) + if hit and hit ~=1 and (allowedMats?[materialHash] or InBoundary(GetEntityCoords(entity), boundary)) then + if not inBounds then + inBounds = true + SetEntityDrawOutlineColor(0, 255, 0, 255) + SetEntityDrawOutline(entity, true) + end + elseif inBounds then + inBounds = false + SetEntityDrawOutline(entity, false) + end + Wait(500) + end + end) end while gizmoEnabled and DoesEntityExist(entity) do @@ -255,11 +129,11 @@ local function gizmoLoop(entity, onConfirm, onUpdate, onCancel) SetEntityCollision(entity, false, false) FreezeEntityPosition(entity, false) local matrixBuffer = makeEntityMatrix(entity) - local changed = DrawGizmo(matrixBuffer:Buffer(), 'Editor1') + local changed = Citizen.InvokeNative(0xEB2EDCA2, matrixBuffer:Buffer(), 'Editor1', Citizen.ReturnResultAnyway()) if changed then - print(GetEntityCoords(entity)) applyEntityMatrix(entity, matrixBuffer) - SetEntityCoords(entity, GetEntityCoords(entity)) + local coords = GetEntityCoords(entity) + SetEntityCoords(entity, coords.x, coords.y, coords.z, false, false, false, false) end if onUpdate and type(onUpdate) == 'function' then onUpdate(entity, GetEntityCoords(entity), GetEntityRotation(entity)) end @@ -291,7 +165,8 @@ local function gizmoLoop(entity, onConfirm, onUpdate, onCancel) local playerPos = GetEntityCoords(PlayerPedId()) local entityPos = GetFinalRenderedCamCoord() local depth = #(playerPos - entityPos) -- Vector3 distance - PlaceableObject.Create({spawned = entity}, nil, onConfirm, onUpdate, onCancel, {depth = depth}) + settings.depth = depth + PlaceableObject.Create({spawned = entity}, nil, onConfirm, onUpdate, onCancel, settings) end) --ctrl @@ -321,6 +196,20 @@ local function gizmoLoop(entity, onConfirm, onUpdate, onCancel) return true end +--=================================================== +-- █▀ █ █ ▄▀▀ █▄▀ ▀▄▀ ▄▀▄ █ █ █▄ █ ██▀ █ █ ██▄ +-- █▀ ▀▄█ ▀▄▄ █ █ █ ▀▄▀ ▀▄█ █ ▀█ █▄▄ ▀▄▀▄▀ █▄█ +--=================================================== +-- .--------. +-- | taktak | +-- |/-------' +-- .-. ,-. +-- ;oo oo; +-- / \| |/ \ +-- |. `. .' .| +-- `;.;' `;.;' +-- .-^-. .-^-. ko1 + -- local function textUILoop() -- CreateThread(function() -- while gizmoEnabled do @@ -343,11 +232,11 @@ end -- EXPORTS -local function useGizmo(entity, onConfirm, onUpdate, onCancel) +local function useGizmo(entity, onConfirm, onUpdate, onCancel, settings) gizmoEnabled = true currentEntity = entity -- textUILoop() - gizmoLoop(entity, onConfirm, onUpdate, onCancel) + gizmoLoop(entity, onConfirm, onUpdate, onCancel, settings) return { handle = entity, @@ -360,7 +249,7 @@ exports("useGizmo", useGizmo) Gizmo = {} local running = false -function Gizmo.UseGizmo(obj, onConfirm, onUpdate, onCancel) +function Gizmo.UseGizmo(obj, onConfirm, onUpdate, onCancel, settings) if running then return end running = true @@ -387,7 +276,7 @@ function Gizmo.UseGizmo(obj, onConfirm, onUpdate, onCancel) running = false end) - quit = useGizmo(obj, onConfirm, onUpdate, onCancel) + quit = useGizmo(obj, onConfirm, onUpdate, onCancel, settings) running = false end exports("UseGizmo", Gizmo.UseGizmo) diff --git a/lib/client/object_Placer.lua b/lib/client/object_Placer.lua index 5a59a7a..0c6e936 100644 --- a/lib/client/object_Placer.lua +++ b/lib/client/object_Placer.lua @@ -20,6 +20,7 @@ local function finishPlacing() end Placeable.PlaceObject = function(object, distance, snapToGround, allowedMats, offset) + distance = tonumber(distance or 10.0 ) if activePlacementProp then return end if not object then Prints.Error('no_prop_defined') end @@ -75,7 +76,6 @@ Placeable.PlaceObject = function(object, distance, snapToGround, allowedMats, of if IsControlJustReleased(0, 38) then if not outLine and (not allowedMats or allowedMats[materialHash]) and distCheck < checkDist then finishPlacing() - return coords, heading end end diff --git a/lib/client/wip_object_placer.lua b/lib/client/placeable_object.lua similarity index 65% rename from lib/client/wip_object_placer.lua rename to lib/client/placeable_object.lua index a849e6c..bfc684f 100644 --- a/lib/client/wip_object_placer.lua +++ b/lib/client/placeable_object.lua @@ -1,8 +1,10 @@ -Scaleform = Scaleform or Require("client/scaleform.lua") -Utility = Utility or Require("client/utility.lua") -Gizmo = Gizmo or Require("client/gizmo.lua") +Scaleform = Scaleform or Require("lib/client/scaleform.lua") +Utility = Utility or Require("lib/client/utility.lua") +Gizmo = Gizmo or Require("lib/client/gizmo.lua") +Raycast = Raycast or Require("lib/client/raycast.lua") PlaceableObject = PlaceableObject or {} + local data = { entity = nil, placing = false, @@ -21,6 +23,13 @@ function PlaceableObject.Eyetrace(depth, disableSphere) return target end +function PlaceableObject.InBoundary(pos, boundary) + if not boundary then return false end + local x, y, z = table.unpack(pos) + local minX, minY, minZ = table.unpack(boundary.min) + local maxX, maxY, maxZ = table.unpack(boundary.max) + return x >= minX and x <= maxX and y >= minY and y <= maxY and z >= minZ and z <= maxZ +end -- Creates a placeable object in the game world. -- @param model (string) The model of the object to create. -- @param onConfirm (function) [optional] The callback function to execute when the object is confirmed. @@ -31,18 +40,21 @@ end -- - depthMax (number) The maximum depth for the object placement. Default is 10.0. -- - rotationStep (number) The rotation step for the object. Default is 15.0. -- - depthStep (number) The depth step for the object. Default is 1.0. -function PlaceableObject.Create(data, model, onConfirm, onUpdate, onCancel, settings, config) +function PlaceableObject.Create(data, model, onConfirm, onUpdate, onCancel, settings) local settings = settings or {} local depthMin = settings.depthMin or 2.0 depthMax = settings.depthMax or 10.0 rotationStep = settings.rotationStep or 15.0 depthStep = settings.depthStep or 1.0 + disableSphere = settings.useSphere and false or true local depth = settings.depth or 10.0 - print(depth) data.placing = true data.paused = false local initialPos = nil local placeOnGround = true + local allowedMats = settings.allowedMats + local boundary = settings.boundary + local config = settings.config or {} --scaleform local scaleform = Scaleform.SetupInstructionalButtons({ @@ -58,39 +70,62 @@ function PlaceableObject.Create(data, model, onConfirm, onUpdate, onCancel, sett {type = "DRAW_INSTRUCTIONAL_BUTTONS"}, {type = "SET_BACKGROUND_COLOUR"}, }) + local heading = -GetEntityHeading(PlayerPedId()) + + local pos = PlaceableObject.Eyetrace(depth, disableSphere) + if not data.spawned and model then + --get player heading + local obj = Utility.CreateProp(model, pos, vector3(0, 0, heading), nil) + data.spawned = obj + end + assert(data.spawned, "Failed to create object") + SetEntityCollision(data.spawned, false, false) + SetEntityAlpha(data.spawned, 150, false) + + SetPedConfigFlag(PlayerPedId(), 146, true) + SetCanClimbOnEntity(data.spawned, false) + SetEntityCompletelyDisableCollision(data.spawned, true, false) + SetEntityNoCollisionEntity(PlayerPedId(), data.spawned, false) + local inBounds = true + if allowedMats or boundary then + CreateThread(function() + while data.placing do + local hit, _, coords, _, materialHash = Raycast.FromCamera(1, 4) + if hit and hit ~=1 and (allowedMats?[materialHash] or PlaceableObject.InBoundary(GetEntityCoords(data.spawned), boundary)) then + if not inBounds then + inBounds = true + SetEntityDrawOutlineColor(0, 255, 0, 255) + SetEntityDrawOutline(data.spawned, true) + end + elseif inBounds then + inBounds = false + SetEntityDrawOutline(data.spawned, false) + end + Wait(500) + end + end) + end + CreateThread(function() - local heading = -GetEntityHeading(PlayerPedId()) - - local pos = PlaceableObject.Eyetrace(depth) - if not data.spawned and model then - --get player heading - local obj = Utility.CreateProp(model, pos, vector3(0, 0, heading), nil) - data.spawned = obj - end - assert(data.spawned, "Failed to create object") - SetEntityCollision(data.spawned, false, false) - SetEntityAlpha(data.spawned, 150, false) - - SetPedConfigFlag(PlayerPedId(), 146, true) - SetCanClimbOnEntity(data.spawned, false) - SetEntityCompletelyDisableCollision(data.spawned, true, false) - SetEntityNoCollisionEntity(PlayerPedId(), data.spawned, false) - while data.placing do if data.paused then goto continue end DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0) DisableControlAction(0, 24, true) -- Attack DisableControlAction(0, 25, true) -- Aim DisableControlAction(0, 36, true) -- INPUT_DUCK. - - if IsControlJustPressed(0, 223) then -- left click - if onConfirm and type(onConfirm) == 'function' then onConfirm(data.spawned, pos, heading) end - data.placing = false + + if IsDisabledControlJustPressed(0, 223) then -- left click + if not inBounds then + Notify.SendNotify("You can't place this object here", "error", 5000) + elseif onConfirm then + onConfirm(data.spawned, pos, heading) + data.placing = false + end end - if IsControlJustPressed(0, 222) then -- right click + if IsDisabledControlJustPressed(0, 25) then -- right click data.placing = false - if onCancel and type(onCancel) == 'function' then + if onCancel then onCancel(data.spawned, pos, heading) else DeleteEntity(data.spawned) @@ -98,13 +133,13 @@ function PlaceableObject.Create(data, model, onConfirm, onUpdate, onCancel, sett return end - if IsControlPressed(0,19) then - placeOnGround = not (placeOnGround or false) + if IsControlJustPressed(0,19) then + placeOnGround = not placeOnGround end if IsControlJustPressed(0, 38) then -- TAB key to switch to gizmo data.placing = false - return Gizmo.UseGizmo(data.spawned, onConfirm, onUpdate, onCancel) + return Gizmo.UseGizmo(data.spawned, onConfirm, onUpdate, onCancel, settings) end if IsControlPressed(0,224) then -- left ctrl @@ -136,7 +171,7 @@ function PlaceableObject.Create(data, model, onConfirm, onUpdate, onCancel, sett end end - pos = PlaceableObject.Eyetrace(depth) + pos = PlaceableObject.Eyetrace(depth, disableSphere) if data.spawned then SetEntityCoords(data.spawned, pos.x, pos.y, pos.z) SetEntityHeading(data.spawned, heading) @@ -155,14 +190,6 @@ function PlaceableObject.Create(data, model, onConfirm, onUpdate, onCancel, sett end) end +return PlaceableObject -RegisterCommand("testers", function() - PlaceableObject.Create({}, "prop_beachflag_01", function(entity, pos, heading) - - end, function(entity, pos, heading) - - end, function(entity, pos, heading) - - end) -end) diff --git a/lib/client/raycast.lua b/lib/client/raycast.lua new file mode 100644 index 0000000..abc6bfe --- /dev/null +++ b/lib/client/raycast.lua @@ -0,0 +1,54 @@ +Raycast = {} + +--Rework of oxs Raycast system +function Raycast.GetForwardVector(rotation) + local camRot = rotation or GetFinalRenderedCamRot(2) + + -- Convert each component to radians + local rx = math.rad(camRot.x) + local ry = math.rad(camRot.y) + local rz = math.rad(camRot.z) + + -- Calculate sin and cos for each axis + local sx = math.sin(rx) + local cx = math.cos(rx) + local sy = math.sin(ry) + local cy = math.cos(ry) + local sz = math.sin(rz) + local cz = math.cos(rz) + + -- Create forward vector components + local x = -sz * math.abs(cx) + local y = cz * math.abs(cx) + local z = sx + + return vector3(x, y, z) +end + +function Raycast.ToCoords(startCoords, endCoords, flag, ignore) + local probe = StartShapeTestLosProbe(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, flag or 511, PlayerPedId(), ignore or 4) + local retval, entity, finalCoords, normals, material = 1, nil, nil, nil, nil + + local timeout = 500 + while retval == 1 and timeout > 0 do + retval, entity, finalCoords, normals, material = GetShapeTestResultIncludingMaterial(probe) + timeout = timeout - 1 + Wait(0) + end + return retval, entity, finalCoords, normals, material +end + +function Raycast.FromCamera(flags, ignore, distance) + local coords = GetFinalRenderedCamCoord() + distance = distance or 10 + local destination = coords + Raycast.GetForwardVector() * distance + local retval, entity, finalCoords, normals, material = Raycast.ToCoords(coords, destination, flags, ignore) + if retval ~= 1 then + print("Raycast hit max distance, trying again", coords, dist) + local newDest = destination - vector3(0, 0, 10) + return Raycast.ToCoords(destination, newDest, flags, ignore) + end + return retval, entity, finalCoords, normals, material +end + +return Raycast \ No newline at end of file diff --git a/lib/init.lua b/lib/init.lua index 91a1c9d..a6cb9d3 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -45,7 +45,7 @@ cLib = { Math = Math or Require("lib/shared/math.lua"), LA = LA or Require("lib/shared/la.lua"), Perlin = Perlin or Require("lib/shared/perlin.lua"), - Actions = Actions or Require("lib/shared/actions.lua"), + -- Actions = Actions or Require("lib/shared/actions.lua"), } exports('cLib', cLib) @@ -59,11 +59,11 @@ cLib.LootTables = LootTables or Require("lib/server/lootTables.lua") if IsDuplicityVersion() then return cLib end ::client:: --- cLib.Gizmo = Gizmo or Require("lib/client/gizmo.lua") --- cLib.Scaleform = Scaleform or Require("lib/client/scaleform.lua") +cLib.Gizmo = Gizmo or Require("lib/client/gizmo.lua") +cLib.Scaleform = Scaleform or Require("lib/client/scaleform.lua") cLib.Placeable = Placeable or Require("lib/client/object_placer.lua") cLib.Utility = Utility or Require("lib/client/utility.lua") --- cLib.PlaceableObject = PlaceableObject or Require("lib/client/object_placer.lua") - +cLib.PlaceableObject = PlaceableObject or Require("lib/client/placeable_object.lua") +cLib.Raycast = Raycast or Require("lib/client/raycast.lua") return cLib \ No newline at end of file diff --git a/lib/server/sqlHandler.lua b/lib/server/sqlHandler.lua index aa329c7..942bcf2 100644 --- a/lib/server/sqlHandler.lua +++ b/lib/server/sqlHandler.lua @@ -61,6 +61,8 @@ function SQL.Delete(tableName, where) MySQL.query.await(query) end -exports('SQL', SQL) +exports('SQL', function() + return SQL +end) return SQL \ No newline at end of file diff --git a/lib/shared/rebound_entities.lua b/lib/shared/rebound_entities.lua index 1f64f7a..9ef9238 100644 --- a/lib/shared/rebound_entities.lua +++ b/lib/shared/rebound_entities.lua @@ -1,61 +1,37 @@ --- -- -- -- -- -- -- -- -- -- --- ▄▀▀ █▄█ ▄▀▄ █▀▄ ██▀ █▀▄ -- --- ▄█▀ █ █ █▀█ █▀▄ █▄▄ █▄▀ -- --- -- -- -- -- -- -- -- -- -- local Entities = {} local isServer = IsDuplicityVersion() - local registeredFunctions = {} ReboundEntities = {} ---- Global Add function which fire when an entity is registered ---- @param func function = function(entityData: table) ---- @return number = ID function ReboundEntities.AddFunction(func) assert(func, "Func is nil") table.insert(registeredFunctions, func) return #registeredFunctions end ---- Remove a registered function ---- @param id number function ReboundEntities.RemoveFunction(id) - assert(id, "ID is nil") - assert(registeredFunctions[id], "ID not found") + assert(id and registeredFunctions[id], "Invalid ID") registeredFunctions[id] = false end ---- Internal: Fire all registered functions ---- @vararg any local function FireFunctions(...) - for k, v in pairs(registeredFunctions) do - if v then - v(...) - end + for _, func in pairs(registeredFunctions) do + print(func) + if func then func(...) end end end ---- Convert a table to an entityData object ---- @param entityData table = {model: string, position: vector3, id: string || nil} ---- @return table = {model: string, position: vector3, id: string, entity: number, isServer: boolean || nil} function ReboundEntities.Register(entityData) - assert(entityData, "Entity data is nil") - local position = entityData.position - assert(position, "Position is nil") - local id = entityData.id or Ids.CreateUniqueId(Entities) + assert(entityData and entityData.position, "Invalid entity data") + entityData.id = entityData.id or Ids.CreateUniqueId(Entities) entityData.isServer = isServer - entityData.id = tostring(id) - local entityData = setmetatable(entityData, { - __tostring = function() - return entityData.id - end - }) - Entities[id] = entityData + entityData.id = tostring(entityData.id) + setmetatable(entityData, { __tostring = function() return entityData.id end }) + Entities[entityData.id] = entityData FireFunctions(entityData) return entityData end --- Internal local function Unregister(id) Entities[id] = nil end @@ -64,68 +40,47 @@ function ReboundEntities.GetSyncData(entityData, key) return entityData[key] end ---- Get all entityDatas registered ---- @return table = {entityData: table} function ReboundEntities.GetAll() return Entities end ---- Get an entityData by its id ---- @param id string ---- @return table = {model: string, position: vector3, id: string, entity: number, isServer: boolean} function ReboundEntities.GetById(id) - id = tostring(id) - return Entities[id] + return Entities[tostring(id)] end ---- Get all entities by their model ---- @param model string ---- @return table = {entityData: table} function ReboundEntities.GetByModel(model) local entities = {} - for k, v in pairs(Entities) do - if v.model == model then - table.insert(entities, v) + for _, entity in pairs(Entities) do + if entity.model == model then + table.insert(entities, entity) end end return entities end ---- Get the closest entityData to a position ---- @param pos vector3 ---- @return table = {model: string, position: vector3, id: string, entity: number, isServer: boolean} function ReboundEntities.GetClosest(pos) - local closest = nil - local closestDist = 9999 - for k, v in pairs(Entities) do - local dist = #(pos - v.pos) + local closest, closestDist = nil, 9999 + for _, entity in pairs(Entities) do + local dist = #(pos - entity.position) if dist < closestDist then - closest = v - closestDist = dist + closest, closestDist = entity, dist end end return closest end ---- Get all entities within a radius of a position ---- @param pos vector3 ---- @param radius number ---- @return table = {entityData: table} function ReboundEntities.GetWithinRadius(pos, radius) local entities = {} - for k, v in pairs(Entities) do - local dist = #(pos - v.pos) - if dist < radius then - table.insert(entities, v) + for _, entity in pairs(Entities) do + if #(pos - entity.position) < radius then + table.insert(entities, entity) end end return entities end function ReboundEntities.SetOnSyncKeyChange(entityData, cb) - print("SetOnSyncKeyChange", entityData, cb) - local id = entityData.id or entityData - local data = ReboundEntities.GetById(id) + local data = ReboundEntities.GetById(entityData.id or entityData) assert(data, "Entity not found") data.onSyncKeyChange = cb end @@ -133,22 +88,12 @@ end exports('ReboundEntities', ReboundEntities) if not isServer then goto client end --- -- -- -- -- -- -- -- -- -- --- ▄▀▀ ██▀ █▀▄ █ █ ██▀ █▀▄ -- --- ▄█▀ █▄▄ █▀▄ ▀▄▀ █▄▄ █▀▄ -- --- -- -- -- -- -- -- -- -- -- ---- Create a rebound entity ---- @param entityData object = {model: string, position: vector3, rotation: vector3 || nil} ---- @param src number = Who to send to. Defaults: -1 ---- @return table = {model: string, position: vector3, rotation: vector3, id: string, entity: number, isServer: boolean} function ReboundEntities.Create(entityData, src) local entity = ReboundEntities.Register(entityData) - assert(entity, "Failed to register entity") - assert(entity.position, "Entity position is nil") + assert(entity and entity.position, "Invalid entity data") entity.rotation = entity.rotation or vector3(0, 0, 0) - src = src and tonumber(src) or -1 - TriggerClientEvent(GetCurrentResourceName() ..":client:CreateReboundEntity", src, entity) + TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntity", src or -1, entity) return entity end @@ -158,67 +103,44 @@ function ReboundEntities.SetCheckRestricted(entityData, cb) end function ReboundEntities.CheckRestricted(src, entityData) - src = tonumber(src) - if not entityData.restricted then return false end - if src == -1 then return true end - return entityData.restricted(src, entityData) or false + return entityData.restricted and entityData.restricted(tonumber(src), entityData) or false end function ReboundEntities.Refresh(src) - src= tonumber(src) - local list = ReboundEntities.GetAccessibleList(src) - TriggerClientEvent(GetCurrentResourceName() ..":client:CreateReboundEntities", src, list) + TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", tonumber(src), ReboundEntities.GetAccessibleList(src)) end ---- Delete a rebound entity ---- @param id string = The entity id: string || entityData: object ---- @return boolean function ReboundEntities.Delete(id) Unregister(id) - TriggerClientEvent(GetCurrentResourceName() ..":client:DeleteReboundEntity", -1, id) + TriggerClientEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntity", -1, id) end --- Create multiple rebound entities ---- @param entityDatas table = {entityData: object} ---- @param src number = Who to send to. Defaults: -1 ---- @return table = {entityData: object} function ReboundEntities.CreateMultiple(entityDatas, src, restricted) - src = src and tonumber(src) or -1 local _entityDatas = {} - for k, v in pairs(entityDatas) do - local entity = ReboundEntities.Register(v) - assert(entity, "Failed to register entity") - assert(entity.position, "Entity position is nil") + for _, data in pairs(entityDatas) do + local entity = ReboundEntities.Register(data) + assert(entity and entity.position, "Invalid entity data") entity.rotation = entity.rotation or vector3(0, 0, 0) - - if restricted then - ReboundEntities.SetCheckRestricted(entity, restricted) - end + if restricted then ReboundEntities.SetCheckRestricted(entity, restricted) end if not ReboundEntities.CheckRestricted(src, entity) then _entityDatas[entity.id] = entity end end - - TriggerClientEvent(GetCurrentResourceName() ..":client:CreateReboundEntities", src, _entityDatas) + TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", src or -1, _entityDatas) return _entityDatas end ---- Sets data to be synced to clients for a rebound entity ---- @param entityData object = {model: string, position: vector3, rotation: vector3, id: string, entity: number, isServer: boolean} ---- @param key string ---- @param value any function ReboundEntities.SetSyncData(entityData, key, value) entityData[key] = value if entityData.onSyncKeyChange then entityData.onSyncKeyChange(entityData, key, value) - end - TriggerClientEvent(GetCurrentResourceName() ..":client:SetReboundSyncData", -1, entityData.id, key, value) + end + TriggerClientEvent(GetCurrentResourceName() .. ":client:SetReboundSyncData", -1, entityData.id, key, value) end function ReboundEntities.GetAccessibleList(src) - src = tonumber(src) local list = {} - for k, data in pairs(ReboundEntities.GetAll()) do + for _, data in pairs(ReboundEntities.GetAll()) do if not ReboundEntities.CheckRestricted(src, data) then list[data.id] = data end @@ -227,15 +149,13 @@ function ReboundEntities.GetAccessibleList(src) end RegisterNetEvent("playerJoining", function() - local src = source - ReboundEntities.Refresh(src) + ReboundEntities.Refresh(source) end) AddEventHandler('onResourceStart', function(resource) if resource == GetCurrentResourceName() then Wait(1000) - local players = GetPlayers() - for k, src in pairs(players) do + for _, src in pairs(GetPlayers()) do ReboundEntities.Refresh(src) end end @@ -243,85 +163,45 @@ end) ::client:: if isServer then return ReboundEntities end --- -- -- -- -- -- -- -- -- -- --- ▄▀▀ █ █ ██▀ █▄ █ ▀█▀ -- --- ▀▄▄ █▄▄ █ █▄▄ █ ▀█ █ -- --- -- -- -- -- -- -- -- -- -- ---- Loads a model into memory ---- @param model : Model name || Model hash ---- @return modelHash : number function ReboundEntities.LoadModel(model) assert(model, "Model is nil") model = type(model) == "number" and model or GetHashKey(model) - local timeout = 100 RequestModel(model) - while not HasModelLoaded(model) and timeout > 0 do - timeout = timeout - 1 + for i = 1, 100 do + if HasModelLoaded(model) then return model end Wait(100) end - assert(timeout > 0, string.format("Failed to load model %s", model)) - return model + error(string.format("Failed to load model %s", model)) end ---- Spawns an entity ---- @param entityData : table = {model: string, position: vector3, rotation: vector3 || nil, onSpawn: function || nil} ---- @return entity : number function ReboundEntities.Spawn(entityData) local model = entityData.model local position = entityData.position assert(position, "Position is nil") local rotation = entityData.rotation or vector3(0, 0, 0) - local entity = nil - if model then - model = ReboundEntities.LoadModel(model) - entity = CreateObject(model, position.x, position.y, position.z, false, false, false) - SetEntityRotation(entity, rotation.x, rotation.y, rotation.z) - end - - local onSpawn = entityData.onSpawn - print(onSpawn) - if onSpawn then - onSpawn(entityData, entity) - end - -- dont forget to save the entity to the entityData table in the spawn loop + local entity = model and CreateObject(ReboundEntities.LoadModel(model), position.x, position.y, position.z, false, false, false) + if entity then SetEntityRotation(entity, rotation.x, rotation.y, rotation.z) end + if entityData.onSpawn then entity = entityData.onSpawn(entityData, entity) or entity end return entity, true end ---- Set the onSpawn callback for an entity. Triggered after the entity is created. ---- @param id : string || entityData : object ---- @param cb : function(entityData: object, entity: number) ---- @return success : boolean function ReboundEntities.SetOnSpawn(id, cb) - id = tostring(id) - local entity = ReboundEntities.GetById(id) + local entity = ReboundEntities.GetById(tostring(id)) assert(entity, "Entity not found") entity.onSpawn = cb return true end ---- Despawns an entity ---- @param entityData : entity || table = {entity: number, onDespawn: function || nil} function ReboundEntities.Despawn(entityData) - assert(entityData, "Entity data is nil") local entity = entityData.entity - local onDespawn = entityData.onDespawn - if onDespawn and type(onDespawn) == "function" then - onDespawn(entityData, entity) - end - if entity and DoesEntityExist(entity) then - DeleteEntity(entity) - end + if entityData.onDespawn then entityData.onDespawn(entityData, entity) end + if entity and DoesEntityExist(entity) then DeleteEntity(entity) end return nil, nil end ---- Set the onDespawn callback for an entity. Triggered before the entity is deleted. ---- @param id : string || entityData : object ---- @param cb : function(entityData: object, entity: number) ---- @return success : boolean function ReboundEntities.SetOnDespawn(id, cb) - id = tostring(id) - local entity = ReboundEntities.GetById(id) + local entity = ReboundEntities.GetById(tostring(id)) assert(entity, "Entity not found") entity.onDespawn = cb return true @@ -329,9 +209,6 @@ end local spawnLoopRunning = false ---- Internal function to spawn entities in a loop ---- @param distanceToCheck : number || nil ---- @param waitTime : number || nil function ReboundEntities.SpawnLoop(distanceToCheck, waitTime) if spawnLoopRunning then return end spawnLoopRunning = true @@ -339,18 +216,13 @@ function ReboundEntities.SpawnLoop(distanceToCheck, waitTime) waitTime = waitTime or 2500 CreateThread(function() while spawnLoopRunning do - for k, v in pairs(Entities) do - - if not v.restricted then - local pos = GetEntityCoords(PlayerPedId()) - local dist = #(pos - v.position) - if dist <= distanceToCheck and (not v.entity and not v.inRange) then - v.entity, v.inRange = ReboundEntities.Spawn(v) - elseif dist > distanceToCheck and (v.entity or v.inRange) then - v.entity, v.inRange = ReboundEntities.Despawn(v) - end - elseif v.entity then - v.entity = ReboundEntities.Despawn(v) + for _, entity in pairs(Entities) do + local pos = GetEntityCoords(PlayerPedId()) + local dist = #(pos - entity.position) + if dist <= distanceToCheck and not entity.entity then + entity.entity, entity.inRange = ReboundEntities.Spawn(entity) + elseif dist > distanceToCheck and entity.entity then + entity.entity, entity.inRange = ReboundEntities.Despawn(entity) end end Wait(waitTime) @@ -358,111 +230,86 @@ function ReboundEntities.SpawnLoop(distanceToCheck, waitTime) end) end ---- Get an entity by its entity number ---- @param entity : number ---- @return entityData : table function ReboundEntities.GetByEntity(entity) - for k, v in pairs(Entities) do - if v.entity == entity then - return v - end + for _, data in pairs(Entities) do + if data.entity == entity then return data end end end ---- Create a client side only, rebound entity ---- @param entityData : table = {model: string, position: vector3, rotation: vector3 || nil} ---- @return entity : table function ReboundEntities.CreateClient(entityData) local entity = ReboundEntities.Register(entityData) - if not entity then return end - ReboundEntities.SpawnLoop() + if entity then ReboundEntities.SpawnLoop() end return entity end ---- Delete a client side only, rebound entity ---- @param id : string || entityData : table ---- @return success : boolean function ReboundEntities.DeleteClient(id) - id = tostring(id) - local entityData = ReboundEntities.GetById(id) - local isServer = entityData.isServer - assert(not isServer, "Cannot delete server entity from client") - local entity = entityData.entity - if entity then - ReboundEntities.Despawn(entityData) - end + local entityData = ReboundEntities.GetById(tostring(id)) + assert(not entityData.isServer, "Cannot delete server entity from client") + if entityData.entity then ReboundEntities.Despawn(entityData) end Unregister(id) return true end ---- Create multiple client only, rebound entities ---- @param entityDatas : table = {entityData: table = {model: string, position: vector3, rotation: vector3 || nil}} ---- @return _entityDatas : objects function ReboundEntities.CreateMultipleClient(entityDatas) local _entityDatas = {} - for k, v in pairs(entityDatas) do - local entityData = ReboundEntities.CreateClient(v) - entityDatas[entityData.id] = entityData + for _, data in pairs(entityDatas) do + local entityData = ReboundEntities.CreateClient(data) + _entityDatas[entityData.id] = entityData end return _entityDatas end ---- Delete multiple client only, rebound entities ---- @param ids : table = {string || entityData: table} function ReboundEntities.DeleteMultipleClient(ids) - for k, v in pairs(ids) do - ReboundEntities.DeleteClient(v) + for _, id in pairs(ids) do + ReboundEntities.DeleteClient(id) end end --- -- -- -- internal -- -- -- -- local function DeleteFromServer(id) - id = tostring(id) - local entityData = ReboundEntities.GetById(id) - if not entityData then return end - entityData.isServer = nil - ReboundEntities.DeleteClient(id) + local entityData = ReboundEntities.GetById(tostring(id)) + if entityData then + entityData.isServer = nil + ReboundEntities.DeleteClient(id) + end end local function DeleteMultipleFromServer(ids) - for k, v in pairs(ids) do - DeleteFromServer(v) + for _, id in pairs(ids) do + DeleteFromServer(id) end end --- -- -- -- -- -- -- -- -- -- -RegisterNetEvent(GetCurrentResourceName() ..":client:CreateReboundEntity", function(entityData) +RegisterNetEvent(GetCurrentResourceName() .. ":client:CreateReboundEntity", function(entityData) ReboundEntities.CreateClient(entityData) end) -RegisterNetEvent(GetCurrentResourceName() ..":client:DeleteReboundEntity", function(id) +RegisterNetEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntity", function(id) DeleteFromServer(id) end) -RegisterNetEvent(GetCurrentResourceName() ..":client:CreateReboundEntities", function(entityDatas) +RegisterNetEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", function(entityDatas) ReboundEntities.CreateMultipleClient(entityDatas) end) -RegisterNetEvent(GetCurrentResourceName() ..":client:DeleteReboundEntities", function(ids) +RegisterNetEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntities", function(ids) DeleteMultipleFromServer(ids) end) -RegisterNetEvent(GetCurrentResourceName() ..":client:SetReboundSyncData", function(id, key, value) +RegisterNetEvent(GetCurrentResourceName() .. ":client:SetReboundSyncData", function(id, key, value) local entityData = ReboundEntities.GetById(id) - if not entityData then return end - entityData[key] = value - if entityData.onSyncKeyChange then - entityData.onSyncKeyChange(entityData, key, value) + if entityData then + entityData[key] = value + if entityData.onSyncKeyChange then + entityData.onSyncKeyChange(entityData, key, value) + end end end) AddEventHandler('onResourceStop', function(resource) if resource == GetCurrentResourceName() then spawnLoopRunning = false - for k, v in pairs(Entities) do - if v.entity then - DeleteEntity(tonumber(v.entity)) - end + for _, entity in pairs(Entities) do + if entity.entity then DeleteEntity(tonumber(entity.entity)) end end end end)