1
0
Fork 0
forked from Simnation/Main
Main/resources/[carscripts]/community_bridge/lib/shells/server/shells.lua
2025-08-06 16:37:06 +02:00

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)