Gleiche Bug-Klasse wie bei der Flasche, nur hatte mein Fix davor ausschliesslich
die Flasche geprueft: wer diving_gear im Tauchgang weggab, blieb ausgeruestet
und atmete weiter.
Die Besitzpruefung deckt jetzt beides ab. verifyTank heisst verifyGear und gibt
{ gear, tank } zurueck - fehlt eines von beidem, kommt die Ausruestung ab, mit
der passenden Meldung. Umsortieren im Inventar bindet weiterhin nur auf den
neuen Slot um.
Die Item-Namen stehen jetzt als gearItem/fillItem in config/shared.lua, statt
verteilt als Zeichenketten im Server-Code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
187 lines
6.3 KiB
Lua
187 lines
6.3 KiB
Lua
local shared = require 'config.shared'
|
|
|
|
--- Zeitpunkt des letzten akzeptierten Syncs pro Spieler. Basis für die
|
|
--- Plausibilitätsprüfung: mehr als maxDrainPerSecond pro echter Sekunde kann
|
|
--- niemand verbrauchen.
|
|
local lastSync = {}
|
|
|
|
--- Restluft eines Slots. Ohne gesetzte Metadata (frisch aus dem Shop, per AddItem
|
|
--- oder Admin-Befehl) entscheidet newTanksFull, ob die Flasche voll oder leer ist.
|
|
--- Muss überall dieselbe Antwort geben, sonst hält die eine Stelle die Flasche für
|
|
--- leer und die andere für voll.
|
|
--- @return number
|
|
local function readOxygen(slot, tankConfig)
|
|
local stored = tonumber(slot.metadata and slot.metadata.oxygen)
|
|
|
|
if not stored then
|
|
return shared.newTanksFull and tankConfig.capacity or 0
|
|
end
|
|
|
|
return math.max(0, math.min(stored, tankConfig.capacity))
|
|
end
|
|
|
|
--- @param source number
|
|
--- @param slotId number
|
|
--- @return table? slot, table? tankConfig
|
|
local function getTankSlot(source, slotId)
|
|
if type(slotId) ~= 'number' then return end
|
|
|
|
local slot = exports.ox_inventory:GetSlot(source, slotId)
|
|
|
|
if not slot then return end
|
|
|
|
local tankConfig = shared.tankByName[slot.name]
|
|
|
|
if not tankConfig then return end
|
|
|
|
return slot, tankConfig
|
|
end
|
|
|
|
--- Schreibt die Restluft in die Metadata.
|
|
--- SetMetadata *ersetzt* die Metadata-Tabelle komplett (modules/inventory/server.lua),
|
|
--- deshalb wird der bestehende Inhalt kopiert statt nur oxygen gesetzt.
|
|
--- @return number oxygen akzeptierte Restsekunden
|
|
local function writeTank(source, slot, tankConfig, oxygen)
|
|
oxygen = math.floor(math.max(0, math.min(oxygen, tankConfig.capacity)))
|
|
|
|
local metadata = {}
|
|
|
|
for key, value in pairs(slot.metadata or {}) do
|
|
metadata[key] = value
|
|
end
|
|
|
|
metadata.oxygen = oxygen
|
|
-- durability zeigt den Füllstand direkt im Inventar-Slot an. Kein decay auf den
|
|
-- Items, sonst würde ox_inventory die leere Flasche löschen (items/server.lua).
|
|
metadata.durability = math.floor(oxygen / tankConfig.capacity * 100)
|
|
|
|
exports.ox_inventory:SetMetadata(source, slot.slot, metadata)
|
|
|
|
return oxygen
|
|
end
|
|
|
|
--- Alle Tauchflaschen im Inventar mit Füllstand.
|
|
--- @param source number
|
|
--- @return table[]
|
|
local function getTankList(source)
|
|
local found = exports.ox_inventory:Search(source, 'slots', shared.tankNames)
|
|
local tanks = {}
|
|
|
|
for i = 1, #shared.tankNames do
|
|
local name = shared.tankNames[i]
|
|
local slots = found and found[name]
|
|
local tankConfig = shared.tankByName[name]
|
|
|
|
if slots then
|
|
for j = 1, #slots do
|
|
local slot = slots[j]
|
|
|
|
tanks[#tanks + 1] = {
|
|
slot = slot.slot,
|
|
name = name,
|
|
label = (exports.ox_inventory:Items(name) or {}).label or name,
|
|
oxygen = readOxygen(slot, tankConfig),
|
|
capacity = tankConfig.capacity,
|
|
pressure = tankConfig.pressure,
|
|
maxDepth = tankConfig.maxDepth,
|
|
gas = tankConfig.gas
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
return tanks
|
|
end
|
|
|
|
--- @return number? accepted
|
|
local function syncTank(source, slotId, oxygen)
|
|
oxygen = tonumber(oxygen)
|
|
|
|
if not oxygen then return end
|
|
|
|
local slot, tankConfig = getTankSlot(source, slotId)
|
|
|
|
if not slot then return end
|
|
|
|
local current = readOxygen(slot, tankConfig)
|
|
local now = os.time()
|
|
local elapsed = lastSync[source] and (now - lastSync[source]) or shared.syncIntervalSeconds
|
|
|
|
lastSync[source] = now
|
|
|
|
-- Luft kann nur weniger werden, und nicht schneller als physikalisch möglich.
|
|
local floorValue = math.max(0, current - math.max(1, elapsed) * shared.maxDrainPerSecond)
|
|
local accepted = math.max(floorValue, math.min(oxygen, current))
|
|
|
|
return writeTank(source, slot, tankConfig, accepted)
|
|
end
|
|
|
|
lib.callback.register('d4rk_divegear:server:getTanks', getTankList)
|
|
|
|
--- Prüft, ob der Spieler Ausrüstung und Flasche überhaupt noch besitzt.
|
|
--- Ohne das konnte man beides im Tauchgang weggeben und trotzdem weiteratmen -
|
|
--- der Client hielt nur eine Slot-Nummer, die niemand mehr gegenprüfte.
|
|
--- @return { gear: boolean, tank: number? }
|
|
lib.callback.register('d4rk_divegear:server:verifyGear', function(source, slotId, name)
|
|
local gearCount = exports.ox_inventory:Search(source, 'count', shared.gearItem)
|
|
local slot = getTankSlot(source, slotId)
|
|
|
|
if slot and slot.name == name then
|
|
return { gear = (gearCount or 0) > 0, tank = slotId }
|
|
end
|
|
|
|
-- Nur umsortiert? Dann dieselbe Flaschensorte woanders im Inventar suchen,
|
|
-- sonst kostet jedes Verschieben im Inventar die Ausrüstung.
|
|
local found = shared.tankByName[name] and exports.ox_inventory:Search(source, 'slots', name)
|
|
|
|
return {
|
|
gear = (gearCount or 0) > 0,
|
|
tank = found and found[1] and found[1].slot or nil
|
|
}
|
|
end)
|
|
|
|
lib.callback.register('d4rk_divegear:server:syncTank', function(source, slotId, oxygen)
|
|
return syncTank(source, slotId, oxygen)
|
|
end)
|
|
|
|
--- Fire-and-Forget-Variante für Tod und Resource-Stop, wo auf keine Antwort
|
|
--- mehr gewartet werden kann.
|
|
RegisterNetEvent('d4rk_divegear:server:syncTankNow', function(slotId, oxygen)
|
|
syncTank(source, slotId, oxygen)
|
|
end)
|
|
|
|
AddEventHandler('playerDropped', function()
|
|
lastSync[source] = nil
|
|
end)
|
|
|
|
exports.qbx_core:CreateUseableItem(shared.gearItem, function(source)
|
|
TriggerClientEvent('d4rk_divegear:client:useGear', source)
|
|
end)
|
|
|
|
exports.qbx_core:CreateUseableItem(shared.fillItem, function(source)
|
|
local tanks = getTankList(source)
|
|
|
|
if #tanks == 0 then
|
|
exports.qbx_core:Notify(source, locale('error.no_tank'), 'error')
|
|
return
|
|
end
|
|
|
|
local slotId = lib.callback.await('d4rk_divegear:client:fillTank', source, tanks)
|
|
|
|
if not slotId then return end
|
|
|
|
local slot, tankConfig = getTankSlot(source, slotId)
|
|
|
|
if not slot then return end
|
|
|
|
-- Nach dem Progressbar nochmal prüfen: das Item kann in der Zwischenzeit weg sein.
|
|
if not exports.ox_inventory:RemoveItem(source, shared.fillItem, 1) then return end
|
|
|
|
writeTank(source, slot, tankConfig, tankConfig.capacity)
|
|
lastSync[source] = os.time()
|
|
|
|
-- Erst melden wenn das Item tatsächlich weg und die Flasche voll ist.
|
|
exports.qbx_core:Notify(source, locale('success.tube_filled'), 'success')
|
|
TriggerClientEvent('d4rk_divegear:client:tankRefilled', source, slot.slot, tankConfig.capacity)
|
|
end)
|