FiveM-Waffenshop-System: Skripte für lizenzierte Waffengeschäfte
Richte einen lizenzierten Waffenshop ein. Mit Genehmigungen, Sicherheitsüberprüfungen, Munition, Aufsätzen und passenden Skripten fürs Rollenspiel.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Überblick über die Architektur des Waffenshops
Ein Waffenshop-System auf einem FiveM-Rollenspielserver muss den Zugang für gesetzestreue Bürger mit realistischen Einschränkungen ausbalancieren, die für mehr Spieltiefe sorgen. Die Architektur ist in zwei separate Bereiche aufgeteilt: legale Shops, die Waffenlizenzen, Hintergrundüberprüfungen und Kaufabklingzeiten durchsetzen, und illegale Schwarzmarkthändler, die nicht zurückverfolgbare Waffen zu höheren Preisen und ohne Papierkram verkaufen. Beide Systeme verwenden dieselbe zugrunde liegende Inventar- und Transaktionslogik, unterscheiden sich jedoch bei den Zugangsbedingungen und den Metadaten der Waffen. Legale Waffen erhalten Seriennummern, die mit der citizen ID des Käufers verknüpft sind und von der Polizei zurückverfolgt werden können. Bei Schwarzmarktwaffen sind die Seriennummern unkenntlich gemacht und lassen sich keinem bestimmten Spieler zuordnen. Dieses Design mit zwei getrennten Vertriebswegen schafft natürliche Roleplay-Spannungen, weil Spieler zwischen der Bequemlichkeit und den geringeren Kosten legaler Käufe und der Anonymität des Schwarzmarkts abwägen müssen.
Datenbankschema für Waffen und Lizenzen
Deine Datenbank muss den Waffenbestand pro Shop, einzelne Waffenseriennummern, Spielerlizenzen und die Kaufhistorie erfassen. Das Seriennummernsystem ist entscheidend, weil es Waffenshops mit den Ermittlungssystemen der Polizei verbindet. Jede legal gekaufte Waffe erhält eine eindeutige Seriennummer, die die Polizei bei Verkehrskontrollen oder Ermittlungen am Tatort abfragen kann. Entwirf das Schema so, dass sowohl die Bestandsverwaltung der Shops als auch die Nachverfolgung einzelner Waffen unterstützt wird:
CREATE TABLE IF NOT EXISTS weapon_shops (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
type ENUM('legal', 'blackmarket') DEFAULT 'legal',
coords_x FLOAT NOT NULL,
coords_y FLOAT NOT NULL,
coords_z FLOAT NOT NULL,
is_active BOOLEAN DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS weapon_inventory (
id INT AUTO_INCREMENT PRIMARY KEY,
shop_id INT NOT NULL,
weapon_name VARCHAR(50) NOT NULL,
label VARCHAR(100) NOT NULL,
price INT NOT NULL,
ammo_price INT DEFAULT 0,
category ENUM('handguns', 'smgs', 'rifles', 'shotguns', 'melee', 'throwables') NOT NULL,
license_required ENUM('none', 'basic', 'advanced', 'military') DEFAULT 'none',
stock INT DEFAULT -1,
INDEX idx_shop (shop_id),
FOREIGN KEY (shop_id) REFERENCES weapon_shops(id)
);
CREATE TABLE IF NOT EXISTS weapon_serials (
serial VARCHAR(20) PRIMARY KEY,
weapon_name VARCHAR(50) NOT NULL,
owner_citizenid VARCHAR(50) DEFAULT NULL,
is_scratched BOOLEAN DEFAULT FALSE,
purchased_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
shop_id INT DEFAULT NULL,
INDEX idx_owner (owner_citizenid)
);
CREATE TABLE IF NOT EXISTS weapon_licenses (
id INT AUTO_INCREMENT PRIMARY KEY,
citizenid VARCHAR(50) NOT NULL,
license_type ENUM('basic', 'advanced', 'military') NOT NULL,
issued_by VARCHAR(50) DEFAULT NULL,
issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
revoked BOOLEAN DEFAULT FALSE,
UNIQUE KEY unique_license (citizenid, license_type)
);
Die weapon_serials Die Tabelle erfasst jede einzelne Waffe über ihren gesamten Lebenszyklus hinweg. Wenn ein Spieler eine Waffe legal kauft, wird eine Seriennummer generiert und mit seiner citizen ID verknüpft. Wenn er die Seriennummer über einen Schwarzmarktdienst unkenntlich macht, wird das is_scratched Flag auf true gesetzt und die owner_citizenid wird auf null gesetzt. Das Feld stock in weapon_inventory verwendet -1 als Kennzeichnung für unbegrenzten Bestand, während positive Werte begrenzte Vorräte ermöglichen, die Knappheit schaffen und die Nachfrage auf dem Schwarzmarkt steigern.
Implementierung legaler Waffenshops
Legale Waffenshops sind für die meisten Spieler auf einem Rollenspielserver die wichtigste Möglichkeit, Schusswaffen zu erwerben. Sie setzen Lizenzanforderungen durch, wenden Kaufabklingzeiten an, um Großeinkäufe zu verhindern, und generieren rückverfolgbare Seriennummern für jede verkaufte Waffe. Die Shop-UI sollte Waffen nach Kategorien gruppiert anzeigen und klar kennzeichnen, welche Lizenzstufe für jede Waffe erforderlich ist. Spieler ohne die passende Lizenz können die Waffe sehen, sie aber nicht kaufen. So werden sie dazu ermutigt, den Lizenzierungsprozess mit Regierungsvertretern auszuspielen. Implementiere ein Abklingzeit-System, das verhindert, dass Spieler innerhalb eines Echtzeit-Tages mehr als eine festgelegte Anzahl an Waffen kaufen, um zu verhindern, dass sie Waffen für den Weiterverkauf auf dem Schwarzmarkt horten:
RegisterNetEvent('weaponshop:server:purchase', function(shopId, weaponName)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local citizenid = Player.PlayerData.citizenid
local item = GetShopItem(shopId, weaponName)
if not item then
TriggerClientEvent('QBCore:Notify', src, 'Item not available', 'error')
return
end
-- Check license requirement
if item.license_required ~= 'none' then
local hasLicense = HasValidLicense(citizenid, item.license_required)
if not hasLicense then
TriggerClientEvent('QBCore:Notify', src, 'You need a ' .. item.license_required .. ' weapons license', 'error')
return
end
end
-- Check purchase cooldown
local recentPurchases = GetRecentPurchaseCount(citizenid, 86400) -- last 24h
if recentPurchases >= Config.DailyPurchaseLimit then
TriggerClientEvent('QBCore:Notify', src, 'Daily purchase limit reached', 'error')
return
end
-- Check stock
if item.stock ~= -1 then
if item.stock <= 0 then
TriggerClientEvent('QBCore:Notify', src, 'Out of stock', 'error')
return
end
end
-- Check funds
if Player.PlayerData.money.bank < item.price then
TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds', 'error')
return
end
-- Generate serial number
local serial = GenerateWeaponSerial()
-- Process purchase
Player.Functions.RemoveMoney('bank', item.price, 'weapon-purchase')
Player.Functions.AddItem(weaponName, 1, false, { serial = serial })
-- Record serial
MySQL.insert('INSERT INTO weapon_serials (serial, weapon_name, owner_citizenid, shop_id) VALUES (?, ?, ?, ?)',
{ serial, weaponName, citizenid, shopId })
-- Update stock
if item.stock ~= -1 then
MySQL.update('UPDATE weapon_inventory SET stock = stock - 1 WHERE shop_id = ? AND weapon_name = ?',
{ shopId, weaponName })
end
TriggerClientEvent('QBCore:Notify', src, 'Purchased ' .. item.label .. ' (S/N: ' .. serial .. ')', 'success')
end)
function GenerateWeaponSerial()
local chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
local serial = ''
for i = 1, 3 do
serial = serial .. chars:sub(math.random(#chars), math.random(#chars))
end
serial = serial .. '-'
for i = 1, 5 do
serial = serial .. tostring(math.random(0, 9))
end
return serial
end
Munition und Aufsätze
Die Munitionsverwaltung sorgt für einen Ressourcenverbrauch, der Spieler in die Wirtschaft einbindet und Exploits für unendlich viel Munition verhindert. Statt Munition als generischen Gegenstand zu verkaufen, solltest du sie an bestimmte Kaliber binden, die zu den Waffenkategorien passen. Eine Pistole verwendet 9mm-Munition, eine SMG kann entweder dieselbe 9mm-Munition oder .45 ACP verwenden und Gewehre nutzen je nach Modell 5.56 oder 7.62. Dieses Kalibersystem sorgt für ein realistischeres Spielerlebnis und gibt Shop-Besitzern Flexibilität bei der Preisgestaltung verschiedener Munitionstypen. Speichere die Munitionsanzahl in den Waffenmetadaten statt als separate Inventargegenstände, damit jede Waffe ihre eigene geladene Munition erfasst. Aufsätze nutzen dasselbe Metadatenmuster: Jeder Waffen-Gegenstand speichert eine Liste der installierten Komponenten, etwa Zielfernrohre, Schalldämpfer, erweiterte Magazine und Taschenlampen:
Config.AmmoTypes = {
['ammo_9mm'] = { label = '9mm Rounds', price = 5, amount = 24, weapons = {'WEAPON_PISTOL', 'WEAPON_COMBATPISTOL', 'WEAPON_SMG'} },
['ammo_45acp'] = { label = '.45 ACP Rounds', price = 7, amount = 24, weapons = {'WEAPON_APPISTOL', 'WEAPON_MACHINEPISTOL'} },
['ammo_556'] = { label = '5.56 Rounds', price = 12, amount = 30, weapons = {'WEAPON_CARBINERIFLE', 'WEAPON_ASSAULTRIFLE'} },
['ammo_762'] = { label = '7.62 Rounds', price = 15, amount = 30, weapons = {'WEAPON_SNIPERRIFLE', 'WEAPON_MARKSMANRIFLE'} },
['ammo_12gauge'] = { label = '12ga Shells', price = 10, amount = 8, weapons = {'WEAPON_PUMPSHOTGUN', 'WEAPON_ASSAULTSHOTGUN'} },
}
Config.Attachments = {
['attachment_suppressor'] = {
label = 'Suppressor',
price = 8500,
component = 'COMPONENT_AT_PI_SUPP_02',
compatible = {'WEAPON_PISTOL', 'WEAPON_COMBATPISTOL', 'WEAPON_SMG', 'WEAPON_CARBINERIFLE'},
license_required = 'advanced',
},
['attachment_scope'] = {
label = 'Holographic Scope',
price = 3200,
component = 'COMPONENT_AT_SCOPE_MACRO_02',
compatible = {'WEAPON_SMG', 'WEAPON_CARBINERIFLE', 'WEAPON_ASSAULTRIFLE'},
license_required = 'basic',
},
['attachment_extmag'] = {
label = 'Extended Magazine',
price = 4500,
component = 'COMPONENT_CARBINERIFLE_CLIP_02',
compatible = {'WEAPON_CARBINERIFLE', 'WEAPON_ASSAULTRIFLE', 'WEAPON_SMG'},
license_required = 'basic',
},
['attachment_flashlight'] = {
label = 'Flashlight',
price = 1200,
component = 'COMPONENT_AT_AR_FLSH',
compatible = {'WEAPON_PISTOL', 'WEAPON_COMBATPISTOL', 'WEAPON_CARBINERIFLE', 'WEAPON_PUMPSHOTGUN'},
license_required = 'none',
},
}
Wenn ein Spieler einen Aufsatz kauft, prüfe, ob seine aktuelle Waffe in der Liste compatible enthalten ist, kontrolliere seine Lizenzstufe und füge dann den Komponenten-Hash zur Metadatentabelle der Waffe hinzu. Rüste bei jedem Anlegen der Waffe die gespeicherten Komponenten aus und wende sie an mit GiveWeaponComponentToPed. Dieser Ansatz stellt sicher, dass Anbauteile sitzungsübergreifend erhalten bleiben und nicht durch Manipulation des Clients dupliziert werden können.
Schwarzmarkt-Mechaniken
Auf dem Schwarzmarkt entsteht die eigentliche Rollenspieltiefe. Anders als in statischen legalen Geschäften sollten Schwarzmarkthändler gefährlich und exklusiv wirken. Implementiere wechselnde Händlerstandorte, die sich alle paar Stunden ändern, damit Spieler Insiderwissen oder Kontakte brauchen, um sie zu finden. Führe ein Reputationssystem ein, bei dem neue Spieler erst durch kleinere Käufe das Vertrauen des Händlers gewinnen müssen, bevor sie Zugang zu Waffen der höchsten Stufe erhalten. Auf dem Schwarzmarkt werden Waffen ohne Seriennummern, eingeschränkte Ausrüstung in Militärqualität, die legale Geschäfte nicht anbieten dürfen, sowie Dienstleistungen wie das Entfernen von Seriennummern verkauft, durch das legal gekaufte Waffen nicht mehr zurückverfolgt werden können. Setze alle Preise 2-3x höher an als in legalen Geschäften, um den Risikoaufschlag widerzuspiegeln und echte wirtschaftliche Spannungen zwischen legalen und illegalen Wegen zu schaffen:
Config.BlackMarket = {
rotationInterval = 10800, -- 3 hours between location changes
locations = {
{ coords = vector3(89.98, -1810.87, 24.98), heading = 230.0, label = 'Underground Parking' },
{ coords = vector3(1394.27, 1141.48, 114.33), heading = 90.0, label = 'Desert Warehouse' },
{ coords = vector3(-58.21, 6443.31, 31.43), heading = 45.0, label = 'Paleto Docks' },
{ coords = vector3(981.45, -1812.66, 31.14), heading = 180.0, label = 'Industrial Zone' },
},
reputationTiers = {
[0] = { label = 'Unknown', items = {'WEAPON_KNIFE', 'WEAPON_BAT'} },
[1] = { label = 'Associate', items = {'WEAPON_PISTOL', 'WEAPON_MICROSMG', 'ammo_9mm'} },
[2] = { label = 'Trusted', items = {'WEAPON_SMG', 'WEAPON_PUMPSHOTGUN', 'ammo_45acp', 'ammo_12gauge', 'attachment_suppressor'} },
[3] = { label = 'Inner Circle', items = {'WEAPON_ASSAULTRIFLE', 'WEAPON_CARBINERIFLE', 'ammo_556', 'service_scratch_serial'} },
[4] = { label = 'Arms Dealer', items = {'WEAPON_SNIPERRIFLE', 'WEAPON_RPG', 'WEAPON_GRENADELAUNCHER', 'ammo_762', 'armor_heavy'} },
},
priceMultiplier = 2.5,
reputationGainPerPurchase = 0.15,
}
-- Serial scratching service
RegisterNetEvent('blackmarket:server:scratchSerial', function(weaponSlot)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local citizenid = Player.PlayerData.citizenid
local reputation = GetBlackMarketReputation(citizenid)
if reputation < 3 then
TriggerClientEvent('QBCore:Notify', src, 'You don\'t have enough reputation', 'error')
return
end
local item = Player.Functions.GetItemBySlot(weaponSlot)
if not item or not item.info or not item.info.serial then
TriggerClientEvent('QBCore:Notify', src, 'No weapon with serial found', 'error')
return
end
local cost = Config.ScratchSerialPrice -- e.g., 15000
if Player.PlayerData.money.cash < cost then
TriggerClientEvent('QBCore:Notify', src, 'Not enough cash', 'error')
return
end
Player.Functions.RemoveMoney('cash', cost, 'serial-scratch')
-- Update database
MySQL.update('UPDATE weapon_serials SET is_scratched = TRUE, owner_citizenid = NULL WHERE serial = ?',
{ item.info.serial })
-- Update item metadata
item.info.serial = 'SCRATCHED'
Player.Functions.SetInventoryItem(item.name, item.amount, item.info, weaponSlot)
TriggerClientEvent('QBCore:Notify', src, 'Serial number removed', 'success')
end)
Waffenlizenzsystem
Das Lizenzsystem schafft eine wichtige bürokratische Rollenspielebene, die Waffengeschäfte mit Regierungs- und Polizeifraktion verbindet. Spieler sollten im Rathaus oder auf einer Polizeiwache eine Waffenlizenz beantragen, eine Hintergrundüberprüfung durchlaufen, bei der ihr Strafregister geprüft wird, und möglicherweise an einem von einem Beamten geleiteten Schießtest teilnehmen. Implementiere drei Lizenzstufen: Basis für Handfeuerwaffen und Schrotflinten, Fortgeschritten für SMGs und Gewehre sowie Militär für schwere Waffen, die nur selten an Zivilisten ausgegeben werden. Lizenzen sollten ein Ablaufdatum haben, üblicherweise 30 Echtzeit-Tage, und müssen dann erneuert werden. Polizeibeamte brauchen einen Befehl, um Lizenzen zu widerrufen, wenn Spieler wegen Gewaltverbrechen verurteilt werden. Dadurch wird ihnen sofort der Kauf in legalen Geschäften verwehrt und eine Nachfrage auf dem Schwarzmarkt geschaffen. Speichere den Lizenzstatus in der Datenbank und prüfe ihn bei jedem Kaufversuch serverseitig. Vertraue niemals auf clientseitige Lizenzprüfungen, da sie trivial umgangen werden können.
Clientseitige Shop-Oberfläche
Die NUI des Waffengeschäfts sollte wie ein professioneller Laden wirken, in dem Waffen in kategorisierten Tabs angezeigt werden. Zeige für jede Waffe ihren Namen, Preis, die erforderliche Lizenz, den aktuellen Lagerbestand und ein kleines Vorschaubild oder Symbol an. Füge einen eigenen Munitionsbereich hinzu, in dem Spieler zuerst ihre Waffe auswählen und anschließend nur passende Munitionstypen sehen. Der Bereich für Anbauteile funktioniert ähnlich und zeigt nur Komponenten an, die mit der aktuell ausgerüsteten Waffe des Spielers kompatibel sind. Implementiere ein Warenkorbsystem, in dem Spieler mehrere Artikel sammeln und in einer einzigen Transaktion bezahlen können, statt jeden Artikel einzeln zu kaufen. Verwende für die Schwarzmarkt-Oberfläche ein dunkleres Design mit einem Fortschrittsbalken für den Ruf, der anzeigt, wie nah der Spieler an der nächsten Stufe ist. Zeige gesperrte Artikel als Silhouetten mit einem Hinweis auf den erforderlichen Ruf an, damit Spieler wissen, worauf sie hinarbeiten. Beide Shoptypen sollten beim Öffnen einen Server-Callback auslösen, um aktuelle Preise und Lagerbestände abzurufen, statt sich auf zwischengespeicherte Konfigurationswerte zu verlassen, da Admins die Preise dynamisch an die wirtschaftliche Situation des Servers anpassen können.
Anti-Exploit und Polizei-Integration
Waffengeschäfte sind ein beliebtes Ziel für Exploiter, die versuchen, teure Waffen zu duplizieren oder Gegenstände ohne Bezahlung zu erzeugen. Validiere jeden Kauf serverseitig, indem du das Geld des Spielers, den Lizenzstatus, die Nähe zum Geschäft und die Kaufabklingzeit prüfst, bevor du Gegenstände erstellst. Protokolliere jede Waffentransaktion mit den Kennungen des Käufers, der Waffenseriennummer, der Shop-ID und dem Zeitstempel, damit Admins die Herkunft jeder Waffe auf dem Server zurückverfolgen können. Integriere das Polizei-MDT-System, damit Beamte bei Ermittlungen eine Waffenseriennummer nachschlagen und den registrierten Besitzer, das Kaufdatum und den Status der Seriennummer einsehen können. Implementiere einen Export für das Waffenregister, der einen Bericht über alle registrierten Waffen einer bestimmten Person erstellt, was bei Bewährungsprüfungen nützlich ist. Begrenze die Häufigkeit von Kaufereignissen, um schnell aufeinanderfolgende Anfragen zu verhindern, die durch Race Conditions die Abklingzeitprüfungen umgehen könnten. Sende Discord-Webhook-Benachrichtigungen, wenn hochrangige Militärwaffen auf dem Schwarzmarkt verkauft werden, damit dein Moderationsteam möglichen Missbrauch überwachen kann. Die Kombination aus ordnungsgemäßer serverseitiger Validierung, umfassender Protokollierung und Polizei-Integration schafft eine realistische und zugleich ausnutzungssichere Waffenwirtschaft.