379 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| Ids = Ids or Require("lib/utility/shared/ids.lua")
 | |
| Shells = Shells or {}
 | |
| Shells.All = Shells.All or {}
 | |
| Shells.ActivePlayers = Shells.ActivePlayers or {} -- Track players in shells
 | |
| 
 | |
| Shells.Interactable = Shells.Interactable or {}
 | |
| Shells.BucketsInUse = Shells.BucketsInUse or {} -- Track buckets in use
 | |
| 
 | |
| function Shells.Interactable.New(_type, id, model, coords, rotation, entityType, distance, meta)
 | |
|     assert(_type, "Shells.Interactable.Create: 'type' is required")
 | |
|     assert(id, "Shells.Interactable.Create: 'name' is required")
 | |
|     assert(coords, "Shells.Interactable.Create: 'coords' is required")
 | |
|     return {
 | |
|         type = _type,
 | |
|         id = id,
 | |
|         model = model,
 | |
|         coords = coords,
 | |
|         rotation = rotation or vector3(0.0, 0.0, 0.0),
 | |
|         distance = distance or 2.0,
 | |
|         entityType = entityType or "object", -- Default entity type
 | |
|         meta = meta or {},
 | |
|     }
 | |
| end
 | |
| 
 | |
| function Shells.New(data)
 | |
|     local id = data.id or Ids.CreateUniqueId(Shells.All)
 | |
|     local _type = data.type or "none"
 | |
|     local model = data.model
 | |
|     local size = data.size or vector3(10.0, 10.0, 10.0) -- Default size if not provided
 | |
|     local coords = data.coords or vector3(0.0, 0.0, 0.0) -- Default coordinates if not provided
 | |
|     local rotation = data.rotation or vector3(0.0, 0.0, 0.0) -- Default rotation if not provided
 | |
|     local interior = data.interior or {}
 | |
|     local exterior = data.exterior or {}
 | |
|     local bucket = data.bucket or Ids.RandomNumber(Shells.BucketsInUse, 4)
 | |
|     assert(id, "Shells.Create: 'id' is required")
 | |
|     assert(model, "Shells.Create: 'shellModel' is required")
 | |
|     assert(coords, "Shells.Create: 'coords' is required")
 | |
| 
 | |
|     local interiorInteractions = {}
 | |
|     for k, v in pairs(interior or {}) do
 | |
|         local interiorCoords = coords + (v.offset or vector3(0.0, 0.0, 0.0))
 | |
|         local interaction = Shells.Interactable.New(
 | |
|             v.type or "none",
 | |
|             v.id or Ids.CreateUniqueId(interiorInteractions),
 | |
|             v.model,
 | |
|             interiorCoords,
 | |
|             v.rotation or vector3(0.0, 0.0, 0.0),
 | |
|             v.entityType or "object", -- Default entity type for interior interactions
 | |
|             v.distance or 2.0,
 | |
|             v.meta
 | |
|         )
 | |
|         interiorInteractions[interaction.id] = interaction
 | |
|     end
 | |
| 
 | |
|     local exteriorInteractions = {}
 | |
|     for k, v in pairs(exterior or {}) do
 | |
|         local interaction = Shells.Interactable.New(
 | |
|             v.type or "none",
 | |
|             v.id or Ids.CreateUniqueId(exteriorInteractions),
 | |
|             v.model,
 | |
|             v.coords,
 | |
|             v.rotation or vector3(0.0, 0.0, 0.0),
 | |
|             v.entityType or "object", -- Default entity type for exterior interactions
 | |
|             v.distance or 2.0,
 | |
|             v.meta
 | |
|         )
 | |
|         exteriorInteractions[interaction.id] = interaction
 | |
|     end
 | |
| 
 | |
|     local shellData = {
 | |
|         id = id,
 | |
|         type = _type,
 | |
|         entityType = "object", -- Default entity type for shells
 | |
|         model = model,
 | |
|         coords = coords,
 | |
|         size = size or vector3(10.0, 10.0, 10.0), -- Default size if not provided
 | |
|         rotation = rotation or vector3(0.0, 0.0, 0.0),
 | |
|         interior = interiorInteractions,
 | |
|         exterior = exteriorInteractions,
 | |
|     }
 | |
| 
 | |
|     Shells.All[id] = shellData
 | |
|     return shellData
 | |
| end
 | |
