Vivienda y apartamentos en FiveM: shells, instancias, escondites y llaves

Cómo funciona un sistema de vivienda en FiveM: interiores shell y MLO, instancias con routing buckets, un escondite por casa, llaves y permisos, y guardar la propiedad en la base de datos.

El síntoma: dos jugadores entran en dos apartamentos diferentes y se ven mutuamente, el escondite se comparte entre todas las casas, o la propiedad desaparece cuando el servidor se reinicia.

Un sistema de vivienda tiene cuatro componentes trabajando juntos: un interior, una forma de separar jugadores, un lugar para almacenar objetos, y un registro de quién puede entrar. Este artículo explica cada parte y cómo se conectan.

Interior shell o MLO

Tienes dos formas de dar a una casa un interior:

Tipo Cómo funciona Bueno para
Shell Una prop spawneada en coordenadas, removida al salir Apartamentos, muchas casas idénticas
MLO Un interior permanente en el mapa Casas únicas y mansiones

Un shell es un modelo que cargas, colocas en una ubicación fija (a menudo alto en el cielo o lejos, bajo el mapa), y eliminas cuando el jugador se va. Porque el mismo shell puede spawnearse para cualquier número de casas, los shells son la forma de hacer edificios de apartamentos. Un MLO se queda en el mapa, así que está limitado a un lugar. Para instalar uno, ver instalando un MLO, y si rompe el mapa alrededor, conflictos MLO en el mapa.

Instancias con routing buckets

Si cada apartamento usa el mismo shell en las mismas coordenadas, entonces todos los que entran están en la misma habitación. La solución es dar a cada casa su propio routing bucket, una copia aislada del mundo en el servidor. Los jugadores en diferentes buckets no pueden verse mutuamente ni sus entidades. La idea en una oración: la casa número 12 usa el bucket 12 (más un offset para que nunca colisione con otros usos).

lua
-- server
local HOUSE_BUCKET_OFFSET = 1000

RegisterNetEvent('housing:enter', function(houseId)
    local src = source
    if not canEnter(src, houseId) then return end   -- ownership or key check

    SetPlayerRoutingBucket(src, HOUSE_BUCKET_OFFSET + houseId)
    TriggerClientEvent('housing:spawnShell', src, houseId)
end)

RegisterNetEvent('housing:leave', function()
    local src = source
    SetPlayerRoutingBucket(src, 0)
    TriggerClientEvent('housing:removeShell', src)
end)

Todos los que visiten esa casa deben estar en el mismo bucket, y cada salida debe establecer el bucket de vuelta a 0. Maneja desconexiones y reinicios de script también, así ningún jugador se queda en un bucket que nadie más pueda alcanzar. Todas las natives se explican en routing buckets.

Consejo: si spawneaste la prop shell en el cliente, solo ese jugador la ve. Para un shell que también vean los visitantes, cada visitante lo spawneará para sí mismo, o el servidor lo crea en el mismo bucket.

Un escondite para cada casa

Cada casa necesita su propio almacenamiento. Con ox_inventory, registra un escondite por casa, con un id que incluya el id de la casa:

lua
-- server
exports.ox_inventory:RegisterStash('house_' .. houseId, 'House storage', 50, 100000, ownerId)

Luego ábrelo con el mismo id en el cliente:

lua
exports.ox_inventory:openInventory('stash', 'house_' .. houseId)

Si el id es el mismo para cada casa, cada casa comparte un escondite, que es el síntoma del inicio de este artículo. Añade la comprobación de permisos en el servidor antes de que se abra el escondite, y ver ox_inventory escondites para slots, peso y quién puede abrir uno.

Llaves y permisos

La propiedad no es la única forma de entrar. Planifica para estos casos:

  • Propietario: siempre entra, puede vender la casa y dar llaves.
  • Tenedores de llaves: amigos o inquilinos que pueden entrar y usar el escondite, pero no pueden vender.
  • Invitados: visitan cuando el propietario está dentro y los deja entrar (un timbre).
  • Policía: un allanamiento o una entrada oficial, si tu servidor tiene una.

Mantén las llaves como datos en lugar de un objeto que puedas soltar: una lista de identificadores de carácter por casa. Comprueba la lista en el servidor cada vez que alguien intente entrar o abrir el escondite.

Guardar propiedad en la base de datos

Todo lo que debe sobrevivir a un reinicio va en la base de datos. Una estructura simple es una tabla para casas y otra para las llaves:

sql
CREATE TABLE IF NOT EXISTS houses (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(64) NOT NULL,
  owner VARCHAR(64) DEFAULT NULL,
  price INT NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS house_keys (
  house_id INT NOT NULL,
  holder VARCHAR(64) NOT NULL,
  PRIMARY KEY (house_id, holder)
);

Almacena el identificador de carácter que usa tu framework para los caracteres (por ejemplo el identificador del carácter en lugar de la licencia del jugador), así la propiedad sigue al carácter. Carga los datos cuando se inicia el recurso y mantén una copia en una tabla del servidor para comprobaciones rápidas, escribiendo de vuelta a la base de datos en cada cambio. Las consultas desde Lua están en consultas oxmysql.

Los flujos de dinero también importan: la compra, la renta y el reembolso de venta deben manejarse en el servidor, y actúan como un sumidero de dinero, como se describe en equilibrando tu economía de servidor.

Errores comunes

  • No resetear el bucket al salir, morir, desconectarse o reiniciar.
  • Mismo id de escondite para todas las casas.
  • Comprobando propiedad en el cliente solo.
  • Propiedad mantenida en memoria y perdida al reiniciar.
  • Spawneando el shell más de una vez cuando el jugador re-entra rápidamente, dejando props duplicadas.

Checklist

Síntoma Solución
Jugadores en diferentes casas se ven mutuamente Da a cada casa su propio routing bucket
Jugador atrapado en un mundo vacío Resetea el bucket a 0 al salir, morir y desconectarse
Cada casa comparte un escondite Usa un id de escondite que incluya el id de la casa
Propiedad perdida al reiniciar Guárdala en la base de datos
Cualquiera puede entrar Comprueba propietario o llave en el servidor
Props shell duplicadas Elimina el shell al salir y protege contra spawns dobles

Respuestas rápidas

¿Cuál es la diferencia entre un shell y un interior MLO?

Un MLO es un interior real colocado en el mapa en coordenadas fijas. Un shell es una prop que spawneamos bajo demanda, generalmente lejos del mapa, y removemos cuando el jugador se va.

¿Por qué los jugadores pueden verse entre casas diferentes?

Cada interior de casa usa las mismas coordenadas, así que jugadores en casas diferentes están en el mismo espacio. Pon cada casa en su propio routing bucket para separarlas.

¿Dónde debería guardarse la propiedad de la casa?

En la base de datos, con el id de la casa y el identificador del carácter del propietario, y una tabla o columna separada para las personas que tienen llaves. Nunca mantengas la propiedad solo en memoria.

Scripts que evitan este problema

CCTV Security CamerasCámaras colocables, una tablet con vista múltiple en vivo y fotos como prueba.Ver script →Shop CreatorCrea una tienda en menos de un minuto — dueños, empleados, caja fuerte y atracos.Ver script →Item Creator V2Crea items usables con animaciones, props, efectos y más — sin escribir código.Ver script →

Sigue leyendo