feat: Flaschengroessen mit echter Laufzeit und persistenter Restluft
Lint / Lint Resource (push) Has been cancelled
Lint / Lint Resource (push) Has been cancelled
Der Sauerstoff war eine lokale Client-Variable: nicht persistent, an kein Item gebunden, weg bei Relog. Dazu ergab startingOxygenLevel=100 bei decayRate=1/s genau 100 Sekunden Tauchzeit statt Minuten. Sauerstoff-Logik: - Restsekunden statt Punkte, Verbrauch ueber GetGameTimer-Delta statt gezaehlter Wait(1000) - ein Tick laeuft unter Last laenger als eine Sekunde, was sich ueber einen langen Tauchgang aufsummiert - Float-Vergleiche (oxygenLevel % 10 == 0, == 0) raus, Warnschwellen als config.warnAtSeconds, jede Schwelle einmal pro Tauchgang - Verbrauch steigt mit der Tiefe (1 + tiefe/30, gedeckelt), abschaltbar - Anzeige als mm:ss Flaschen: - Vier Items mit capacity in Sekunden (300/600/900/1200) in config/shared.lua - Restluft in metadata.oxygen, Fuellstand zusaetzlich in metadata.durability, damit der Balken direkt im Inventar-Slot sichtbar ist - Kein decay auf den Items: ox_inventory loescht sonst die leere Flasche - Auswahlmenue beim Anlegen und Auffuellen (entfaellt bei nur einer Flasche) - diving_fill fuellt bis capacity statt auf einen Fixwert, verweigert volle Flaschen und verbraucht sich nur bei Erfolg - items_for_ox_inventory.lua zum Reinkopieren Server: - getTanks/syncTank als Callbacks, Restluft kann nur sinken und nicht schneller als maxDrainPerSecond pro echter Sekunde - SetMetadata ersetzt die Metadata-Tabelle komplett, daher wird der bestehende Inhalt kopiert statt nur oxygen gesetzt Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+142
-5
@@ -1,10 +1,147 @@
|
||||
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 = {}
|
||||
|
||||
--- @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]
|
||||
local oxygen = tonumber(slot.metadata and slot.metadata.oxygen) or 0
|
||||
|
||||
tanks[#tanks + 1] = {
|
||||
slot = slot.slot,
|
||||
name = name,
|
||||
label = (exports.ox_inventory:Items(name) or {}).label or name,
|
||||
oxygen = math.max(0, math.min(oxygen, tankConfig.capacity)),
|
||||
capacity = tankConfig.capacity
|
||||
}
|
||||
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 = tonumber(slot.metadata and slot.metadata.oxygen) or tankConfig.capacity
|
||||
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)
|
||||
|
||||
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('diving_gear', function(source)
|
||||
TriggerClientEvent('qbx_divegear:client:useGear', source)
|
||||
TriggerClientEvent('d4rk_divegear:client:useGear', source)
|
||||
end)
|
||||
|
||||
exports.qbx_core:CreateUseableItem('diving_fill', function(source)
|
||||
local success = lib.callback.await('qbx_divegear:client:fillTank', source)
|
||||
if success then
|
||||
exports.ox_inventory:RemoveItem(source, 'diving_fill', 1)
|
||||
local tanks = getTankList(source)
|
||||
|
||||
if #tanks == 0 then
|
||||
exports.qbx_core:Notify(source, locale('error.no_tank'), 'error')
|
||||
return
|
||||
end
|
||||
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, 'diving_fill', 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)
|
||||
|
||||
Reference in New Issue
Block a user