destroyulator/game/scripts/Modes.gd
Monster Robot Party e5f4e8c5e4 LANE7: the Gauntlet, four modes, two more provocations, and ambient attrition
THE GAUNTLET (Gauntlet.gd + Cabinet.gd)
Five identical cabinets, each with its alphabetical range on a Label3D. The brief names
a CLIENT, never a cabinet, so working out that HENDERSON lives in F-J is the puzzle —
no waypoint, nothing glows. Destruction is granular on purpose: pull a drawer, it
slides out on a spring and goes live, and the folders inside become individual
smashables. Doing that by hand inside the clock isn't really possible.

The perfect tool: every site hides a SHREDDER that eats a whole open drawer at once,
and the game never says so. Noticing it and working out what it's for IS the level.
Getting it wrong isn't fatal, just slow — the right shape for a secret, since it
rewards the observant without hard-failing everyone else.

Each site nominates its own records area in its spec. Building the bank 3.4 m in front
of wherever the player happened to be standing put it through walls.

MODES (Modes.gd, M cycles)
  CUBICLE HELL       the main loop
  THE GAUNTLET       above
  TOTAL DESTRUCTION  no job, no pay, clear the site, scored on time
  AGAINST THE CLOCK  fixed timer, maximise damage VALUE — the exact inverse of Cubicle
                     Hell, where expensive things become the goal rather than the
                     mistake. Same level, opposite instinct.
Entering a mode arms only what that mode needs; Cubicle Hell and the Gauntlet are
mutually exclusive, because you cannot be doing your job and shredding the Henderson
file at the same time.

TWO MORE PROVOCATIONS (Tasks.gd)
  STAPLES     SPACE lifts a staple, then it must be pulled the way it's bent — and it's
              bent differently each time. Dither and the sheet tears, bundle reprints.
  GUILLOTINE  align to a mark and cut. The guide has a PERMANENT offset and nothing on
              screen admits it. Once you work out the error it's easy, which is the
              joke: you have to learn to distrust the equipment.
Desks now alternate spreadsheet/staples so "a desk" isn't always the same minigame.

AMBIENCE (Ambience.gd) — the attrition you can't play around. The tube above your desk
flickers, and actually flickers the real lights. Fish in the microwave. A phone ringing
four desks away. Reply-all, forty-one recipients, "thanks!". Small periodic ticks with
no interaction at all; it's what makes the room hostile rather than merely beige.

Fixes: the payslip was rendering over the Gauntlet (it belongs to Cubicle Hell, and only
once the shift has actually ended); gauntlet drawer reach was 2.0 m when the true
distance from standing-at-a-cabinet to a bottom drawer is over 2 m.

dev/probe_gauntlet.gd drives a whole run headlessly — mode switch, labels, open every
drawer, shred, win — because that catches the failure that matters: a run you can't
finish because the files never become destroyable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:12:20 +10:00

125 lines
3.4 KiB
GDScript

extends Node
class_name Modes
## Which game you're playing. M cycles.
##
## CUBICLE HELL ...... the main one. Work a shift, get provoked, snap, wreck things,
## go back to your desk. Scored as wages minus damages.
## THE GAUNTLET ...... find which cabinet holds one client's file and destroy all of
## it, against a clock, with a hidden perfect tool.
## TOTAL DESTRUCTION . no job, no pay. Wreck the whole site. Scored on time.
## AGAINST THE CLOCK . fixed timer, maximise damage VALUE. The exact inverse of
## Cubicle Hell, where the expensive things are the goal rather
## than the mistake — same level, opposite instinct.
##
## Modes own only their rules. The level, the rage meter, the tasks and the destruction
## all exist regardless; a mode decides which of them are switched on and how the number
## at the top of the screen is computed.
##
## Godot 4.7 GDScript 2.0.
enum M { CUBICLE_HELL, GAUNTLET, TOTAL, CLOCK }
const NAMES := ["CUBICLE HELL", "THE GAUNTLET", "TOTAL DESTRUCTION", "AGAINST THE CLOCK"]
const ORDER := [M.CUBICLE_HELL, M.GAUNTLET, M.TOTAL, M.CLOCK]
@export var clock_time: float = 120.0
var mode: int = M.CUBICLE_HELL
var running := false
var time_left := 0.0
var result := ""
# TOTAL DESTRUCTION
var _to_break := 0
var _broken := 0
var _elapsed := 0.0
# AGAINST THE CLOCK
var damage_value := 0
signal changed(mode: int)
func cycle() -> void:
mode = ORDER[(ORDER.find(mode) + 1) % ORDER.size()]
changed.emit(mode)
func set_mode(m: int) -> void:
mode = m
changed.emit(mode)
func name_of() -> String:
return NAMES[mode]
## Called by Main after a level is populated, with how many smashables exist.
func begin(total_bodies: int) -> void:
running = true
result = ""
_elapsed = 0.0
_broken = 0
damage_value = 0
_to_break = total_bodies
time_left = clock_time
func on_smashed(value: int) -> void:
if not running:
return
_broken += 1
damage_value += value
if mode == M.TOTAL and _broken >= _to_break:
running = false
result = "SITE CLEARED in %s" % _mmss(_elapsed)
func _process(dt: float) -> void:
if not running:
return
match mode:
M.TOTAL:
_elapsed += dt
M.CLOCK:
time_left -= dt
if time_left <= 0.0:
time_left = 0.0
running = false
result = "$%s of damage" % _comma(damage_value)
func progress() -> float:
if mode != M.TOTAL or _to_break <= 0:
return 0.0
return clampf(float(_broken) / float(_to_break), 0.0, 1.0)
func hud_line() -> String:
match mode:
M.TOTAL:
if not running:
return "TOTAL DESTRUCTION — %s (R again · M mode)" % result
return "TOTAL DESTRUCTION %s · %d / %d (%d%%)" % [
_mmss(_elapsed), _broken, _to_break, int(progress() * 100.0)]
M.CLOCK:
if not running:
return "AGAINST THE CLOCK — %s (R again · M mode)" % result
return "AGAINST THE CLOCK %02d:%02d · $%s · the expensive things" % [
int(time_left) / 60, int(time_left) % 60, _comma(damage_value)]
return ""
func headline() -> String:
match mode:
M.TOTAL:
return "%d%%" % int(progress() * 100.0)
M.CLOCK:
return "$%s" % _comma(damage_value)
return ""
func _mmss(t: float) -> String:
return "%d:%02d" % [int(t) / 60, int(t) % 60]
func _comma(n: int) -> String:
var s := str(absi(n))
var out := ""
var c := 0
for i in range(s.length() - 1, -1, -1):
out = s[i] + out
c += 1
if c % 3 == 0 and i > 0:
out = "," + out
return ("-" if n < 0 else "") + out