Volver al blog
Guide9 min de lectura

Almacenes y depósitos para FiveM: alijos compartidos con scripts

Crea almacenes compartidos en FiveM. Alijos para bandas, taquillas de trabajo, límites de peso y scripts para ox_inventory y más.

Agency Scripts

Fundador y desarrollador principal en Agency Scripts

Sistemas de almacenamiento en servidores de roleplay

Todo servidor de roleplay necesita un sistema de almacenamiento sólido que vaya más allá del inventario básico del jugador. Los inventarios personales tienen capacidad limitada y los jugadores acumulan objetos de trabajos, fabricación, intercambios y saqueos que quieren guardar, pero no pueden llevar encima en todo momento. Los sistemas de almacenes y almacenamiento resuelven esto con ubicaciones físicas en el mundo del juego donde los jugadores pueden depositar y retirar objetos. Estos sistemas sirven para varios propósitos de juego: las taquillas personales dan espacio adicional a cada jugador, los almacenes de organizaciones permiten que bandas y negocios compartan recursos, y las unidades de almacenamiento en alquiler crean un mercado inmobiliario que genera ingresos recurrentes para la economía del servidor. La implementación técnica debe gestionar el acceso simultáneo, los límites de peso y espacios, los permisos de acceso y la integración con el sistema de inventario que ya use tu servidor.

Esquema de base de datos para el almacenamiento

Diseña la base de datos de almacenamiento para que admita varios tipos de unidades con propiedad flexible y control de acceso. Cada unidad necesita un identificador único, una referencia al propietario que puede ser el citizenid de un jugador o el identificador de una organización, límites de capacidad definidos por el peso y el número de espacios, y los datos del inventario serializados como JSON. Incluye una tabla del sistema de alquiler que registre el estado de los pagos de las unidades alquiladas, con bloqueo automático cuando se vence el plazo de pago. La tabla de control de acceso permite que los propietarios concedan a otros jugadores permiso para acceder a sus unidades de almacenamiento:

CREATE TABLE IF NOT EXISTS storage_units (
    id INT AUTO_INCREMENT PRIMARY KEY,
    unit_id VARCHAR(50) UNIQUE NOT NULL,
    unit_type ENUM('personal', 'organization', 'rental', 'property') DEFAULT 'personal',
    owner_id VARCHAR(50) NOT NULL,
    label VARCHAR(100) DEFAULT NULL,
    max_weight INT DEFAULT 100000,
    max_slots INT DEFAULT 50,
    items LONGTEXT DEFAULT '[]',
    location VARCHAR(50) NOT NULL,
    is_locked BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_owner (owner_id),
    INDEX idx_location (location),
    INDEX idx_type (unit_type)
);

CREATE TABLE IF NOT EXISTS storage_access (
    unit_id VARCHAR(50) NOT NULL,
    citizenid VARCHAR(50) NOT NULL,
    permission ENUM('view', 'deposit', 'full') DEFAULT 'deposit',
    granted_by VARCHAR(50) NOT NULL,
    granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (unit_id, citizenid)
);

CREATE TABLE IF NOT EXISTS storage_rentals (
    unit_id VARCHAR(50) PRIMARY KEY,
    tenant_id VARCHAR(50) NOT NULL,
    rent_amount INT NOT NULL,
    rent_interval ENUM('daily', 'weekly') DEFAULT 'weekly',
    last_paid TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    next_due TIMESTAMP NOT NULL,
    is_overdue BOOLEAN DEFAULT FALSE,
    INDEX idx_tenant (tenant_id),
    INDEX idx_due (next_due)
);

Configuración de ubicaciones de almacenes

Define ubicaciones de almacenes por el mapa teniendo en cuenta el contexto del roleplay. Las zonas industriales cerca del puerto son adecuadas para grandes almacenes comerciales. Los barrios residenciales son apropiados para las taquillas personales. Las ubicaciones subterráneas u ocultas sirven como escondites de bandas. Cada ubicación necesita una configuración para su posición física, los tipos de unidades de almacenamiento disponibles, los precios de las unidades de alquiler y el interior que se carga cuando entra un jugador. Utiliza los proxies de interiores de GTA o MLO personalizados para los interiores de los almacenes, así los jugadores entran físicamente en un edificio en lugar de abrir un menú en la calle:

