Zurück zum Blog
Guide7 Min. Lesezeit

FiveM-Lager und Aufbewahrung: Script für gemeinsame Lager

Richte gemeinsame Lager in FiveM ein, etwa für Gangs oder Jobs. Mit Gewichtslimits und Scripts für ox_inventory und mehr.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Lagersysteme auf Roleplay-Servern

Jeder Roleplay-Server braucht ein robustes Lagersystem, das über das grundlegende Spielerinventar hinausgeht. Persönliche Inventare haben eine begrenzte Kapazität und Spieler sammeln durch Jobs, Crafting, Handel und Beute Gegenstände an, die sie behalten, aber nicht jederzeit mit sich tragen können. Lager- und Aufbewahrungssysteme lösen dieses Problem, indem sie physische Orte in der Spielwelt bieten, an denen Spieler Gegenstände ablegen und abholen können. Diese Systeme erfüllen verschiedene Gameplay-Zwecke: Persönliche Lager geben einzelnen Spielern zusätzlichen Platz, Lagerhäuser von Organisationen ermöglichen es Gangs und Unternehmen, Ressourcen gemeinsam zu verwalten, und mietbare Lagerräume schaffen einen Immobilienmarkt, der dem Server wiederkehrende Einnahmen bringt. Bei der technischen Umsetzung müssen gleichzeitige Zugriffe, Gewichts- und Slotlimits, Zugriffsberechtigungen und die Integration in das bestehende Inventar-Framework des Servers berücksichtigt werden.

Datenbankschema für Lager

Entwirf deine Lagerdatenbank so, dass sie mehrere Lagertypen mit flexiblen Besitz- und Zugriffsrechten unterstützt. Jede Lagereinheit benötigt eine eindeutige Kennung, einen Verweis auf den Besitzer, der entweder eine Spieler-citizenid oder eine Organisationskennung sein kann, Kapazitätsgrenzen für Gewicht und Slot-Anzahl sowie die eigentlichen, als JSON serialisierten Inventardaten. Füge eine Tabelle für Mietverhältnisse hinzu, die den Zahlungsstatus gemieteter Einheiten erfasst und diese automatisch sperrt, wenn die Miete überfällig ist. Über die Zugriffskontrolltabelle können Besitzer anderen Spielern den Zugriff auf ihre Lagereinheiten erlauben:

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)
);

Konfiguration der Lagerstandorte

Lege Lagerstandorte auf der Karte fest und berücksichtige dabei den Roleplay-Kontext. Industriegebiete in der Nähe des Hafens eignen sich gut für große gewerbliche Lager. Wohngebiete sind passend für persönliche Schließfächer. Unterirdische oder versteckte Orte eignen sich als Lagerhäuser für Gangs. Für jeden Standort musst du die physische Position, die verfügbaren Lagereinheit-Typen, die Mietpreise und das Interior-Shell konfigurieren, das beim Betreten geladen wird. Nutze GTA-Interior-Proxys oder benutzerdefinierte MLOs für die Lager-Interiors, damit Spieler tatsächlich ein Gebäude betreten, statt einfach nur mitten auf der Straße ein Menü zu öffnen:

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},
        },
    },
}

Inventarintegration

Dein Lagersystem muss sich nahtlos in das Inventar-Framework deines Servers integrieren, egal ob du ox_inventory, qb-inventory oder eine eigene Lösung verwendest. Die Lager-UI sollte der vertrauten Inventaroberfläche entsprechen, damit Spieler sofort verstehen, wie sie Gegenstände zwischen ihrem persönlichen Inventar und der Lagereinheit verschieben. Implementiere eine Drag-and-drop-Übertragung von Gegenständen mit Mengenauswahl für stapelbare Gegenstände. Wenn ein Spieler eine Lagereinheit öffnet, lade die gespeicherten Gegenstände aus der Datenbank und zeige sie zusammen mit dem persönlichen Inventar des Spielers an. Alle Gegenstandsbewegungen müssen serverseitig validiert werden, um Duplizierungs-Exploits zu verhindern. Prüfe, ob das Quellinventar den zu verschiebenden Gegenstand tatsächlich enthält, ob das Ziel über ausreichend Kapazität bei Gewicht und Slots verfügt und ob der Gegenstand nicht als unverkäuflich oder an den Spieler gebunden markiert ist. Verwende Datenbanktransaktionen für Gegenstandsübertragungen, damit das Entfernen aus einem Inventar und das Hinzufügen zum anderen atomar erfolgt:

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)

Mietzahlungssystem

