feat(light): auf die spieleigene Scuba-Lampe umgestellt
Lint / Lint Resource (push) Has been cancelled

GTA bringt seit v1493 eine eigene Lampe an der Tauchausruestung mit:

    SetEnableScubaGearLight(ped, toggle)   0xEE2476B9EE4A094F
    IsScubaGearLightEnabled(ped)           0x88274C11CF0D866D

Die sitzt an der richtigen Stelle, leuchtet in Blickrichtung und braucht weder
Prop noch Offsets noch einen Draw-Thread. Damit faellt der komplette
DrawSpotLight-Aufbau weg: Kegelwerte, Farbe, Schatten, Distanzfilter,
Lichter-Limit, die bone-relative Richtungsrechnung und das Sortieren der
Kandidaten pro Frame. client/light.lua schrumpft von ~110 auf ~55 Zeilen.

Gefunden ueber wobozkyng/esx_scuba.

- Der Flag ist ein lokaler Ped-Zustand, der Statebag bleibt also. Gesetzt wird
  er per AddStateBagChangeHandler und zusaetzlich alle 500ms nachgezogen, weil
  Peds beim Streaming, Respawn und Model-Wechsel neu erzeugt werden
- Die Lampe haengt an der Scuba-Kleidung (Component 8). Das Outfit wird bewusst
  nicht angefasst; wer sie nicht traegt, bekommt beim Einschalten eine Meldung
  statt stiller Wirkungslosigkeit
