FiveM 仓库与存储:共享藏匿点脚本
设计 FiveM 共享仓库和存储。帮派藏匿处、工作储物柜、重量限制及适用于 ox_inventory 等的最佳存储脚本。
Agency Scripts
Agency Scripts 创始人兼首席开发者
角色扮演服务器中的存储系统
每个角色扮演服务器都需要比基本玩家库存更强大的存储系统。个人库存容量有限,玩家通过工作、制作、交易和掠夺积累的物品无法随时携带。仓库和存储系统通过游戏世界中的物理位置解决此问题,玩家可存取物品。这些系统满足多种玩法需求:个人储物柜为玩家提供额外空间,组织仓库让帮派和企业共享资源,租赁储物单元创造物业市场,为服务器经济带来持续收入。技术实现必须处理并发访问、重量和槽位限制、访问权限及与服务器现有库存框架的集成。
存储的数据库架构
设计存储数据库以支持多种存储类型,具备灵活的所有权和访问控制。每个存储单元需唯一标识符,所有者引用可为玩家 citizenid 或组织标识符,容量限制以重量和格位计,实际库存数据序列化为 JSON。包含租赁系统表,跟踪租赁单元的付款状态,逾期自动锁定。访问控制表允许所有者授权其他玩家访问其存储单元:
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)
);
仓库位置配置
定义地图上的仓库位置,注意角色扮演背景。港口附近的工业区适合大型商业仓库,住宅区适合个人储物柜,地下或隐蔽地点适合作为帮派藏匿处。每个位置需配置其物理位置、可用储物单元类型、租赁价格和玩家进入时加载的内部结构。使用 GTA 内部代理或自定义 MLO 作为仓库内部,使玩家能实际走进建筑,而非仅在街上打开菜单:
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},
},
},
}
库存集成
您的仓库系统必须与服务器使用的任何库存框架无缝集成,无论是ox_inventory、qb-inventory还是自定义解决方案。存储UI应镜像熟悉的库存界面,使玩家本能地理解如何在个人库存和存储单元之间移动物品。实现可拖放的物品转移,并为可堆叠物品提供数量选择。当玩家打开存储单元时,从数据库加载存储的物品,并与玩家的个人库存一起显示。所有物品移动必须在服务器端验证,以防止复制漏洞。检查源库存是否实际包含被移动的物品,目标库存在重量和格位上是否有足够容量,以及物品是否未被标记为不可交易或绑定玩家。使用数据库事务进行物品转移,以确保从一个库存移除并添加到另一个库存的操作是原子性的:
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)
租金支付系统
租赁储物单元需要一个自动收取租户费用并处理逾期账户的循环支付系统。运行服务器端定时任务,定期检查所有租赁单元,处理到期单元的付款,并锁定逾期单元。当租金到期时,尝试从租户银行账户扣款。如果账户余额不足,标记租赁为逾期并发送通知给玩家。给予逾期租户几天的宽限期,然后锁定其单元。锁定的单元阻止租户访问存储物品,直到结清欠款。如果租赁长时间未付,内容可能被拍卖或清理,创造有趣的二次玩法。实现支付历史记录日志,租户可通过储物管理界面查看租金和付款日期。
组织与共享存储
帮派、企业和政府部门等组织需要共享存储,多个成员可根据权限访问。组织领导拥有完全控制权,可添加或移除访问成员,设置个人权限等级,并查看所有存入或取出物品的审计日志。普通成员可能仅有存入权限,能添加物品但不能取出,这对帮派成员贡献材料到共享池很有用。受信成员则拥有存取全权限。实现全面的审计日志,记录所有物品进出共享存储的动作,包括执行玩家、物品及数量和时间戳。此审计轨迹帮助组织领导追踪贡献者及可能的盗窃者。日志可通过存储UI查看,并支持按玩家、物品类型和日期范围过滤。
容量升级和等级
允许玩家通过升级系统提升存储容量,提供进阶激励。起始为容量和重量有限的小型存储单元,随后提供升级等级以提升这些限制,需支付费用。每个升级等级费用递增,为服务器经济创造有意义的资金消耗。升级可增加最大重量、增加额外槽位,或解锁特殊功能,如温控存储以保存易腐物品,或加固安全性防止入侵。在存储管理界面显示当前等级和可用升级,明确价格及每级提供的好处。考虑将部分升级等级与成就或游戏时长挂钩,而非纯粹货币成本,奖励专注玩家以获得无法用钱直接购买的高级存储福利。
安全与入侵机制
存储单元不应完全免受犯罪活动影响。实现入室盗窃机制,允许玩家尝试访问非自己拥有的存储单元,为存储贵重物品带来风险并产生抢劫场景。入室过程应需要特定工具,如开锁器或电子绕过设备,耗时较长,并有可见和可听的指示,提醒附近玩家,同时触发对存储所有者和执法部门的通知。高等级存储单元及带安全升级的单元应更难入侵,需要更好的工具和更多时间。入侵成功后,犯罪者可临时访问存储库存并取走物品,但系统应记录入侵行为,并向所有者提供部分身份信息等证据,供警方调查。这创造了围绕仓库突袭、调查和后果的完整游戏循环,丰富了服务器的犯罪与执法动态。