Config.StorageLocations = {
    ['industrial_1'] = {
        label = 'Elysian Island Storage',
        entrance = vector3(-96.67, -1601.77, 29.41),
        blipSprite = 473,
        blipColor = 2,
        units = {
            {id = 'ind1_small_1', type = 'rental', size = 'small', maxWeight = 50000, maxSlots = 30, rent = 500, interval = 'weekly'},
            {id = 'ind1_small_2', type = 'rental', size = 'small', maxWeight = 50000, maxSlots = 30, rent = 500, interval = 'weekly'},
            {id = 'ind1_medium_1', type = 'rental', size = 'medium', maxWeight = 150000, maxSlots = 75, rent = 1500, interval = 'weekly'},
            {id = 'ind1_large_1', type = 'rental', size = 'large', maxWeight = 300000, maxSlots = 150, rent = 3500, interval = 'weekly'},
        },
    },
    ['gang_southside'] = {
        label = 'Southside Stash',
        entrance = vector3(93.41, -1961.08, 20.82),
        blipSprite = 0,  -- No blip for hidden locations
        restrictedTo = {'ballas', 'gsf'},  -- Gang-only access
        units = {
            {id = 'gang_ss_main', type = 'organization', size = 'large', maxWeight = 500000, maxSlots = 200},
        },
    },
}

Integración del inventario

Tu sistema de almacenes debe integrarse sin problemas con el sistema de inventario que use tu servidor, ya sea ox_inventory, qb-inventory o una solución personalizada. La interfaz de almacenamiento debería reflejar la interfaz de inventario conocida para que los jugadores entiendan instintivamente cómo mover objetos entre su inventario personal y la unidad de almacenamiento. Implementa transferencias de objetos mediante arrastrar y soltar, con selección de cantidad para los objetos apilables. Cuando un jugador abra una unidad de almacenamiento, carga los objetos guardados desde la base de datos y muéstralos junto al inventario personal del jugador. Todos los movimientos de objetos deben validarse en el servidor para evitar exploits de duplicación. Comprueba que el inventario de origen realmente contenga el objeto que se va a mover, que el destino tenga capacidad suficiente tanto en peso como en espacios, y que el objeto no esté marcado como intransferible ni vinculado al jugador. Utiliza transacciones de base de datos para las transferencias de objetos, de modo que quitar un objeto de un inventario y añadirlo a otro ocurra de forma atómica:

RegisterNetEvent('storage:server:moveItem', function(unitId, direction, itemName, amount, fromSlot, toSlot)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate access
    if not HasStorageAccess(Player.PlayerData.citizenid, unitId, 'full') then
        TriggerClientEvent('QBCore:Notify', src, 'Access denied', 'error')
        return
    end

    -- Load storage unit
    local unit = GetStorageUnit(unitId)
    if not unit or unit.is_locked then
        TriggerClientEvent('QBCore:Notify', src, 'Storage is locked', 'error')
        return
    end

    amount = math.floor(tonumber(amount) or 0)
    if amount <= 0 then return end

    if direction == 'to_storage' then
        -- Player -> Storage
        local playerItem = Player.Functions.GetItemBySlot(fromSlot)
        if not playerItem or playerItem.name ~= itemName then return end
        if playerItem.amount < amount then return end

        local itemWeight = QBCore.Shared.Items[itemName].weight * amount
        local currentWeight = CalculateStorageWeight(unit.items)

        if currentWeight + itemWeight > unit.max_weight then
            TriggerClientEvent('QBCore:Notify', src, 'Storage is full (weight)', 'error')
            return
        end

        -- Atomic transfer
        Player.Functions.RemoveItem(itemName, amount, fromSlot)
        AddItemToStorage(unitId, itemName, amount, playerItem.info, toSlot)

    elseif direction == 'from_storage' then
        -- Storage -> Player
        local storageItem = GetStorageItemBySlot(unit.items, fromSlot)
        if not storageItem or storageItem.name ~= itemName then return end
        if storageItem.amount < amount then return end

        if not Player.Functions.AddItem(itemName, amount, toSlot, storageItem.info) then
            TriggerClientEvent('QBCore:Notify', src, 'Inventory full', 'error')
            return
        end

        RemoveItemFromStorage(unitId, itemName, amount, fromSlot)
    end

    -- Refresh UI for all viewers
    RefreshStorageViewers(unitId)
end)

Sistema de pagos de alquiler