- /divelampcheck sagt, ob das aktuelle Outfit eine Lampe hat
- Prop ist jetzt optional und per Default aus

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:45:02 +02:00
co-authored by Claude Opus 5
parent 037acf1bfe
commit d2f6e3cd30
6 changed files with 119 additions and 189 deletions
+37 -37
View File
@@ -172,44 +172,31 @@ FiveM* umlegbar.
| Option | Default | Bedeutung | | Option | Default | Bedeutung |
|---|---|---| |---|---|---|
| `lampKey` | `'l'` | Vorbelegung | | `lampKey` | `'l'` | Vorbelegung |
| `lampBone` | `12844` | Bone für Prop **und** Licht — derselbe, damit der Strahl aus der Lampe kommt. `28422` = Hand statt Kopf | | `lampReconcileIntervalMs` | `500` | Wie oft der Lampen-Zustand auf die Peds in der Nähe nachgezogen wird |
| `lampProp` | `'prop_cs_police_torch'` | Optik. Ungültiges Model → Warnung im Log, Licht funktioniert trotzdem. `''` = kein Prop | | `lampProp` | `''` | Optionales Prop am Kopf, rein optisch. Die Lampe leuchtet unabhängig davon |
| `lampPropOffset` / `lampPropRotation` | `vec3(0,0,0)` | Sitz des Props am Bone — mit `/divelamp` einstellen | | `lampBone` / `lampPropOffset` / `lampPropRotation` | `12844` / `vec3(0,0,0)` | Sitz des optionalen Props — mit `/divelamp` einstellen |
| `lampOffset` | `vec3(0,0,0)` | Ursprung des Lichtkegels, bone-relativ |
| `lampDirection` | `vec3(0, 1, 0)` | Richtung des Kegels, bone-relativ |
| `lampColour` | `{255, 250, 235}` | RGB |
| `lampDistance` / `lampBrightness` / `lampRoundness` / `lampRadius` / `lampFalloff` | `28/3/0/16/2` | Form des Lichtkegels, Werte aus `d4rk_phone` übernommen |
| `lampShadows` | `false` | `true` nutzt `DrawSpotLightWithShadow` — sieht besser aus, kostet aber deutlich Performance |
| `lampDrawDistance` | `45.0` | Ab hier werden fremde Lampen nicht mehr gezeichnet |
| `lampMaxLights` | `4` | Gleichzeitige Lampen, die nächstgelegenen gewinnen |
> **Beim Balancing beachten:** `maxDrainPerSecond` (shared) muss über > **Beim Balancing beachten:** `maxDrainPerSecond` (shared) muss über
> `maxDecayFactor` (client) liegen. Sonst hält der Server den ehrlichen Verbrauch > `maxDecayFactor` (client) liegen. Sonst hält der Server den ehrlichen Verbrauch
> in großer Tiefe für Cheat und schreibt Luft zurück. > in großer Tiefe für Cheat und schreibt Luft zurück.
## Lampe einstellen ## Debug-Befehle
Wo Prop und Lichtkegel am Kopf sitzen, hängt daran, wie das Prop-Model selbst Mit `debug = true` in `config/client.lua`:
ausgerichtet ist — das lässt sich nicht am Schreibtisch erraten. Mit
`debug = true` in `config/client.lua` gibt es zwei Befehle, die die Werte im
laufenden Spiel ändern und die fertige Config-Zeile ins Log schreiben:
``` ```
/divelamp <x> <y> <z> <rx> <ry> <rz> Sitz des Lampen-Props /divelampcheck Hat das aktuelle Outfit eine Scuba-Lampe?
/divelight <x> <y> <z> <dx> <dy> <dz> Ursprung und Richtung des Lichtkegels /divelamp <x> <y> <z> <rx> <ry> <rz> Sitz des optionalen Lampen-Props
``` ```
Das Prop wird sofort neu angehängt, das Licht zieht ohne Verzögerung mit — kein `/divelamp` hängt das Prop sofort neu an und schreibt die fertige Config-Zeile ins
Resource-Restart pro Versuch. Sind die Werte gut, die ausgegebenen Zeilen in die Log — kein Resource-Restart pro Versuch. Alternativ mit einem Prop-Attach-Editor
Config übernehmen und `debug` wieder ausschalten. wie [noted_propattacher](https://github.com/NotedDevelopment/noted_propattacher)
ausmessen.
Alternativ mit einem Prop-Attach-Editor wie > Ein Tauchlampen-Prop gibt es im Basegame nicht. Was es gibt, sind Varianten
[noted_propattacher](https://github.com/NotedDevelopment/noted_propattacher) > derselben Polizei-Taschenlampe: `prop_cs_police_torch`, `prop_police_torch`,
ausmessen: Prop spawnen, mit der Maus an die Stelle ziehen, Werte übernehmen. > `prop_scn_police_torch`. Für die Beleuchtung braucht es das Prop ohnehin nicht.
> Im Basegame gibt es **keine** Tauchlampe. `prop_cs_police_torch` ist eine
> Polizei-Taschenlampe; Varianten davon sind `prop_police_torch` und
> `prop_scn_police_torch`. Wer eine echte Tauchlampe will, braucht ein Custom-Model.
## Exports ## Exports
@@ -222,18 +209,31 @@ Gedacht für HUD- und Screenshot-Resources. Zusätzlich folgt das HUD automatisc
`IsHudHidden()`, greift also bei allem was `DisplayHud(false)` setzt, ohne dass `IsHudHidden()`, greift also bei allem was `DisplayHud(false)` setzt, ohne dass
etwas angebunden werden muss. etwas angebunden werden muss.
## Wie die Lampe synchron wird ## Wie die Lampe funktioniert
Licht ist in GTA nie synchronisiert — `DrawSpotLight` zeichnet nur lokal für den Es wird kein Licht selbst gezeichnet. GTA bringt seit v1493 (Cayo Perico) eine
aktuellen Frame. Der Lampenzustand wird deshalb als Statebag repliziert eigene Lampe an der Tauchausrüstung mit:
(`LocalPlayer.state:set('divelight', …, true)`), und jeder Client zeichnet die
Lampen aller Taucher in seiner Nähe selbst.
Die Richtung kommt aus **zwei bone-relativen Punkten** am Kopf-Bone, nicht aus ```lua
`GetEntityForwardVector`: beim Tauchen liegt der Ped waagerecht im Wasser, der SetEnableScubaGearLight(ped, toggle) -- 0xEE2476B9EE4A094F
Entity-Forward würde stur horizontal leuchten. Die Offsets von `GetPedBoneCoords` IsScubaGearLightEnabled(ped) -- 0x88274C11CF0D866D
sind laut Native-Doku *"relative to the bone's rotation"*, die Differenz zweier ```
solcher Punkte enthält also den Pitch.
Die sitzt an der richtigen Stelle, leuchtet in Blickrichtung und braucht weder
Prop noch Offsets noch einen Draw-Thread.
> **Voraussetzung:** Die Lampe hängt an der echten **Scuba-Kleidung** des Peds
> (Component 8). Trägt der Spieler die nicht, tut die Native nichts. Diese
> Resource fasst das Outfit bewusst **nicht** an — das gehört ins Kleidungs-System.
> Beim Einschalten ohne passendes Outfit gibt es eine Meldung statt stiller Wirkungslosigkeit.
> Mit `debug = true` sagt `/divelampcheck`, ob das aktuelle Outfit eine Lampe hat.
Der Flag ist ein **lokaler** Ped-Zustand und wird nicht von selbst repliziert.
Deshalb läuft der Zustand über einen Statebag
(`LocalPlayer.state:set('divelight', …, true)`), und jeder Client setzt den Flag
für die Taucher in seiner Nähe selbst — sofort per `AddStateBagChangeHandler`,
plus ein Abgleich alle `lampReconcileIntervalMs`, weil Peds beim Streaming,
Respawn und Model-Wechsel neu erzeugt werden und den Flag dabei verlieren.
## Entwicklung ## Entwicklung
+35 -86
View File
@@ -1,111 +1,60 @@
local config = require 'config.client' local config = require 'config.client'
-- Reiner Renderer: zeichnet die Lampen aller Taucher in der Nähe, gesteuert -- Tauchlampe über die spieleigene Scuba-Lampe.
-- ausschließlich über den replizierten Statebag. Kein gemeinsamer Zustand mit
-- client/main.lua - das hier läuft auch für Spieler, deren Ausrüstung wir gar
-- nicht kennen.
-- --
-- Licht ist in GTA nie synchronisiert: DrawSpotLight zeichnet nur lokal, für den -- GTA bringt seit v1493 (Cayo Perico) eine eigene Lampe an der Tauchausrüstung mit:
-- aktuellen Frame. "Gesynct" heißt deshalb, dass jeder Client das Licht jedes -- SetEnableScubaGearLight(ped, toggle) 0xEE2476B9EE4A094F
-- anderen Tauchers selbst zeichnet. -- IsScubaGearLightEnabled(ped) 0x88274C11CF0D866D
--
-- Die sitzt an der richtigen Stelle, leuchtet in Blickrichtung und braucht weder
-- Prop noch Offsets noch einen eigenen Draw-Thread.
--
-- WICHTIG: Die Lampe hängt an der echten Scuba-Kleidung des Peds (Component 8).
-- Trägt der Spieler die nicht, tut die Native nichts - dieses Script fasst das
-- Outfit bewusst nicht an.
--
-- Der Flag ist ein lokaler Ped-Zustand und wird nicht von selbst repliziert.
-- Deshalb wie vorher der Umweg über den Statebag: jeder Client setzt den Flag für
-- jeden Taucher in seiner Nähe selbst.
local STATE_KEY = 'divelight' local STATE_KEY = 'divelight'
--- Kandidaten des aktuellen Frames. Wird wiederverwendet statt jedes Frame neu --- @param ped number
--- angelegt, damit der GC bei Dauerbetrieb nichts zu tun bekommt. --- @param enabled boolean
local candidates = {} local function applyLight(ped, enabled)
if ped == 0 or not DoesEntityExist(ped) then return end
--- Position und Richtung der Lampe eines Peds. -- Nur setzen wenn es sich tatsächlich ändert. Die Native ist billig, aber der
--- -- Aufruf pro Ped und Durchlauf wäre trotzdem unnötig.
--- Die Offsets von GetPedBoneCoords sind laut Native-Doku "relative to the bone's if IsScubaGearLightEnabled(ped) == enabled then return end
--- rotation" - zwei bone-relative Punkte ergeben also die echte Blickrichtung
--- inklusive Pitch. Genau das wird beim Tauchen gebraucht: der Ped liegt waagerecht
--- im Wasser, GetEntityForwardVector würde die Lampe stur horizontal leuchten lassen.
local function getLampVectors(ped)
local offset = config.lampOffset
local direction = config.lampDirection
local from = GetPedBoneCoords(ped, config.lampBone, offset.x, offset.y, offset.z) SetEnableScubaGearLight(ped, enabled)
local ahead = GetPedBoneCoords(ped, config.lampBone,
offset.x + direction.x,
offset.y + direction.y,
offset.z + direction.z)
local delta = ahead - from
local length = #delta
if length <= 0 then return end
return from, delta / length
end end
local function drawLamp(ped) --- Reagiert sofort auf Änderungen, auch von entfernten Spielern.
local from, direction = getLampVectors(ped) AddStateBagChangeHandler(STATE_KEY, nil, function(bagName, _, value)
local player = GetPlayerFromStateBagName(bagName)
if not from then return end if player == 0 then return end
local colour = config.lampColour applyLight(GetPlayerPed(player), value == true)
end)
if config.lampShadows then
DrawSpotLightWithShadow(from.x, from.y, from.z, direction.x, direction.y, direction.z,
colour[1], colour[2], colour[3],
config.lampDistance, config.lampBrightness, config.lampRoundness,
config.lampRadius, config.lampFalloff, 0)
else
DrawSpotLight(from.x, from.y, from.z, direction.x, direction.y, direction.z,
colour[1], colour[2], colour[3],
config.lampDistance, config.lampBrightness, config.lampRoundness,
config.lampRadius, config.lampFalloff)
end
end
--- Der Statebag-Handler allein reicht nicht: Peds werden beim Streaming neu
--- erzeugt, beim Respawn ausgetauscht und beim Model-Wechsel ersetzt - der Flag
--- ist dann wieder weg, ohne dass sich der Statebag geändert hat. Deshalb
--- regelmäßig nachziehen.
CreateThread(function() CreateThread(function()
while true do while true do
local players = GetActivePlayers() local players = GetActivePlayers()
local myCoords = GetEntityCoords(cache.ped)
local count = 0
for i = 1, #players do for i = 1, #players do
local player = players[i] local player = players[i]
local state = Player(GetPlayerServerId(player)).state local state = Player(GetPlayerServerId(player)).state
if state and state[STATE_KEY] then applyLight(GetPlayerPed(player), state and state[STATE_KEY] == true)
local ped = GetPlayerPed(player)
if ped ~= 0 and DoesEntityExist(ped) then
local distance = #(myCoords - GetEntityCoords(ped))
if distance < config.lampDrawDistance then
count += 1
local entry = candidates[count]
if entry then
entry.ped, entry.distance = ped, distance
else
candidates[count] = { ped = ped, distance = distance }
end
end
end
end
end end
-- Reste des letzten Frames abschneiden, sonst sortiert table.sort veraltete Wait(config.lampReconcileIntervalMs)
-- Einträge mit und ein alter, näherer Eintrag verdrängt einen echten.
for i = #candidates, count + 1, -1 do
candidates[i] = nil
end
-- Nur sortieren wenn tatsächlich mehr Lampen da sind als gezeichnet werden
-- dürfen. DrawSpotLightWithShadow ist teuer, in einer vollen Höhle sonst
-- der erstbeste Frame-Killer.
if count > config.lampMaxLights then
table.sort(candidates, function(a, b) return a.distance < b.distance end)
end
for i = 1, math.min(count, config.lampMaxLights) do
drawLamp(candidates[i].ped)
end
Wait(count > 0 and 0 or 300)
end end
end) end)
+21 -13
View File
@@ -144,12 +144,22 @@ local function attachGear(ped)
end end
--- @param state boolean --- @param state boolean
local function setLamp(state) --- @param notifyIfUnavailable boolean? Rückmeldung, wenn das Outfit keine Lampe hat
local function setLamp(state, notifyIfUnavailable)
if not currentGear.enabled then state = false end if not currentGear.enabled then state = false end
lampOn = state lampOn = state
-- Repliziert: client/light.lua zeichnet daraus die Lampen aller Taucher. -- Repliziert: client/light.lua setzt daraus den Lampen-Flag bei allen Tauchern.
LocalPlayer.state:set('divelight', state, true) LocalPlayer.state:set('divelight', state, true)
-- Lokal sofort setzen statt auf den Abgleich-Thread zu warten.
SetEnableScubaGearLight(cache.ped, state)
-- Die Spiel-Lampe hängt an der Scuba-Kleidung (Component 8). Fehlt die, tut
-- die Native nichts - ohne Hinweis sieht das nach einem kaputten Keybind aus.
if state and notifyIfUnavailable and not IsScubaGearLightEnabled(cache.ped) then
exports.qbx_core:Notify(locale('error.no_lamp_on_outfit'), 'error')
end
end end
local function isPropIntact(prop) local function isPropIntact(prop)
@@ -532,7 +542,7 @@ lib.addKeybind({
onPressed = function() onPressed = function()
if not currentGear.enabled then return end if not currentGear.enabled then return end
setLamp(not lampOn) setLamp(not lampOn, true)
end end
}) })
@@ -575,17 +585,15 @@ if config.debug then
lib.print.info(('lampPropOffset = %s,\nlampPropRotation = %s,'):format(fmtVec(offset), fmtVec(rotation))) lib.print.info(('lampPropOffset = %s,\nlampPropRotation = %s,'):format(fmtVec(offset), fmtVec(rotation)))
end, false) end, false)
RegisterCommand('divelight', function(_, args) --- Die Spiel-Lampe braucht die Scuba-Kleidung am Ped. Dieser Befehl sagt, ob
local offset, direction = readVec(args, 1), readVec(args, 4) --- das aktuelle Outfit sie überhaupt hat - sonst rätselt man, warum nichts
--- leuchtet.
RegisterCommand('divelampcheck', function()
SetEnableScubaGearLight(cache.ped, true)
local works = IsScubaGearLightEnabled(cache.ped)
SetEnableScubaGearLight(cache.ped, lampOn)
if not offset or not direction then lib.print.info(('Scuba-Lampe am aktuellen Outfit: %s'):format(works and 'ja' or 'nein'))
lib.print.info('/divelight <x> <y> <z> <dx> <dy> <dz> - Ursprung und Richtung des Lichtkegels')
return
end
config.lampOffset, config.lampDirection = offset, direction
lib.print.info(('lampOffset = %s,\nlampDirection = %s,'):format(fmtVec(offset), fmtVec(direction)))
end, false) end, false)
end end
+22 -51
View File
@@ -66,58 +66,29 @@ return {
--- Tastenbelegungen unter "FiveM" umlegen. --- Tastenbelegungen unter "FiveM" umlegen.
lampKey = 'l', lampKey = 'l',
--- Bone, an dem Lampe und Licht hängen. Beides derselbe Bone, damit der --- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit
--- Strahl auch sichtbar aus der Lampe kommt. --- v1493). Sie sitzt an der richtigen Stelle und leuchtet in Blickrichtung -
--- 12844 = Kopf (wie die Maske) - Strahl folgt der Blickrichtung --- deshalb gibt es hier weder Offsets noch Farb- und Kegelwerte.
--- 28422 = Hand (wie das Prop in d4rk_phone) - Handlampe, aber beim ---
--- Schwimmen sind die Arme seitlich ausgestreckt, der Kegel --- ACHTUNG: Sie hängt an der echten Scuba-Kleidung des Peds (Component 8).
--- wandert also mit den Schwimmzügen --- Trägt der Spieler die nicht, passiert nichts. Diese Resource fasst das
--- Outfit bewusst nicht an - das macht bei Bedarf das Kleidungs-System.
--- Wie oft der Lampen-Zustand auf die Peds in der Nähe nachgezogen wird (ms).
--- Nötig, weil Peds beim Streaming, Respawn und Model-Wechsel neu erzeugt
--- werden und den Flag dabei verlieren.
lampReconcileIntervalMs = 500,
--- Optionales Prop am Kopf, rein optisch. Die Lampe leuchtet unabhängig davon.
--- Standardmäßig aus, weil die Spiel-Lampe schon an der Ausrüstung sitzt.
--- Ein Basegame-Tauchlampen-Prop gibt es nicht; die Polizei-Taschenlampe
--- ('prop_cs_police_torch', 'prop_police_torch', 'prop_scn_police_torch')
--- passt optisch nur bedingt.
lampProp = '',
--- Bone und Sitz des optionalen Props. 12844 = Kopf (wie die Maske).
--- Mit /divelamp im Spiel einstellen statt hier zu raten.
lampBone = 12844, lampBone = 12844,
--- Prop am Kopf. Rein optisch - das Licht hängt nicht daran, es wird nur
--- vom selben Bone aus gezeichnet. Ist das Model ungültig, leuchtet es
--- trotzdem und es gibt eine Warnung im Log.
--- Im Basegame gibt es keine Tauchlampe. Alternativen sind Varianten
--- derselben Polizei-Lampe: 'prop_police_torch', 'prop_scn_police_torch'.
--- Auf '' setzen = gar kein Prop, nur Licht.
lampProp = 'prop_cs_police_torch',
--- Sitz des Props am Bone. Wie in d4rk_phone bewusst auf null: das Prop sitzt
--- dann genau im Bone statt auf geratenen Offsets. Beim Kopf-Bone darf ein
--- kleiner Versatz nach vorne/seitlich nötig sein, damit die Lampe nicht im
--- Schädel steckt - mit /divelamp im Spiel einstellen statt hier zu raten.
lampPropOffset = vec3(0.0, 0.0, 0.0), lampPropOffset = vec3(0.0, 0.0, 0.0),
lampPropRotation = vec3(0.0, 0.0, 0.0), lampPropRotation = vec3(0.0, 0.0, 0.0),
--- Ursprung des Lichtkegels relativ zum Bone (bone-relativ, nicht Welt).
--- Sollte ungefähr dort liegen, wo das Prop sitzt.
lampOffset = vec3(0.0, 0.0, 0.0),
--- Richtung des Lichtkegels, ebenfalls bone-relativ. Leuchtet der Kegel in die
--- falsche Richtung, hier die Achse tauschen oder das Vorzeichen drehen -
--- welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt am Skelett.
lampDirection = vec3(0.0, 1.0, 0.0),
--- Lichtwerte aus d4rk_phone übernommen - dort im Betrieb erprobt, statt hier
--- neu geraten. Leicht warmweiß wie eine echte Taschenlampe.
lampColour = { 255, 250, 235 },
lampDistance = 28.0, -- Reichweite in Metern
lampBrightness = 3.0, -- Helligkeit
lampRoundness = 0.0, -- Weichzeichnung des Kegelrands
lampRadius = 16.0, -- Öffnungswinkel
lampFalloff = 2.0, -- Abfall zum Rand hin
--- Schatten kosten deutlich Performance. d4rk_phone nutzt bewusst das
--- schattenlose DrawSpotLight, deshalb hier auch der Default - unter Wasser
--- fällt der Unterschied kaum auf, in einer Höhle mit mehreren Tauchern
--- dagegen sehr wohl.
lampShadows = false,
--- Ab dieser Entfernung werden fremde Lampen nicht mehr gezeichnet (Meter).
lampDrawDistance = 45.0,
--- Wie viele Lampen gleichzeitig gezeichnet werden. Die nächstgelegenen
--- gewinnen. Schützt vor Frame-Einbrüchen bei Gruppentauchgängen.
lampMaxLights = 4,
} }
+2 -1
View File
@@ -6,7 +6,8 @@
"no_tank": "Du hast keine Tauchflasche zum Auffüllen dabei!", "no_tank": "Du hast keine Tauchflasche zum Auffüllen dabei!",
"tank_empty": "Diese Tauchflasche ist leer!", "tank_empty": "Diese Tauchflasche ist leer!",
"tank_already_full": "Diese Tauchflasche ist bereits voll!", "tank_already_full": "Diese Tauchflasche ist bereits voll!",
"out_of_air": "Deine Luft ist alle! Sofort auftauchen!" "out_of_air": "Deine Luft ist alle! Sofort auftauchen!",
"no_lamp_on_outfit": "Deine Tauchausrüstung hat keine Lampe."
}, },
"success": { "success": {
"took_out": "Du hast deine Ausrüstung abgelegt!", "took_out": "Du hast deine Ausrüstung abgelegt!",
+2 -1
View File
@@ -6,7 +6,8 @@
"no_tank": "You don't have an air tank to refill!", "no_tank": "You don't have an air tank to refill!",
"tank_empty": "This air tank is empty!", "tank_empty": "This air tank is empty!",
"tank_already_full": "This air tank is already full!", "tank_already_full": "This air tank is already full!",
"out_of_air": "You're out of air! Surface now!" "out_of_air": "You're out of air! Surface now!",
"no_lamp_on_outfit": "Your diving suit doesn't have a lamp."
}, },
"success": { "success": {
"took_out": "You've taken your gear off!", "took_out": "You've taken your gear off!",