feat: Tiefengrenze ueber das Atemgas
Lint / Lint Resource (push) Has been cancelled

Macht ein Tiefenlimit mechanisch statt zur Absprache - und oeffnet die tiefen
Spots fuer den, der die richtige Flasche dabei hat.

Der Grund fuer die realen 40-45 m ist nicht der Druck, sondern der Stickstoff in
der Pressluft: tiefer wirkt er narkotisch, ab ~56 m wird auch der Sauerstoff
giftig. Wer tiefer will, atmet Trimix, wo Helium einen Teil des Stickstoffs
ersetzt. Genau so umgesetzt:

- maxDepth und gas pro Flasche in config/shared.lua; Pressluft 45 m,
  neues diving_tank_trimix 100 m
- Darunter Tiefenrausch in drei Stufen: Bildschirmeffekt, Kamerawackeln,
  hoeherer Luftverbrauch, in der letzten Stufe Schaden. Schwellen, Effekte,
  Staerken und Schaden komplett in config.narcosis
- Vorwarnung 5 m vor der Grenze, Tiefenanzeige im HUD wird rot und pulsiert
- Effekte werden beim Auftauchen, Ablegen, Tod und Resource-Stop aufgeraeumt
- Auswahlmenue zeigt Gas und Tiefengrenze pro Flasche
- Icon fuer die Trimix-Flasche, violett abgesetzt

Keine Dekompression - auf Wunsch bewusst weggelassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 09:11:12 +02:00
co-authored by Claude Opus 5
parent e8ffe8870d
commit e5f47610a1
20 changed files with 315 additions and 32 deletions
+105 -3
View File
@@ -55,13 +55,61 @@ local function getDepth()
return math.max(0.0, waterZ - coords.z)
end
--- Aktuell aktive Tiefenrausch-Stufe (Index in config.narcosis.stages), 0 = keine.
local narcosisStage = 0
local narcosisWarned = false
local narcosisDamageBuffer = 0.0
--- Wie weit die aktuelle Tiefe unter der Grenze der getragenen Flasche liegt.
--- @return number Meter unterhalb der Grenze, negativ solange alles gut ist
local function getDepthOverLimit()
if not activeTank or not activeTank.maxDepth then return -math.huge end
return currentDepth - activeTank.maxDepth
end
--- Setzt Bildschirmeffekt und Kamerawackeln auf die angegebene Stufe.
--- @param stage number 0 = alles aus
local function applyNarcosisStage(stage)
if stage == narcosisStage then return end
narcosisStage = stage
ClearTimecycleModifier()
StopGameplayCamShaking(true)
local data = stage > 0 and config.narcosis.stages[stage]
if not data then return end
if data.timecycle then
SetTimecycleModifier(data.timecycle)
SetTimecycleModifierStrength(data.strength or 1.0)
end
if data.shake then
ShakeGameplayCam(data.shake, data.shakeIntensity or 0.5)
end
end
--- Verbrauchsfaktor. Tiefer tauchen kostet mehr Luft.
--- Nutzt die geglättete Tiefe aus dem HUD-Thread, damit Wellengang den Verbrauch
--- nicht im Sekundentakt springen lässt.
local function getDecayFactor()
if not config.depthDecayEnabled then return 1.0 end
local factor = 1.0
return math.min(config.maxDecayFactor, 1.0 + currentDepth / config.depthDecayReference)
if config.depthDecayEnabled then
factor = math.min(config.maxDecayFactor, 1.0 + currentDepth / config.depthDecayReference)
end
-- Tiefenrausch: unter Stress atmet man mehr.
local stage = narcosisStage > 0 and config.narcosis.stages[narcosisStage]
if stage then
factor = factor * (stage.drainFactor or 1.0)
end
return factor
end
--- @param ped number? default cache.ped - beim Model-Wechsel muss der neue Ped explizit rein
@@ -198,8 +246,11 @@ local function removeGear(outOfAir, immediateSync)
currentGear.enabled = false
setLamp(false)
applyNarcosisStage(0)
activeTank = nil
warned = {}
narcosisWarned = false
narcosisDamageBuffer = 0.0
deleteGear()
disableScuba(outOfAir)
@@ -220,7 +271,8 @@ local function chooseTank(tanks)
options[i] = {
title = tank.label,
description = locale('info.tank_remaining', formatTime(tank.oxygen),
math.floor(tank.oxygen / tank.capacity * 100)),
math.floor(tank.oxygen / tank.capacity * 100),
tank.gas or '?', math.floor(tank.maxDepth or 0)),
icon = 'fa-solid fa-bottle-water',
onSelect = function() selection:resolve(tank) end
}
@@ -331,6 +383,8 @@ local function sendHudState(visible)
reserve = pressure > 0 and pressure <= config.reservePressure,
level = level,
deep = currentDepth >= config.deepWarningDepth,
-- Grenze der getragenen Flasche überschritten: Tiefenrausch läuft.
overLimit = config.narcosis.enabled and getDepthOverLimit() >= 0,
hasLamp = true,
lamp = lampOn
})
@@ -396,6 +450,51 @@ local function startHudThread()
end)
end
--- Wertet die Tiefe gegen die Grenze der getragenen Flasche aus und setzt Effekte,
--- Verbrauchsaufschlag und Schaden. Läuft auch über Wasser weiter, damit die
--- Effekte beim Auftauchen wieder verschwinden.
--- @param elapsed number Sekunden seit dem letzten Aufruf
local function updateNarcosis(elapsed)
if not config.narcosis.enabled then return end
local over = getDepthOverLimit()
local stages = config.narcosis.stages
local stage = 0
for i = 1, #stages do
if over >= stages[i].over then stage = i end
end
applyNarcosisStage(stage)
-- Vorwarnung, solange noch Zeit zum Umkehren ist.
if activeTank and over >= -config.narcosis.warnBeforeMeters then
if not narcosisWarned then
narcosisWarned = true
exports.qbx_core:Notify(locale('info.depth_limit', math.floor(activeTank.maxDepth or 0)), 'warning')
end
else
narcosisWarned = false
end
local data = stage > 0 and stages[stage]
if not data or (data.damagePerTick or 0) <= 0 then
narcosisDamageBuffer = 0.0
return
end
-- Schaden sammeln statt jeden Tick zu runden, sonst geht bei 250 ms Takt
-- alles unter 4 Schaden pro Sekunde verloren.
narcosisDamageBuffer = narcosisDamageBuffer + data.damagePerTick * elapsed
if narcosisDamageBuffer >= 1.0 then
local damage = math.floor(narcosisDamageBuffer)
narcosisDamageBuffer -= damage
ApplyDamageToPed(cache.ped, damage, false)
end
end
--- Zieht Luft über die tatsächlich vergangene Zeit ab statt über gezählte Ticks.
--- Ein Wait(1000) läuft bei Last spürbar länger als eine Sekunde - über einen
--- 20-Minuten-Tauchgang summiert sich das sichtbar auf.
@@ -411,6 +510,8 @@ local function startOxygenLevelDecrementerThread()
local elapsed = (now - lastTick) / 1000
lastTick = now
updateNarcosis(elapsed)
if not activeTank then goto continue end
if IsPedSwimmingUnderWater(cache.ped) and activeTank.oxygen > 0 then
@@ -622,6 +723,7 @@ AddEventHandler('onResourceStop', function(resource)
syncTank(true)
setLamp(false)
applyNarcosisStage(0)
deleteGear()
SendNUIMessage({ action = 'state', visible = false })
SetEnableScuba(cache.ped, false)