Las unidades de almacenamiento en alquiler necesitan un sistema de pagos periódicos que cobre automáticamente a los inquilinos y gestione las cuentas vencidas. Ejecuta una tarea programada del lado del servidor que revise todas las unidades de alquiler a intervalos regulares, procese los pagos de las unidades que deben pagar y bloquee las unidades con pagos vencidos. Cuando venza un pago, intenta deducir el importe del alquiler de la cuenta bancaria del inquilino. Si la cuenta bancaria no tiene fondos suficientes, marca el alquiler como vencido y envía una notificación al jugador. Concede a los inquilinos con pagos vencidos un periodo de gracia de unos días antes de bloquear su unidad. Una unidad bloqueada impide que el inquilino acceda a los objetos guardados hasta que pague el saldo pendiente. Si el alquiler sigue sin pagarse tras un plazo más largo, se podrían subastar o vaciar los objetos, lo que generaría dinámicas de juego secundarias interesantes. Implementa un registro del historial de pagos para que los inquilinos puedan consultar los cargos del alquiler y las fechas de pago desde la interfaz de gestión de almacenamiento.

Almacenamiento compartido de organizaciones

Las organizaciones, como bandas, negocios y departamentos del gobierno, necesitan almacenamiento compartido al que puedan acceder varios miembros con los niveles de permisos adecuados. El líder de la organización tiene control total y puede añadir o quitar miembros de la lista de acceso, establecer niveles de permisos individuales y consultar registros de auditoría de todos los objetos depositados o retirados. Los miembros normales pueden tener permiso para depositar, pero no para retirar, lo que resulta útil para que los miembros de una banda aporten materiales a un fondo común. Los miembros de confianza tienen acceso completo para depositar y retirar. Implementa un registro de auditoría completo que documente cada movimiento de objetos que entre o salga del almacenamiento compartido, incluido el jugador que realizó la acción, el objeto y la cantidad, y la marca de tiempo. Este registro ayuda a los líderes de la organización a controlar quién contribuye y quién podría estar robando del fondo común. El registro debe poder consultarse desde la interfaz de almacenamiento, con opciones para filtrar por jugador, tipo de objeto y periodo de fechas.

Mejoras de capacidad y niveles

Permite que los jugadores mejoren la capacidad de almacenamiento mediante un sistema de mejoras que incentive la progresión. Empieza con unidades de almacenamiento pequeñas que tengan límites de peso y espacio reducidos, y luego ofrece niveles de mejora que aumenten estos límites por un precio. Cada nivel de mejora debe costar progresivamente más, creando una vía de gasto importante para la economía del servidor. Las mejoras pueden aumentar el peso máximo, añadir espacio adicional o desbloquear funciones especiales, como almacenamiento con temperatura controlada para productos perecederos o seguridad reforzada que haga que la unidad resista los robos. Muestra el nivel actual y las mejoras disponibles en la interfaz de gestión del almacenamiento, con precios claros y los beneficios de cada nivel. Considera vincular algunos niveles de mejora a logros o hitos de tiempo de juego en lugar de basarlos únicamente en el dinero, para recompensar a los jugadores más dedicados con beneficios de almacenamiento prémium que no puedan comprar sin más.

Mecánicas de seguridad y allanamiento

Las unidades de almacenamiento no deben estar completamente a salvo de la actividad delictiva. Implementa una mecánica de allanamiento que permita a los jugadores intentar acceder a unidades de almacenamiento que no les pertenecen, poniendo en riesgo los objetos de valor guardados y creando situaciones de atraco. El proceso de allanamiento debe requerir herramientas específicas, como ganzúas o dispositivos electrónicos de intrusión, llevar bastante tiempo y producir señales visibles y audibles que alerten a los jugadores cercanos, además de enviar una notificación al dueño del almacenamiento y a las fuerzas del orden. Las unidades de almacenamiento de nivel superior y las que tengan mejoras de seguridad deben ser progresivamente más difíciles de forzar, y requerir mejores herramientas y más tiempo. Si el allanamiento tiene éxito, el delincuente obtiene acceso temporal al inventario de almacenamiento y puede llevarse objetos, pero el sistema debe registrar el allanamiento y proporcionar pruebas al dueño, como información parcial de identificación que la policía pueda investigar. Esto crea un ciclo de juego completo de asaltos a almacenes, investigaciones y consecuencias que enriquece la dinámica criminal y policial del servidor.

¿Listo para empezar?

Consigue los scripts en nuestra tienda, o únete al Discord para soporte, novedades y un adelanto de lo que viene.