Für gemietete Lagereinheiten ist ein wiederkehrendes Zahlungssystem erforderlich, das Mietern automatisch Gebühren berechnet und überfällige Konten verwaltet. Führe serverseitig einen geplanten Task aus, der regelmäßig alle Mieteinheiten prüft, fällige Mietzahlungen verarbeitet und überfällige Einheiten sperrt. Wenn eine Zahlung fällig ist, versuche, den Mietbetrag vom Bankkonto des Mieters abzubuchen. Wenn das Bankkonto nicht ausreichend gedeckt ist, markiere die Miete als überfällig und benachrichtige den Spieler. Gib Mietern mit Zahlungsrückstand einige Tage Kulanzzeit, bevor du ihre Einheit sperrst. Eine gesperrte Einheit verhindert, dass der Mieter auf seine eingelagerten Gegenstände zugreift, bis der offene Betrag bezahlt wurde. Bleibt die Miete über einen längeren Zeitraum unbezahlt, könnten die Inhalte versteigert oder entfernt werden, was für interessantes zusätzliches Gameplay sorgt. Implementiere ein Zahlungsverlaufsprotokoll, damit Mieter ihre Mietgebühren und Zahlungstermine über die Lagerverwaltung einsehen können.

Organisationen und gemeinsam genutzte Lager

Organisationen wie Gangs, Unternehmen und Behörden benötigen gemeinsam genutzte Lager, auf die mehrere Mitglieder mit passenden Berechtigungsstufen zugreifen können. Die Leitung der Organisation hat die volle Kontrolle und kann Mitglieder zur Zugriffsliste hinzufügen oder daraus entfernen, individuelle Berechtigungsstufen festlegen und Prüfprotokolle zu allen eingelagerten oder entnommenen Gegenständen einsehen. Normale Mitglieder können beispielsweise nur Gegenstände einlagern, ohne sie entnehmen zu können. Das ist praktisch, wenn Gangmitglieder Materialien zu einem gemeinsamen Vorrat beisteuern. Vertrauenswürdige Mitglieder erhalten vollen Zugriff zum Einlagern und Entnehmen. Implementiere ein umfassendes Prüfprotokoll, das jede Bewegung von Gegenständen in und aus dem gemeinsam genutzten Lager erfasst, einschließlich des ausführenden Spielers, des Gegenstands, der Menge und des Zeitstempels. So können Organisationsleitungen nachvollziehen, wer etwas beiträgt und wer möglicherweise aus dem gemeinsamen Vorrat stiehlt. Das Protokoll sollte über die Lager-UI mit Filteroptionen nach Spieler, Gegenstandstyp und Zeitraum einsehbar sein.

Kapazitäts-Upgrades und Stufen

Ermögliche es Spielern, ihre Lagerkapazität über ein Upgrade-System zu erhöhen, das einen Anreiz für den Spielfortschritt bietet. Beginne mit kleinen Lagereinheiten mit begrenzter Gewichts- und Slot-Kapazität und biete dann Upgrade-Stufen an, die diese Grenzen gegen Bezahlung erhöhen. Jede Upgrade-Stufe sollte zunehmend mehr kosten und so der Serverwirtschaft effektiv Geld entziehen. Upgrades können das maximale Gewicht erhöhen, zusätzliche Slots hinzufügen oder besondere Funktionen freischalten, etwa temperaturgeregelte Lagerung für verderbliche Gegenstände oder verstärkte Sicherheit, die die Einheit widerstandsfähiger gegen Einbrüche macht. Zeige die aktuelle Stufe und verfügbare Upgrades in der Lagerverwaltung an, mit klaren Preisen und den Vorteilen jeder Stufe. Du kannst manche Upgrade-Stufen an Erfolge oder Spielzeit-Meilensteine statt nur an Geldkosten knüpfen und so engagierte Spieler mit exklusiven Lagervorteilen belohnen, die sie nicht einfach kaufen können.

Sicherheit und Einbruchsmechaniken

Lagereinheiten sollten vor kriminellen Aktivitäten nicht vollständig sicher sein. Implementiere eine Einbruchsmechanik, mit der Spieler versuchen können, auf fremde Lagereinheiten zuzugreifen. So geraten eingelagerte Wertgegenstände in Gefahr und es entstehen Raubüberfall-Szenarien. Für einen Einbruch sollten bestimmte Werkzeuge wie Dietriche oder elektronische Überbrückungsgeräte erforderlich sein. Der Vorgang sollte eine erhebliche Zeit dauern und sichtbare sowie hörbare Hinweise erzeugen, die Spieler in der Nähe alarmieren, außerdem sollten der Besitzer des Lagers und die Strafverfolgungsbehörden benachrichtigt werden. Lagereinheiten höherer Stufen und mit Sicherheits-Upgrades sollten zunehmend schwerer aufzubrechen sein und bessere Werkzeuge sowie mehr Zeit erfordern. Gelingt ein Einbruch, erhält der Täter vorübergehend Zugriff auf das Lagerinventar und kann Gegenstände mitnehmen. Das System sollte den Einbruch jedoch protokollieren und dem Besitzer Beweismaterial liefern, etwa teilweise Informationen zur Kennung, die die Polizei untersuchen kann. So entsteht ein vollständiger Gameplay-Loop aus Lagerhausüberfällen, Ermittlungen und Konsequenzen, der die Dynamik zwischen Kriminellen und Strafverfolgung auf dem Server bereichert.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.