extends Node class_name Ambience ## The provocations you can't interact with, which is exactly why they work. ## ## The task system provokes you when you make a mistake, so a good player can hold the ## meter down. Ambience can't be played around: the tube above your desk flickers, a ## phone rings four desks away and nobody picks it up, someone puts fish in the ## microwave. Each is a small periodic rage tick and a line of text, and together they're ## what makes the room feel hostile rather than merely beige. ## ## Each event announces itself in the HUD, because half the irritation of an office is ## knowing exactly what is doing it to you and being unable to stop it. ## ## Godot 4.7 GDScript 2.0. ## {gap: seconds between occurrences, rage: meter added, line: what the HUD says} const EVENTS := [ {"gap": 17.0, "rage": 0.030, "line": "the tube above your desk flickers again"}, {"gap": 23.0, "rage": 0.035, "line": "somebody's phone has been ringing for a while"}, {"gap": 41.0, "rage": 0.055, "line": "someone has put fish in the microwave"}, {"gap": 29.0, "rage": 0.028, "line": "the air-con drips onto the carpet · tok · tok"}, {"gap": 34.0, "rage": 0.040, "line": "\"just following up on my last email\""}, {"gap": 47.0, "rage": 0.045, "line": "reply-all. forty-one recipients. \"thanks!\""}, {"gap": 26.0, "rage": 0.032, "line": "the printer says it is offline. it is not offline."}, {"gap": 53.0, "rage": 0.050, "line": "your chair has started doing the thing again"}, {"gap": 38.0, "rage": 0.038, "line": "somewhere, a laminator"}, ] var rage: Rage var hud: Hud var running := true var _next: Array[float] = [] var _flicker: Array[OmniLight3D] = [] var _flicker_t := 0.0 var _flicker_until := 0.0 func setup(r: Rage, h: Hud) -> void: rage = r hud = h _arm() func _arm() -> void: _next.clear() for e in EVENTS: # stagger the first occurrence so they don't all land together on the first minute _next.append(randf_range(0.35, 1.0) * float(e["gap"])) ## Called by Main after a level build, so the flicker picks lights in the NEW room. func adopt_lights(from: Node) -> void: _flicker.clear() var all: Array = [] _collect(from, all) all.shuffle() for i in mini(3, all.size()): _flicker.append(all[i]) func _collect(n: Node, acc: Array) -> void: if n is OmniLight3D: acc.append(n) for c in n.get_children(): _collect(c, acc) func _process(dt: float) -> void: if rage == null or not running: return # during a meltdown the office has already lost its power over you if rage.state != Rage.State.WORKING: return for i in _next.size(): _next[i] -= dt if _next[i] > 0.0: continue var e: Dictionary = EVENTS[i] _next[i] = float(e["gap"]) * randf_range(0.75, 1.35) rage.provoke(float(e["rage"]), String(e["line"])) if hud != null: hud.toast(String(e["line"])) if i == 0: _flicker_until = 2.4 # the tube event actually flickers the tube _tick_flicker(dt) ## A failing fluorescent doesn't blink evenly — it stutters, catches, and stutters again. func _tick_flicker(dt: float) -> void: if _flicker_until <= 0.0: return _flicker_until -= dt _flicker_t += dt var on := true if _flicker_until > 0.0: var p := fposmod(_flicker_t * 13.0, 1.0) on = p > 0.42 or fposmod(_flicker_t, 0.7) < 0.25 for l in _flicker: if is_instance_valid(l): l.light_energy = (2.0 if on else 0.25) if _flicker_until > 0.0 else 2.0