| 
 | |
| function Shells.Create(data)
 | |
|     local shell = Shells.New(data)
 | |
|     assert(shell, "Shells.Create: 'shell' is required")
 | |
|     TriggerClientEvent('community_bridge:client:CreateShell', -1, shell)
 | |
|     return shell
 | |
| end
 | |
| 
 | |
| function Shells.CreateBulk(shells)
 | |
|     assert(shells, "Shells.CreateBulk: 'shells' is required")
 | |
|     assert(type(shells) == "table", "Shells.CreateBulk: 'shells' must be a table")
 | |
|     local toClient = {}
 | |
|     for _, shellData in pairs(shells) do
 | |
|         local shell = Shells.New(shellData)
 | |
|         toClient[shell.id] = shell
 | |
|     end
 | |
|     TriggerClientEvent('community_bridge:client:CreateShells', -1, toClient)
 | |
| end
 | |
| 
 | |
| function Shells.Enter(src, shellId, entranceId)
 | |
|     src = tonumber(src)
 | |
|     assert(src, "Shells.EnterShell: 'src' is required")
 | |
|     assert(shellId, "Shells.EnterShell: 'shellId' is required")
 | |
|     print(shellId)
 | |
|     local shell = Shells.All[shellId]
 | |
|     assert(shell, "Shell not found: " .. tostring(shellId))
 | |
|     if shell.onEnter then
 | |
|         local canEnter = shell.onEnter(src, shellId)
 | |
|         if not canEnter then return false end
 | |
|     end
 | |
|     if not shell.bucket then
 | |
|         local randNum = Ids.RandomNumber(Shells.BucketsInUse, 4)
 | |
|         shell.bucket = tonumber(randNum)
 | |
|         Shells.BucketsInUse[tostring(randNum)] = true
 | |
|     end
 | |
|     print(shell.bucket)
 | |
|     SetPlayerRoutingBucket(src, shell.bucket)
 | |
|     local exit = shell.exterior[entranceId]?.meta?.link
 | |
|     TriggerClientEvent('community_bridge:client:EnterShell', src, shellId, exit, Shells.ActivePlayers[tostring(src)])
 | |
|     Shells.ActivePlayers[tostring(src)] = shellId
 | |
|     return true
 | |
| end
 | |
| 
 | |
| 
 | |
| function Shells.Exit(src, shellId, oldId)
 | |
| 
 | |
|     print(oldId)
 | |
|     src = tonumber(src)
 | |
|     assert(src, "Shells.ExitShell: 'src' is required")
 | |
|     assert(shellId, "Player is not in a shell")
 | |
| 
 | |
|     local shell = Shells.All[shellId]
 | |
|     assert(shell, "Shell not found: " .. tostring(shellId))
 | |
| 
 | |
|     -- Restore original routing bucket
 | |
|     SetPlayerRoutingBucket(src, 0)
 | |
|     local exit = shell.interior[oldId]?.meta?.link
 | |
|     -- Clear player's shell data
 | |
|     Shells.ActivePlayers[tostring(src)] = nil
 | |
|     if shell.onExit then
 | |
|         shell.onExit(src, shellId)
 | |
|     end
 | |
|     TriggerClientEvent('community_bridge:client:ExitShell', src, shellId, exit)
 | |
|     return true
 | |
| end
 | |
| 
 | |
| 
 | |
| function Shells.Get(shellId)
 | |
|     assert(shellId, "Shells.GetShellById: 'shellId' is required")
 | |
|     return Shells.All[shellId]
 | |
| end
 | |
| 
 | |
| function Shells.Inside(src)
 | |
|     src = tonumber(src)
 | |
|     assert(src, "Shells.IsInside: 'src' is required")
 | |
| 
 | |
|     local shellId = Shells.ActivePlayers[tostring(src)]
 | |
|     if not shellId then
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     local shell = Shells.All[shellId]
 | |
|     if not shell then
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     return shell
 | |
| end
 | |
| 
 | |
| function Shells.AddObjects(shellId, objects)
 | |
|     assert(shellId, "Shells.AddObjects: 'shellId' is required")
 | |
|     assert(objects, "Shells.AddObjects: 'objects' is required")
 | |
|     assert(type(objects) == "table", "Shells.AddObjects: 'objects' must be a table")
 | |
| 
 | |
|     local shell = Shells.All[shellId]
 | |
