├── LICENSE ├── README.md ├── client ├── main.lua ├── menus.lua └── object.lua ├── fxmanifest.lua ├── server ├── commands.lua ├── db.lua ├── main.lua └── objects.lua ├── shared └── config.lua └── sql.sql /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Synced Object Spawner for FiveM 2 | 3 | ## requirements 4 | 5 | - ox_lib ([https://github.com/overextended/ox_lib](https://github.com/overextended/ox_lib)) 6 | - oxymsql ([https://github.com/overextended/oxmysql](https://github.com/overextended/oxmysql)) 7 | - object_gizmo ([https://github.com/Demigod916/object_gizmo/tree/main](https://github.com/Demigod916/object_gizmo/tree/main)) 8 | - OneSync 9 | 10 | ## Installation 11 | 12 | - make sure you meet all requirements listed above 13 | - drag and drop the resource into your `resources` directory 14 | - run the `sql.sql` file on your database 15 | - add the following to your `server.cfg` -> `start qw_objectspawner` 16 | 17 | ## Mentions 18 | 19 | - Thanks to the overextended team for the statebag wrapping logic which can be found here: ([statebag wrapper](https://github.com/overextended/ox_core/blob/main/client/utils.lua)) 20 | - Thanks to Demigod for the gizmo resource which can be found here: ([object gizmo](https://github.com/Demigod916/object_gizmo/tree/main)) 21 | 22 | ## Preview 23 | 24 | [https://streamable.com/0mlvxy](https://streamable.com/0mlvxy) 25 | 26 | ## Commands 27 | 28 | `/objectspawner` will open the menu -------------------------------------------------------------------------------- /client/main.lua: -------------------------------------------------------------------------------- 1 | local menus = require 'client.menus' 2 | 3 | RegisterNetEvent("objects:client:menu", function() 4 | if GetInvokingResource() then return end -- only allow this to be called from the server 5 | menus.homeMenu() 6 | end) 7 | -------------------------------------------------------------------------------- /client/menus.lua: -------------------------------------------------------------------------------- 1 | local obj = require 'client.object' 2 | 3 | local lib = lib 4 | local menus = {} 5 | 6 | local function newObject(sceneId) 7 | local input = lib.inputDialog('Synced Object', { 8 | { 9 | type = 'input', 10 | label = 'Object Name', 11 | required = true, 12 | }, 13 | }) 14 | 15 | local object = tostring(input[1]) 16 | 17 | if not IsModelInCdimage(joaat(object)) then 18 | lib.notify({ 19 | title = 'Object Spawner', 20 | description = ("The object \"%s\" is not in cd image, are you sure this exists?"):format(object), 21 | type = 'error' 22 | }) 23 | return 24 | end 25 | 26 | obj.previewObject(object, sceneId) 27 | end 28 | 29 | local function createNewScene() 30 | local input = lib.inputDialog('New Scene', { 31 | { 32 | type = 'input', 33 | label = 'Scene Name', 34 | icon = 'pencil', 35 | required = true, 36 | }, 37 | }) 38 | 39 | if not input then return lib.showContext('object_menu_main') end 40 | 41 | local name = tostring(input[1]) 42 | 43 | local newScene = lib.callback.await('objects:newScene', 100, name) 44 | 45 | if newScene then 46 | lib.notify({ 47 | title = 'Object Spawner', 48 | description = ('Scene %s created'):format(name), 49 | type = 'success' 50 | }) 51 | end 52 | end 53 | 54 | lib.registerContext({ 55 | id = 'object_menu_main', 56 | title = 'Synced Objects', 57 | options = { 58 | { 59 | title = 'Scenes', 60 | description = 'view scenes that have been created', 61 | icon = 'camera', 62 | onSelect = function() 63 | menus.viewAllScenes() 64 | end, 65 | }, 66 | { 67 | title = 'Create a New Scene', 68 | description = 'edit objects that have been placed', 69 | icon = 'plus', 70 | onSelect = function() 71 | createNewScene() 72 | end, 73 | }, 74 | }, 75 | }) 76 | 77 | function menus.homeMenu() 78 | lib.showContext('object_menu_main') 79 | end 80 | 81 | function menus.viewAllScenes() 82 | local allScenes = lib.callback.await('objects:getAllScenes', 100) 83 | 84 | if #allScenes == 0 then 85 | lib.notify({ 86 | title = 'Object Spawner', 87 | description = 'No scenes created', 88 | type = 'error' 89 | }) 90 | return 91 | end 92 | 93 | local options = {} 94 | 95 | for i = 1, #allScenes do 96 | local scene = allScenes[i] 97 | local count = scene.count 98 | local name = scene.name 99 | local id = scene.id 100 | 101 | options[#options+1] = { 102 | title = name, 103 | description = ('View Scene: %s (%s Objects)'):format(name, count), 104 | icon = 'camera', 105 | onSelect = function() 106 | menus.viewObjectsInScene(id, name) 107 | end, 108 | } 109 | end 110 | 111 | lib.registerContext({ 112 | id = 'object_menu_scenes', 113 | title = 'Scenes', 114 | menu = 'object_menu_main', 115 | options = options, 116 | }) 117 | 118 | lib.showContext('object_menu_scenes') 119 | end 120 | 121 | function menus.editConfirmMenu(insertId) 122 | local objects = ClientObjects 123 | local object = objects[insertId] 124 | if DoesEntityExist(object.handle) then 125 | SetEntityDrawOutline(object.handle, true) 126 | SetEntityDrawOutlineColor(255, 0, 0, 255) 127 | end 128 | lib.registerContext({ 129 | id = 'object_confirm_edit', 130 | title = ('Edit: %s'):format(object.model), 131 | onExit = function() 132 | if DoesEntityExist(object.handle) then 133 | SetEntityDrawOutline(object.handle, false) 134 | end 135 | end, 136 | options = { 137 | { 138 | title = 'Edit', 139 | icon = 'check', 140 | disabled = not DoesEntityExist(object.handle), 141 | onSelect = function() 142 | SetEntityDrawOutline(object.handle, false) 143 | obj.editPlaced(insertId) 144 | end, 145 | }, 146 | { 147 | title = 'Delete', 148 | icon = 'trash', 149 | disabled = not DoesEntityExist(object.handle), 150 | onSelect = function() 151 | SetEntityDrawOutline(object.handle, false) 152 | obj.removeObject(insertId) 153 | end, 154 | }, 155 | { 156 | title = 'TP To Entity', 157 | icon = 'arrows-to-circle', 158 | onSelect = function() 159 | if DoesEntityExist(object.handle) then 160 | SetEntityDrawOutline(object.handle, false) 161 | end 162 | SetEntityCoords(cache.ped, object.coords.x, object.coords.y, object.coords.z) 163 | end, 164 | } 165 | }, 166 | }) 167 | 168 | lib.showContext('object_confirm_edit') 169 | end 170 | 171 | local function getAllObjectsByScene(sceneId) 172 | local sceneObjects = {} 173 | for k, v in pairs(ClientObjects) do 174 | if v.sceneid == sceneId then 175 | sceneObjects[#sceneObjects+1] = v 176 | end 177 | end 178 | return sceneObjects 179 | end 180 | 181 | function menus.viewObjectsInScene(sceneId, sceneName) 182 | local sceneObjects = getAllObjectsByScene(sceneId) 183 | 184 | local options = {} 185 | 186 | options[#options+1] = { 187 | title = 'Add New Object', 188 | description = 'add a new object to this scene', 189 | icon = 'plus', 190 | onSelect = function() 191 | newObject(sceneId) 192 | end, 193 | } 194 | 195 | 196 | for i = 1, #sceneObjects do 197 | local object = sceneObjects[i] 198 | local model = object.model 199 | local fmtCoords = ('coords: %.3f, %.3f, %.3f'):format(object.coords.x, object.coords.y, object.coords.z) 200 | options[#options+1] = { 201 | title = model, 202 | description = fmtCoords, 203 | icon = 'object-ungroup', 204 | onSelect = function() 205 | menus.editConfirmMenu(object.id) 206 | end, 207 | } 208 | end 209 | 210 | lib.registerContext({ 211 | id = 'scene_object_menu', 212 | title = ('Scene: %s'):format(sceneName), 213 | menu = 'object_menu_scenes', 214 | options = options, 215 | }) 216 | 217 | lib.showContext('scene_object_menu') 218 | end 219 | 220 | return menus -------------------------------------------------------------------------------- /client/object.lua: -------------------------------------------------------------------------------- 1 | local obj = {} 2 | 3 | ClientObjects = {} 4 | 5 | local inObjectPreview = false 6 | local handle = nil 7 | 8 | ---removed an object from the database and the world 9 | ---@param insertId number 10 | function obj.removeObject(insertId) 11 | TriggerServerEvent('objects:server:removeObject', insertId) 12 | end 13 | 14 | ---edit an object in the database and the world 15 | ---@param insertId number 16 | function obj.editPlaced(insertId) 17 | local object = ClientObjects[insertId] 18 | local handle = object.handle 19 | local name = object.model 20 | 21 | if not DoesEntityExist(handle) then return end 22 | 23 | local data = exports.object_gizmo:useGizmo(handle) 24 | 25 | if data then 26 | local coords = data.position 27 | local rotation = data.rotation 28 | TriggerServerEvent('objects:server:updateObject', { 29 | model = name, 30 | x = ('%.3f'):format(coords.x), 31 | y = ('%.3f'):format(coords.y), 32 | z = ('%.3f'):format(coords.z), 33 | rx = ('%.3f'):format(rotation.x), 34 | ry = ('%.3f'):format(rotation.y), 35 | rz = ('%.3f'):format(rotation.z), 36 | insertId = insertId 37 | } ) 38 | end 39 | end 40 | 41 | ---spawn an object in the world 42 | ---@param model string 43 | ---@param sceneId number 44 | function obj.previewObject(model, sceneId) 45 | inObjectPreview = true 46 | 47 | lib.requestModel(joaat(model), 1000) 48 | 49 | handle = CreateObject(model, GetEntityCoords(cache.ped), false, true, true) 50 | 51 | SetEntityAlpha(handle, 200, false) 52 | SetEntityCollision(handle, false, false) 53 | FreezeEntityPosition(handle, true) 54 | 55 | lib.hideTextUI() 56 | lib.showTextUI( 57 | '[E] Place Object \n [G] Rotate 90 \n [L/R Arrow] Rotate Left/Right \n [Q] Cancel', { 58 | position = "left-center", 59 | }) 60 | 61 | CreateThread(function() 62 | while inObjectPreview do 63 | local hit, _, coords, _, _ = lib.raycast.cam(1, 4) 64 | if hit then 65 | SetEntityCoords(handle, coords.x, coords.y, coords.z) 66 | PlaceObjectOnGroundProperly(handle) 67 | 68 | -- Left 69 | if IsControlPressed(0, 174) then 70 | SetEntityHeading(handle, GetEntityHeading(handle) - 0.3) 71 | end 72 | 73 | -- Right 74 | if IsControlPressed(0, 175) then 75 | SetEntityHeading(handle, GetEntityHeading(handle) + 0.3) 76 | end 77 | 78 | -- G 79 | if IsControlJustPressed(0, 47) then 80 | SetEntityHeading(handle, GetEntityHeading(handle) + 90.0) 81 | end 82 | 83 | -- Q 84 | if IsControlJustPressed(0, 44) then 85 | lib.hideTextUI() 86 | DeleteEntity(handle) 87 | inObjectPreview = false 88 | end 89 | 90 | -- E 91 | if IsControlJustPressed(0, 38) then 92 | local rotation = GetEntityRotation(handle, 2) 93 | local heading = GetEntityHeading(handle) 94 | TriggerServerEvent('objects:server:newObject', { 95 | model = model, 96 | x = ('%.3f'):format(coords.x), 97 | y = ('%.3f'):format(coords.y), 98 | z = ('%.3f'):format(coords.z), 99 | rx = ('%.3f'):format(rotation.x), 100 | ry = ('%.3f'):format(rotation.y), 101 | rz = ('%.3f'):format(rotation.z), 102 | heading = heading, 103 | sceneid = sceneId 104 | }) 105 | 106 | lib.hideTextUI() 107 | SetEntityAsMissionEntity(handle, false, true) 108 | DeleteObject(handle) 109 | inObjectPreview = false 110 | handle = nil 111 | end 112 | end 113 | end 114 | end) 115 | end 116 | 117 | RegisterNetEvent("objects:client:addObject", function(object) 118 | ClientObjects[object.id] = object 119 | end) 120 | 121 | RegisterNetEvent("objects:client:removeObject", function(insertId) 122 | local object = ClientObjects[insertId] 123 | 124 | if not object then return end 125 | 126 | if DoesEntityExist(object.handle) then 127 | SetEntityAsMissionEntity(object.handle, false, true) 128 | DeleteObject(object.handle) 129 | end 130 | 131 | ClientObjects[insertId] = nil 132 | end) 133 | 134 | RegisterNetEvent("objects:client:updateObject", function(data) 135 | local coords = data.coords 136 | local rotation = data.rotation 137 | local object = ClientObjects[data.insertId] 138 | 139 | if not object then return end 140 | 141 | if not DoesEntityExist(object.handle) then 142 | object.coords = coords 143 | object.rotation = rotation 144 | return 145 | end 146 | 147 | SetEntityCoords(object.handle, coords.x, coords.y, coords.z, false) 148 | SetEntityRotation(object.handle, rotation.x, rotation.y, rotation.z, 2, true) 149 | FreezeEntityPosition(object.handle, true) 150 | end) 151 | 152 | RegisterNetEvent("objects:client:loadObjects", function(objects) 153 | ClientObjects = objects 154 | end) 155 | 156 | 157 | local function SpawnObject(payload) 158 | lib.requestModel(joaat(payload.model)) 159 | local obj = CreateObjectNoOffset(payload.model, payload.coords.x, payload.coords.y , payload.coords.z, false, true, true) 160 | SetEntityRotation(obj, payload.rotation.x, payload.rotation.y, payload.rotation.z, 2, true) 161 | FreezeEntityPosition(obj, true) 162 | SetModelAsNoLongerNeeded(payload.model) 163 | return obj 164 | end 165 | 166 | local function forceDeleteEntity(insertId) 167 | local object = ClientObjects[insertId] 168 | 169 | if not object then return end 170 | 171 | SetEntityAsMissionEntity(object.handle, false, true) 172 | DeleteObject(object.handle) 173 | object.handle = false 174 | end 175 | 176 | CreateThread(function() 177 | while true do 178 | local pCoords = GetEntityCoords(cache.ped) 179 | 180 | for k, v in pairs(ClientObjects) do 181 | local isClose = #(pCoords - v.coords) < 100.0 182 | 183 | if not isClose and v.handle then 184 | forceDeleteEntity(k) 185 | Wait(0) 186 | elseif isClose and (not v.handle or not DoesEntityExist(v.handle)) then 187 | v.handle = SpawnObject(v) 188 | Wait(0) 189 | end 190 | end 191 | Wait(1200) 192 | end 193 | end) 194 | 195 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() 196 | lib.callback('objects:getAllObjects', 1000, function(allObjects) 197 | ClientObjects = allObjects 198 | end) 199 | end) 200 | 201 | RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() 202 | for k, v in pairs(ClientObjects) do 203 | if v.handle then 204 | forceDeleteEntity(k) 205 | end 206 | end 207 | ClientObjects = {} 208 | end) 209 | 210 | AddEventHandler('onResourceStop', function(resource) 211 | if resource == GetCurrentResourceName() then 212 | for k, v in pairs(ClientObjects) do 213 | if v.handle then 214 | forceDeleteEntity(k) 215 | end 216 | end 217 | end 218 | end) 219 | 220 | return obj 221 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | use_experimental_fxv2_oal 'yes' 3 | game 'gta5' 4 | 5 | description 'object spawner for fivem' 6 | author 'qw-scripts' 7 | version '0.2.0' 8 | 9 | client_scripts { 10 | 'client/**/*' 11 | } 12 | 13 | server_scripts { 14 | '@oxmysql/lib/MySQL.lua', 15 | 'server/**/*' 16 | } 17 | 18 | shared_scripts { 19 | 'shared/**/*', 20 | '@ox_lib/init.lua' 21 | } 22 | 23 | lua54 'yes' 24 | -------------------------------------------------------------------------------- /server/commands.lua: -------------------------------------------------------------------------------- 1 | lib.addCommand('objectspawner', { 2 | help = 'open the object spawner', 3 | restricted = 'group.admin' 4 | }, function(source, args, raw) 5 | TriggerClientEvent('qw_decorator:client:open', source) 6 | end) -------------------------------------------------------------------------------- /server/db.lua: -------------------------------------------------------------------------------- 1 | local MySQL = MySQL 2 | local db = {} 3 | 4 | 5 | local SELECT_ALL_SYNCED_OBJECTS_FROM_SCENE = 'SELECT * FROM `synced_objects` WHERE `sceneid` = ?' 6 | ---Select all synced objects from a scene 7 | ---@param sceneid number 8 | ---@return table 9 | function db.selectAllSyncedObjectsFromScene(sceneid) 10 | return MySQL.query.await(SELECT_ALL_SYNCED_OBJECTS_FROM_SCENE, { sceneid }) or {} 11 | end 12 | 13 | local SELECT_ALL_SYNCED_OBJECTS= 'SELECT * FROM `synced_objects`' 14 | ---Select all synced objects from a scene 15 | ---@return table 16 | function db.selectAllSyncedObjects() 17 | return MySQL.query.await(SELECT_ALL_SYNCED_OBJECTS) or {} 18 | end 19 | 20 | local SELECT_ALL_SCENES_WITH_COUNT_OF_SCENE_OBJECTS = 'SELECT `synced_objects_scenes`.`id`, `synced_objects_scenes`.`name`, COUNT(`synced_objects`.`id`) AS `count` FROM `synced_objects_scenes` LEFT JOIN `synced_objects` ON `synced_objects_scenes`.`id` = `synced_objects`.`sceneid` GROUP BY `synced_objects_scenes`.`id`' 21 | ---Select all scenes with count of scene objects 22 | ---@return table 23 | function db.selectAllScenesWithCountOfSceneObjects() 24 | return MySQL.query.await(SELECT_ALL_SCENES_WITH_COUNT_OF_SCENE_OBJECTS) or {} 25 | end 26 | 27 | local INSERT_NEW_SYNCED_OBJECT = 'INSERT INTO `synced_objects` (`model`, `x`, `y`, `z`, `rx`, `ry`, `rz`, `heading`, `sceneid`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)' 28 | ---Insert a new synced object 29 | ---@param model string 30 | ---@param x string 31 | ---@param y string 32 | ---@param z string 33 | ---@param rx string 34 | ---@param ry string 35 | ---@param rz string 36 | ---@param heading number 37 | ---@param sceneid number 38 | ---@return table 39 | function db.insertNewSyncedObject(model, x, y, z, rx, ry, rz, heading, sceneid) 40 | return MySQL.prepare.await(INSERT_NEW_SYNCED_OBJECT, { model, x, y, z, rx, ry, rz, heading, sceneid }) 41 | end 42 | 43 | local DELETE_SCENE_BY_ID = 'DELETE FROM `synced_objects_scenes` WHERE `id` = ?' 44 | ---Delete a scene by id 45 | ---@param id number 46 | ---@return table 47 | function db.deleteSceneById(id) 48 | return MySQL.prepare.await(DELETE_SCENE_BY_ID, { id }) 49 | end 50 | 51 | local DELETE_SYNCED_OBJECT = 'DELETE FROM `synced_objects` WHERE `id` = ?' 52 | ---Delete a synced object 53 | ---@param id number 54 | ---@return table 55 | function db.deleteSyncedObject(id) 56 | return MySQL.prepare.await(DELETE_SYNCED_OBJECT, { id }) 57 | end 58 | 59 | local UPDATE_SYNCED_OBJECT = 'UPDATE `synced_objects` SET `model` = ?, `x` = ?, `y` = ?, `z` = ?, `rx` = ?, `ry` = ?, `rz` = ? WHERE `id` = ?' 60 | ---Update a synced object 61 | ---@param model string 62 | ---@param x string 63 | ---@param y string 64 | ---@param z string 65 | ---@param rx string 66 | ---@param ry string 67 | ---@param rz string 68 | ---@param id number 69 | ---@return table 70 | function db.updateSyncedObject(model, x, y, z, rx, ry, rz, id) 71 | return MySQL.prepare.await(UPDATE_SYNCED_OBJECT, { model, x, y, z, rx, ry, rz, id }) 72 | end 73 | 74 | local INSERT_NEW_SCENE = 'INSERT INTO `synced_objects_scenes` (`name`) VALUES (?)' 75 | ---Insert a new scene 76 | ---@param name string 77 | ---@return table 78 | function db.insertNewScene(name) 79 | return MySQL.prepare.await(INSERT_NEW_SCENE, { name }) 80 | end 81 | 82 | return db -------------------------------------------------------------------------------- /server/main.lua: -------------------------------------------------------------------------------- 1 | local db = require 'server.db' 2 | local objects = require 'server.objects' 3 | 4 | RegisterNetEvent('objects:server:newObject', function(data) 5 | objects.spawnNewObject(data) 6 | end) 7 | 8 | RegisterNetEvent('objects:server:updateObject', function(data) 9 | objects.updateObject(data) 10 | end) 11 | 12 | RegisterNetEvent("objects:server:removeObject", function(insertId) 13 | objects.removeObject(insertId) 14 | end) 15 | 16 | lib.callback.register('objects:getAllObjects', function(source) 17 | return ServerObjects 18 | end) 19 | 20 | lib.callback.register('objects:getAllScenes', function(source) 21 | local allScenes = db.selectAllScenesWithCountOfSceneObjects() 22 | return allScenes 23 | end) 24 | 25 | lib.callback.register('objects:newScene', function(source, sceneName) 26 | local newScene = db.insertNewScene(sceneName) 27 | 28 | if newScene ~= 0 then 29 | return true 30 | end 31 | 32 | return false 33 | end) 34 | 35 | lib.addCommand('objectspawner', { 36 | help = 'open the object spawner', 37 | restricted = 'group.admin' 38 | }, function(source, args, raw) 39 | TriggerClientEvent('objects:client:menu', source) 40 | end) -------------------------------------------------------------------------------- /server/objects.lua: -------------------------------------------------------------------------------- 1 | local db = require 'server.db' 2 | 3 | local objects = {} 4 | ServerObjects = {} 5 | 6 | function objects.spawnNewObject(data) 7 | local insertId = db.insertNewSyncedObject(data.model, data.x, data.y, data.z, data.rx, data.ry, data.rz, data.heading, data.sceneid) 8 | 9 | local coords = vector3(tonumber(data.x), tonumber(data.y), tonumber(data.z)) 10 | local rotation = vector3(tonumber(data.rx), tonumber(data.ry), tonumber(data.rz)) 11 | 12 | if insertId == 0 then return end 13 | 14 | ServerObjects[insertId] = { 15 | coords = coords, 16 | rotation = rotation, 17 | model = data.model, 18 | sceneid = data.sceneid, 19 | id = insertId, 20 | } 21 | 22 | TriggerClientEvent('objects:client:addObject', -1, ServerObjects[insertId]) 23 | end 24 | 25 | --- removes an object from the database and the world 26 | ---@param insertId number 27 | function objects.removeObject(insertId) 28 | local deletedObjectId = db.deleteSyncedObject(insertId) 29 | 30 | if deletedObjectId == 0 then return end 31 | 32 | ServerObjects[deletedObjectId] = nil 33 | TriggerClientEvent('objects:client:removeObject', -1, insertId) 34 | end 35 | 36 | --- update an object in the database and the world 37 | ---@param data table 38 | function objects.updateObject(data) 39 | local insertId = data.insertId 40 | local model = data.model 41 | local x, y, z = data.x, data.y, data.z 42 | local rx, ry, rz = data.rx, data.ry, data.rz 43 | local updatedObject = db.updateSyncedObject(model, x, y, z, rx, ry, rz, insertId) 44 | 45 | if updatedObject == 0 then return end 46 | 47 | local coords = vec3(tonumber(x), tonumber(y), tonumber(z)) 48 | local rotation = vec3(tonumber(rx), tonumber(ry), tonumber(rz)) 49 | 50 | local spawnedObject = ServerObjects[insertId] 51 | spawnedObject.coords = coords 52 | spawnedObject.rotation = rotation 53 | TriggerClientEvent('objects:client:updateObject', -1, { coords = coords, rotation = rotation, insertId = insertId }) 54 | end 55 | 56 | 57 | AddEventHandler('onResourceStart', function(resource) 58 | if resource == GetCurrentResourceName() then 59 | Wait(1000) 60 | local savedObjects = db.selectAllSyncedObjects() 61 | 62 | if #savedObjects == 0 then return end 63 | 64 | for _, v in pairs(savedObjects) do 65 | local coords = vector3(tonumber(v.x), tonumber(v.y), tonumber(v.z)) 66 | local rotation = vector3(tonumber(v.rx), tonumber(v.ry), tonumber(v.rz)) 67 | local model = v.model 68 | local insertId = v.id 69 | 70 | ServerObjects[insertId] = { 71 | coords = coords, 72 | rotation = rotation, 73 | model = model, 74 | sceneid = v.sceneid, 75 | id = insertId, 76 | } 77 | end 78 | 79 | 80 | TriggerClientEvent('objects:client:loadObjects', -1, ServerObjects) 81 | end 82 | end) 83 | 84 | return objects 85 | -------------------------------------------------------------------------------- /shared/config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | -- DEBUG -- 4 | Config.Debug = true -------------------------------------------------------------------------------- /sql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `synced_objects_scenes` ( 2 | `id` int(11) NOT NULL AUTO_INCREMENT, 3 | `name` varchar(50) NOT NULL, 4 | PRIMARY KEY (`id`) USING BTREE 5 | ) 6 | COLLATE='utf8mb4_general_ci' ENGINE=InnoDB AUTO_INCREMENT=1; 7 | 8 | CREATE TABLE `synced_objects` ( 9 | `id` int(11) NOT NULL AUTO_INCREMENT, 10 | `model` varchar(50) NOT NULL, 11 | `x` varchar(50) NOT NULL, 12 | `y` varchar(50) NOT NULL, 13 | `z` varchar(50) NOT NULL, 14 | `rx` varchar(50) NOT NULL, 15 | `ry` varchar(50) NOT NULL, 16 | `rz` varchar(50) NOT NULL, 17 | `heading` int(11) NOT NULL, 18 | `sceneid` int(11) NOT NULL, 19 | PRIMARY KEY (`id`) USING BTREE, 20 | KEY `FK_objects_scene` (`sceneid`) USING BTREE, 21 | CONSTRAINT `FK_objects_scene` FOREIGN KEY (`sceneid`) REFERENCES `synced_objects_scenes` (`id`) ON UPDATE CASCADE ON DELETE CASCADE 22 | ) 23 | COLLATE='utf8mb4_general_ci' ENGINE=InnoDB AUTO_INCREMENT=1; --------------------------------------------------------------------------------