qb-interior
qb-interior is a shell spawner. Each export creates a single interior shell object at coordinates
you pass in, freezes it, fades the screen, and teleports the player inside. It returns the handles
it created plus the offsets of the shell’s points of interest, so the caller can place its own
markers and clean up afterwards.
It does not own housing, apartments, or robbery logic. Those live in qb-houses, qb-apartments, and qb-houserobbery, which call these exports to get a room to put the player in.
The manifest at the commit linked above declares version 1.2.0.
Dependencies and start order
The manifest declares no dependency entry and loads only client/main.lua and
client/optional.lua. It sets this_is_a_map 'yes' and registers
stream/starter_shells_k4mb1.ytyp as a DLC_ITYP_REQUEST data file, so the bundled archetypes are
available to the game.
Because it is a streaming resource, start it early, before the housing and robbery resources that call into it.
What is bundled and what is not
This is the part most setups get wrong.
client/main.lua spawns models that ship in the repository’s own stream/ folder, for example
furnitured_midapart, shell_v16low, shell_v16mid, shell_trevor, shell_trailer,
shell_lester, shell_ranch, container_shell, modernhotel_shell, shell_frankaunt,
shell_michael, shell_office1, shell_store1, shell_warehouse1.
client/optional.lua defines well over a hundred further exports that request models such as
shell_medium2, shell_banham, k4_mansion_shell, luxury_housing1_k4mb1, and
k4mb1_house1_shell. None of those model files are in this repository. They come from separate
K4MB1 packages that a server owner has to obtain and stream themselves.
Calling an optional.lua export without the matching stream assets means HasModelLoaded never
returns true, and the export loops on Wait(1000) while the screen stays faded out. Verify which
model a given export requests before wiring it into a menu.
Exports
All exports are client side.
Generic shell creation
exports['qb-interior']:CreateShell(spawn, exitXYZH, model)| Parameter | Type | Meaning |
|---|---|---|
spawn | vector3 or table with x, y, z | World position the shell object is created at |
exitXYZH | table with x, y, z, h | Offset from spawn where the player is placed, plus a heading |
model | string or hash | Archetype to request |
Returns a two element array: { objects, POIOffsets }. objects is an array of created entity
handles. POIOffsets is a table whose exit field holds the offset you passed in.
Named shell helpers
Each named export wraps CreateShell with a fixed model and a fixed exit offset, so the caller only
supplies spawn. The ones backed by bundled models are:
| Export | Model |
|---|---|
CreateApartmentFurnished | furnitured_midapart |
CreateFurniMid | furnitured_midapart |
CreateHouseRobbery | furnitured_midapart |
CreateApartmentShell | shell_v16low |
CreateTier1House | shell_v16mid |
CreateTrevorsShell | shell_trevor |
CreateCaravanShell | shell_trailer |
CreateLesterShell | shell_lester |
CreateRanchShell | shell_ranch |
CreateContainer | container_shell |
CreateFurniMotelModern | modernhotel_shell |
CreateFranklinAunt | shell_frankaunt |
CreateMichael | shell_michael |
CreateOffice1 | shell_office1 |
CreateStore1 | shell_store1 |
CreateWarehouse1 | shell_warehouse1 |
CreateApartmentFurnished is the only one that adds extra points of interest. It sets
clothes, stash, and logout offsets on the returned POIOffsets table, and if the client has
received qb-interior:client:SetNewState with true, it fires
qb-clothes:client:CreateFirstCharacter 750 ms after the shell appears.
CreateGarageMed requests the model shell_garagemed. The bundled stream folder contains
shell_garagem.ydr. Confirm the archetype name your own build actually provides before relying on
that export.
Cleanup
exports['qb-interior']:DespawnInterior(objects, cb)Iterates the handle array returned by a create export, deletes each entity that still exists, then
invokes cb. Call this whenever the player leaves, and on resource stop, otherwise shells stay in
the world.
Integration example
-- client side
local currentShell
local function EnterPlayerHouse(houseCoords)
local result = exports['qb-interior']:CreateTier1House(houseCoords)
currentShell = result[1]
local offsets = result[2]
-- offsets.exit is relative to houseCoords, so build absolute coords for your own marker
local exitCoords = vector3(
houseCoords.x + offsets.exit.x,
houseCoords.y + offsets.exit.y,
houseCoords.z + offsets.exit.z
)
TriggerEvent('myresource:client:registerExitZone', exitCoords)
end
local function LeavePlayerHouse()
if not currentShell then return end
exports['qb-interior']:DespawnInterior(currentShell, function()
currentShell = nil
end)
endEvents
qb-interior:client:SetNewState(bool) is a client event. Set it to true before calling
CreateApartmentFurnished for a brand new character, so the first-character clothing flow runs.
The resource resets the flag to false itself after firing.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Screen stays black after entering | The requested model never loaded. Either the archetype is not streamed, or you called an optional.lua export whose K4MB1 package you do not own |
| Shell appears but the player is outside it | The exitXYZH offset does not match the model. Use the named helper for that model rather than raw CreateShell |
| Old shells pile up in the world | DespawnInterior was never called, or the caller lost the handle array. Store the array from the create call |
| Players see each other’s interiors | Shells are plain world objects at real coordinates. Separate them yourself, for example with far apart spawn coordinates or a routing bucket. See OneSync and scope |
Proposed manual smoke test
Not yet executed. On staging:
- Call one bundled shell export and confirm the fade completes and the player lands inside.
- Confirm the returned offsets place your exit marker at the real doorway.
- Call
DespawnInteriorand confirm the object is gone withGetGamePool('CObject'). - Restart the resource while a shell is spawned and confirm no orphan object remains.