|     assert(shell, "Shell not found: " .. tostring(shellId))
 | |
| 
 | |
|     local interiors = objects.interior or {}
 | |
|     local exteriors = objects.exterior or {}
 | |
|     for _, objData in pairs(interiors) do
 | |
|         local obj = Shells.Interactable.New(
 | |
|             objData.type,
 | |
|             objData.id,
 | |
|             objData.model,
 | |
|             objData.coords,
 | |
|             objData.rotation,
 | |
|             objData.entityType,
 | |
|             objData.distance,
 | |
|             objData.meta
 | |
|         )
 | |
|         shell.interior[obj.id] = obj -- Update the shell's interior with the new object
 | |
|     end
 | |
|     for _, objData in pairs(exteriors) do
 | |
|         local obj = Shells.Interactable.New(
 | |
|             objData.type,
 | |
|             objData.id,
 | |
|             objData.model,
 | |
|             objData.coords,
 | |
|             objData.rotation,
 | |
|             objData.entityType,
 | |
|             objData.distance,
 | |
|             objData.meta
 | |
|         )
 | |
|         shell.exterior[obj.id] = obj -- Update the shell's exterior with the new object
 | |
|     end
 | |
| 
 | |
|     TriggerClientEvent('community_bridge:client:AddObjectsToShell', -1, shellId, shell.interior, shell.exterior)
 | |
|     return shell
 | |
| end
 | |
| 
 | |
| function Shells.RemoveObjects(shellId, objectIds)
 | |
|     assert(shellId, "Shells.RemoveObjects: 'shellId' is required")
 | |
|     assert(objectIds, "Shells.RemoveObjects: 'objects' is required")
 | |
|     if type(objectIds) ~= "table" then
 | |
|         objectIds = {objectIds} -- Ensure it's a table
 | |
|     end
 | |
| 
 | |
|     local shell = Shells.All[shellId]
 | |
|     assert(shell, "Shell not found: " .. tostring(shellId))
 | |
| 
 | |
|     for _, objId in pairs(objectIds) do
 | |
|         shell.interior[objId] = nil
 | |
|         shell.exterior[objId] = nil
 | |
|     end
 | |
| 
 | |
|     TriggerClientEvent('community_bridge:client:RemoveObjectsFromShell', -1, shellId, objectIds)
 | |
|     return shell
 | |
| end
 | |
| 
 | |
| RegisterNetEvent('community_bridge:server:EnterShell', function(shellId, entranceId)
 | |
|     local src = source
 | |
|     Shells.Enter(src, shellId, entranceId)
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent('community_bridge:server:ExitShell', function(shellId, oldId)
 | |
|     local src = source
 | |
|     Shells.Exit(src, shellId, oldId)
 | |
| end)
 | |
| 
 | |
| AddEventHandler('onPlayerJoining', function(playerId)
 | |
|     local src = source
 | |
|     TriggerClientEvent('community_bridge:client:CreateShells', src, Shells.All)
 | |
| end)
 | |
| 
 | |
| function Shells.GetInteractable(shellId, id)
 | |
|     assert(shellId, "Shells.GetInteractable: 'shellId' is required")
 | |
|     assert(id, "Shells.GetInteractable: 'id' is required")
 | |
| 
 | |
|     local shell = Shells.All[shellId]
 | |
|     if not shell then
 | |
|         return nil, "Shell not found: " .. tostring(shellId)
 | |
|     end
 | |
| 
 | |
|     local interactable = shell.interior[id] or shell.exterior[id]
 | |
|     if not interactable then
 | |
|         return nil, "Interactable not found: " .. tostring(id)
 | |
|     end
 | |
| 
 | |
|     return interactable
 | |
| end
 | |
| 
 | |
| function Shells.GetClosestInteractable(shellId, coords, exterior)
 | |
|     assert(shellId, "Shells.GetClosestInteriorInteractable: 'shellId' is required")
 | |
|     assert(coords, "Shells.GetClosestInteriorInteractable: 'coords' is required")
 | |
| 
 | |
|     local shell = Shells.All[shellId]
 | |
|     if not shell then
 | |
|         return nil, "Shell not found: " .. tostring(shellId)
 | |
|     end
 | |
| 
 | |
|     local closest = nil
 | |
|     local closestDistance = math.huge
 | |
|     if exterior then
 | |
|         for _, interactable in pairs(shell.exterior) do
 | |
|             local distance = #(coords - interactable.coords)
 | |
|             if distance < closestDistance then
 | |
|                 closestDistance = distance
 | |
|                 closest = interactable
 | |
|             end
 | |
|         end
 | |
|         return closest, closestDistance
 | |
|     end
 | |
| 
 | |
|     for _, interactable in pairs(shell.interior) do
 | |
|         local distance = #(coords - interactable.coords)
 | |
|         if distance < closestDistance then
 | |
|             closestDistance = distance
 | |
|             closest = interactable
 | |
|         end
 | |
|     end
 | |
|     return closest, closestDistance
 | |
| end
 | |
| 
 | |
| local testShell = nil
 | |
| RegisterCommand('shells:create', function(source, args, rawCommand)
 | |
|     local coords = GetEntityCoords(GetPlayerPed(source))
 | |
|     if testShell then
 | |
|         Shells.Exit(source, testShell.id, 'exit1') -- Exit previous shell if it exists
 | |
|     end
 | |
|     testShell = Shells.Create({
 | |
|         id = Ids.CreateUniqueId(Shells.All),
 | |
|         type = "shell",
 | |
|         model = "shell_garagem",
 | |
|         coords = coords + vector3(0.0, 0.0, 100.0), -- Adjusted to place shell slightly below player
 | |
|         rotation = vector3(0.0, 0.0, 0.0),
 | |
|         size = vector3(10.0, 10.0, 10.0),
 | |
|         interior = {
 | |
|             {
 | |
|                 id = 'exit1',
 | |
|                 type = 'exit',
 | |
|                 coords = coords + vector3(0.0, 0.0, 1.0),
 | |
|                 rotation = vector3(0.0, 0.0, 0.0),
 | |
|                 distance = 2.0,
 | |
|                 meta = {
 | |
|                     link = 'entrance1',
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|         exterior = {
 | |
|             {
 | |
|                 id = 'entrance1',
 | |
|                 type = 'entrance',
 | |
|                 -- entityType = "object",
 | |
|                 -- model = "xm_int_lev_sub_chair_02",
 | |
|                 coords = coords - vector3(0.0, 0.0, 0.5),
 | |
|                 rotation = vector3(0.0, 0.0, 0.0),
 | |
|                 distance = 2.0,
 | |
|                 meta = {
 | |
|                     link = 'exit1',
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|     })
 | |
| end, true)
 | |
| 
 | |
| RegisterCommand('shells:addobject', function(source, args, rawCommand)
 | |
|     if not testShell then
 | |
|         print("No shell created. Use /shells:create first.")
 | |
|         return
 | |
|     end
 | |
|     local model = args[1]
 | |
|     if not model then
 | |
|         print("Usage: /shells:addobject <shellId> <model>")
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local shellId = testShell.id
 | |
|     local coords = GetEntityCoords(GetPlayerPed(source))
 | |
|     local objectData = {
 | |
|         type = "none",
 | |
|         entityType = "ped",
 | |
|         id = Ids.CreateUniqueId(Shells.All[shellId].interior),
 | |
|         model = model,
 | |
|         coords = coords - vector3(0.0, 0.0, 0.5), -- Adjusted to place object slightly above player
 | |
|         rotation = vector3(0.0, 0.0, 0.0),
 | |
|         distance = 2.0,
 | |
|         meta = {}
 | |
|     }
 | |
|     Shells.AddObjects(shellId, {interior = {objectData}})
 | |
| end, true)
 | |
| 
 | |
| AddEventHandler('onResourceStop', function(resource)
 | |
|     if resource == GetCurrentResourceName() then
 | |
|         for src, shellId in pairs(Shells.ActivePlayers) do
 | |
|             SetPlayerRoutingBucket(tonumber(src), 0) -- Reset routing bucket for all players
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| AddEventHandler('community_bridge:Server:OnPlayerUnload', function(src)
 | |
|     if not Shells.ActivePlayers[tostring(src)] then return end
 | |
|     print(string.format("Player %d is exiting shell %s", src, Shells.ActivePlayers[tostring(src)]))
 | |
|     Shells.Exit(src, Shells.ActivePlayers[tostring(src)])
 | |
|     Shells.ActivePlayers[tostring(src)] = nil
 | |
| end) | 
