The hip-hop room has stock (7 racks, 47 bins, 2,865 records); the entry hall and back room are empty. Import all four anyway: the empty rooms are the connective tissue, and they are the only floor in the building big enough to get an office chair up to speed. A portal pair is one doorway seen from both sides, so the importer walks the graph and places each room by making its portal land exactly on its neighbour's. Every pair here sits on parallel walls with opposite facings, so each join is a pure translation — and when one isn't, it says so rather than quietly getting it wrong. It then recentres the union, emits the interior walls with a gap at each doorway, and puts the spawn in the entry hall, because that is the way in. hiphop-room <-> entry-hall <-> shop-floor <-> back-room 12.96 x 15.41 m - 36 racks - 325 bins - 302 real genre labels - 24,024 records Levels.record_store() now takes the walls and rooms from the import, hangs the street glazing on the entry hall's outer wall, and lays a light grid across whatever footprint the rooms came out as. probe_store.gd also checks the new facts: five doorways exist, and the spawn has nothing in it (excluding the player, who is supposed to be standing there — that false positive was the probe's bug, not the level's). All gates green: smoke clean, 7/7 sites 0.00 m/s, overlap CLEAN, all probes pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
831 lines
39 KiB
GDScript
831 lines
39 KiB
GDScript
extends RefCounted
|
|
class_name Levels
|
|
|
|
## The workplaces, as data. Each is a Floorplan spec (see Floorplan.gd for the schema).
|
|
##
|
|
## The point of these being data and not four subclasses: a new site is authorable in
|
|
## minutes, they can be diffed, and a generator can later emit the same structure.
|
|
##
|
|
## They are deliberately NOT reskins. Each differs in the three things a player actually
|
|
## reads — the palette, the light, and what the walls are made of:
|
|
##
|
|
## SCRANTON ..... magnolia, grey carpet, drop ceiling, fluorescent troffers, daylight
|
|
## down one glazed wall. The baseline office.
|
|
## PAWNEE ....... civic beige and blue-grey, low partitions everywhere, a public
|
|
## counter, pinboards. Municipal, over-partitioned, no privacy.
|
|
## THE HOUSE .... timber floor, white walls, 3 m ceiling, pendant lights, a glass wall
|
|
## onto a pool. Nobody here has an office; they work at a dining table.
|
|
## THE BASEMENT . concrete, no windows at all, exposed services, bare strip lights, a
|
|
## wall of server racks. The light is green and everything is junk.
|
|
##
|
|
## Godot 4.7 GDScript 2.0.
|
|
|
|
const ORDER := ["scranton", "pawnee", "house", "basement", "grocer", "backrooms", "store"]
|
|
|
|
static func get_level(id: String) -> Dictionary:
|
|
match id:
|
|
"pawnee": return pawnee()
|
|
"house": return house()
|
|
"basement": return basement()
|
|
"backrooms": return backrooms()
|
|
"grocer": return grocer()
|
|
"store": return record_store()
|
|
return scranton()
|
|
|
|
static func next_id(id: String) -> String:
|
|
var i := ORDER.find(id)
|
|
return ORDER[(i + 1) % ORDER.size()] if i >= 0 else ORDER[0]
|
|
|
|
# ================================================================ LEVEL 01
|
|
static func scranton() -> Dictionary:
|
|
return {
|
|
"name": "SCRANTON BRANCH",
|
|
"subtitle": "paper company · 2nd floor",
|
|
"bounds": {"x0": -11.0, "x1": 11.0, "z0": -8.0, "z1": 8.0, "h": 2.75},
|
|
"palette": {
|
|
"floor": Color(0.30, 0.30, 0.32), "wall": Color(0.80, 0.78, 0.72),
|
|
"ceiling": Color(0.88, 0.88, 0.86), "trim": Color(0.13, 0.12, 0.13),
|
|
"partition": Color(0.44, 0.44, 0.40), "wood": Color(0.42, 0.29, 0.17),
|
|
"accent": Color(0.36, 0.42, 0.50),
|
|
},
|
|
"floor_style": "carpet",
|
|
"ceiling": "tile",
|
|
"walls": [
|
|
# manager's office — half solid, half glass, so you can be watched through it
|
|
{"a": "x", "at": -4.4, "from": -11.0, "to": -6.6, "door": -9.2},
|
|
{"a": "z", "at": -6.6, "from": -8.0, "to": -4.4, "style": "glass_top"},
|
|
# conference room
|
|
{"a": "x", "at": -4.4, "from": -6.6, "to": -0.4, "door": -3.4},
|
|
{"a": "z", "at": -0.4, "from": -8.0, "to": -4.4},
|
|
# break room
|
|
{"a": "z", "at": 5.4, "from": -1.2, "to": 8.0, "door": 2.2},
|
|
{"a": "x", "at": -1.2, "from": 5.4, "to": 11.0},
|
|
# copier alcove
|
|
{"a": "z", "at": 3.6, "from": -1.6, "to": 1.0},
|
|
# reception screen
|
|
{"a": "z", "at": -6.9, "from": 4.6, "to": 8.0, "door": 6.8},
|
|
],
|
|
"windows": [{"a": "z", "at": 11.0, "from": -8.0, "to": 8.0,
|
|
"y0": 0.85, "y1": 2.35, "blinds": true}],
|
|
"lights": {"xs": [-8.5, -4.6, -0.7, 3.2, 7.1], "zs": [-6.2, -2.6, 1.0, 4.6],
|
|
"color": Color(1.0, 0.98, 0.93), "energy": 1.9, "style": "troffer"},
|
|
"sun": {"color": Color(0.82, 0.88, 1.0), "energy": 0.7,
|
|
"rot": Vector3(-30, -104, 0)},
|
|
"env": {"bg": Color(0.55, 0.62, 0.72), "ambient": Color(0.50, 0.50, 0.50),
|
|
"ambient_energy": 0.5},
|
|
"clusters": [Vector2(-4.6, -2.0), Vector2(-4.6, 2.8),
|
|
Vector2(-0.4, -2.0), Vector2(-0.4, 2.8)],
|
|
"cluster_gap": 0.78,
|
|
"slabs": [
|
|
# reception counter
|
|
{"size": Vector3(0.70, 1.10, 3.0), "at": Vector3(-9.9, 0.55, 5.6), "mat": "wood"},
|
|
{"size": Vector3(0.92, 0.08, 3.2), "at": Vector3(-9.9, 1.14, 5.6), "mat": "trim"},
|
|
{"size": Vector3(1.8, 1.10, 0.70), "at": Vector3(-9.6, 0.55, 4.45), "mat": "wood"},
|
|
# conference table + whiteboard
|
|
{"size": Vector3(3.9, 0.09, 1.35), "at": Vector3(-3.5, 0.74, -6.2), "mat": "wood"},
|
|
{"size": Vector3(0.16, 0.72, 0.90), "at": Vector3(-5.1, 0.36, -6.2), "mat": "trim"},
|
|
{"size": Vector3(0.16, 0.72, 0.90), "at": Vector3(-1.9, 0.36, -6.2), "mat": "trim"},
|
|
{"size": Vector3(2.5, 1.25, 0.03), "at": Vector3(-3.5, 1.6, -7.93), "mat": "trim"},
|
|
{"size": Vector3(2.4, 1.15, 0.05), "at": Vector3(-3.5, 1.6, -7.90), "mat": "ceiling"},
|
|
# break room kitchen + tables
|
|
{"size": Vector3(0.62, 0.88, 3.4), "at": Vector3(10.58, 0.44, 4.0), "mat": "wood"},
|
|
{"size": Vector3(0.70, 0.06, 3.5), "at": Vector3(10.58, 0.90, 4.0), "mat": "trim"},
|
|
{"size": Vector3(1.05, 0.07, 1.05), "at": Vector3(7.4, 0.73, 0.4), "mat": "ceiling"},
|
|
{"size": Vector3(0.12, 0.70, 0.12), "at": Vector3(7.4, 0.36, 0.4), "mat": "trim"},
|
|
{"size": Vector3(1.05, 0.07, 1.05), "at": Vector3(7.4, 0.73, 3.0), "mat": "ceiling"},
|
|
{"size": Vector3(0.12, 0.70, 0.12), "at": Vector3(7.4, 0.36, 3.0), "mat": "trim"},
|
|
# warehouse roller door
|
|
{"size": Vector3(2.6, 2.4, 0.12), "at": Vector3(7.8, 1.2, -7.94), "mat": "trim"},
|
|
],
|
|
"props": [
|
|
{"glb": "store-column-white", "at": Vector3(-2.2, 0.0, -3.4)},
|
|
{"glb": "store-column-white", "at": Vector3(2.2, 0.0, -3.4)},
|
|
{"glb": "store-aircon", "at": Vector3(2.6, 2.35, -7.9)},
|
|
{"glb": "office-plant", "at": Vector3(-6.4, 0.0, 6.6)},
|
|
{"glb": "office-plant", "at": Vector3(2.9, 0.0, 6.4)},
|
|
{"glb": "vending-machine", "at": Vector3(6.3, 0.0, 7.4), "yaw": PI},
|
|
{"glb": "vending-machine", "at": Vector3(7.3, 0.0, 7.4), "yaw": PI},
|
|
{"glb": "microwave", "at": Vector3(10.55, 0.93, 5.1), "yaw": -PI / 2},
|
|
],
|
|
"chairs": [
|
|
{"at": Vector3(-7.6, 0.0, 5.5), "yaw": -PI / 2},
|
|
{"at": Vector3(-7.6, 0.0, 6.35), "yaw": -PI / 2},
|
|
{"at": Vector3(-7.6, 0.0, 7.2), "yaw": -PI / 2},
|
|
{"at": Vector3(-4.85, 0.0, -7.35), "yaw": 0.0},
|
|
{"at": Vector3(-3.95, 0.0, -7.35), "yaw": 0.0},
|
|
{"at": Vector3(-3.05, 0.0, -7.35), "yaw": 0.0},
|
|
{"at": Vector3(-2.15, 0.0, -7.35), "yaw": 0.0},
|
|
{"at": Vector3(-4.85, 0.0, -5.05), "yaw": PI},
|
|
{"at": Vector3(-3.95, 0.0, -5.05), "yaw": PI},
|
|
{"at": Vector3(-3.05, 0.0, -5.05), "yaw": PI},
|
|
{"at": Vector3(-2.15, 0.0, -5.05), "yaw": PI},
|
|
{"at": Vector3(-8.8, 0.0, -7.1), "yaw": 0.0},
|
|
{"at": Vector3(-9.6, 0.0, -5.1), "yaw": PI},
|
|
{"at": Vector3(8.0, 0.0, 1.13), "yaw": 2.0},
|
|
{"at": Vector3(7.7, 0.0, -0.49), "yaw": 4.1},
|
|
{"at": Vector3(6.5, 0.0, 0.56), "yaw": 0.2},
|
|
{"at": Vector3(8.0, 0.0, 3.73), "yaw": 2.0},
|
|
{"at": Vector3(7.7, 0.0, 2.11), "yaw": 4.1},
|
|
{"at": Vector3(6.5, 0.0, 3.16), "yaw": 0.2},
|
|
],
|
|
"copier": {"at": Vector3(3.0, 0.0, -0.3), "yaw": -PI / 2},
|
|
"smashables": [
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(4.5, -1.6)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(4.5, -0.9)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(4.5, -0.2)},
|
|
{"glb": "water-cooler", "kind": "steel", "at": Vector2(-6.2, 3.4)},
|
|
{"glb": "water-cooler", "kind": "steel", "at": Vector2(5.9, -0.6)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.3, 5.2)},
|
|
# an archive box is 1.35 m across, so a "couple of boxes dumped together" is a
|
|
# PILE, not two of them side by side inside each other
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(1.6, 5.4)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(1.74, 5.26),
|
|
"sit_on": 0.40, "yaw": 0.10},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(6.4, -3.3)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(6.52, -3.44),
|
|
"sit_on": 0.40, "yaw": -0.08},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-9.7, 1.2)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(-6.0, -1.0)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(1.0, 1.0)},
|
|
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(-9.8, 5.0), "sit_on": 1.18},
|
|
{"glb": "rack", "kind": "wood", "at": Vector2(9.4, -6.6), "frozen": true},
|
|
{"glb": "rack", "kind": "wood", "at": Vector2(9.4, -4.3), "frozen": true},
|
|
{"glb": "turntable", "kind": "wood", "at": Vector2(6.9, -5.9)},
|
|
{"glb": "guillotine", "kind": "steel", "at": Vector2(4.5, 1.2)},
|
|
{"glb": "stapler", "kind": "steel", "at": Vector2(-5.25, -2.78), "sit_on": 0.75},
|
|
],
|
|
"records": [Vector2(6.2, -6.6), Vector2(7.0, -6.9), Vector2(7.8, -6.6)],
|
|
"gauntlet": {"at": Vector3(0.0, 0.0, -3.9), "yaw": PI,
|
|
"stand": Vector3(0.0, 0.0, -1.7), "shredder": Vector3(2.7, 0.0, -2.4)},
|
|
"spawn": {"at": Vector3(-8.6, 0.0, 7.2), "yaw": deg_to_rad(196.0)},
|
|
}
|
|
|
|
# ================================================================ LEVEL 02
|
|
static func pawnee() -> Dictionary:
|
|
return {
|
|
"name": "PAWNEE CITY HALL",
|
|
"subtitle": "parks & recreation · 4th floor",
|
|
"bounds": {"x0": -10.0, "x1": 10.0, "z0": -7.0, "z1": 7.5, "h": 2.70},
|
|
"palette": {
|
|
"floor": Color(0.34, 0.36, 0.40), "wall": Color(0.84, 0.80, 0.68),
|
|
"ceiling": Color(0.90, 0.89, 0.85), "trim": Color(0.30, 0.22, 0.14),
|
|
"partition": Color(0.52, 0.50, 0.42), "wood": Color(0.50, 0.34, 0.19),
|
|
"accent": Color(0.16, 0.36, 0.28),
|
|
},
|
|
"floor_style": "carpet",
|
|
"ceiling": "tile",
|
|
"walls": [
|
|
# the department head's office, and the meeting room beside it
|
|
{"a": "x", "at": -3.4, "from": -10.0, "to": -5.6, "door": -7.0},
|
|
{"a": "z", "at": -5.6, "from": -7.0, "to": -3.4, "style": "glass_top"},
|
|
{"a": "x", "at": -3.4, "from": -5.6, "to": 0.4, "door": -1.6},
|
|
{"a": "z", "at": 0.4, "from": -7.0, "to": -3.4},
|
|
# records room, and the corridor wall that makes this place a warren
|
|
{"a": "z", "at": 4.6, "from": -7.0, "to": -2.0, "door": -4.4},
|
|
{"a": "x", "at": -2.0, "from": 4.6, "to": 10.0},
|
|
# the public counter divides the front third from everything else
|
|
{"a": "x", "at": 3.6, "from": -10.0, "to": 3.0, "door": -8.0, "style": "half"},
|
|
],
|
|
"windows": [{"a": "x", "at": 7.5, "from": -10.0, "to": 10.0,
|
|
"y0": 0.95, "y1": 2.30, "blinds": true}],
|
|
"lights": {"xs": [-7.5, -3.4, 0.7, 4.8, 8.4], "zs": [-5.2, -1.6, 2.0, 5.6],
|
|
"color": Color(1.0, 0.96, 0.86), "energy": 1.8, "style": "troffer",
|
|
"key_energy": 1.4},
|
|
"sun": {"color": Color(0.90, 0.88, 0.78), "energy": 0.55,
|
|
"rot": Vector3(-34, 20, 0)},
|
|
"env": {"bg": Color(0.62, 0.66, 0.62), "ambient": Color(0.50, 0.48, 0.44),
|
|
"ambient_energy": 0.52, "exposure": 1.02},
|
|
"clusters": [Vector2(-6.0, 0.4), Vector2(-1.4, 0.4), Vector2(2.6, 0.4)],
|
|
"cluster_gap": 0.78,
|
|
"slabs": [
|
|
# the public counter itself — the thing between you and the citizens
|
|
{"size": Vector3(6.6, 1.06, 0.66), "at": Vector3(-1.2, 0.53, 5.4), "mat": "wood"},
|
|
{"size": Vector3(6.9, 0.07, 0.86), "at": Vector3(-1.2, 1.10, 5.4), "mat": "trim"},
|
|
{"size": Vector3(0.66, 1.06, 2.2), "at": Vector3(2.1, 0.53, 6.4), "mat": "wood"},
|
|
# meeting room table
|
|
{"size": Vector3(2.9, 0.09, 1.25), "at": Vector3(-2.6, 0.74, -5.2), "mat": "wood"},
|
|
{"size": Vector3(0.14, 0.72, 0.84), "at": Vector3(-3.7, 0.36, -5.2), "mat": "trim"},
|
|
{"size": Vector3(0.14, 0.72, 0.84), "at": Vector3(-1.5, 0.36, -5.2), "mat": "trim"},
|
|
# pinboards — municipal offices are 40% pinboard by surface area
|
|
{"size": Vector3(2.2, 1.20, 0.05), "at": Vector3(5.0, 1.65, -6.93), "mat": "accent"},
|
|
{"size": Vector3(2.3, 1.30, 0.03), "at": Vector3(5.0, 1.65, -6.90), "mat": "trim"},
|
|
{"size": Vector3(0.05, 1.20, 2.6), "at": Vector3(-9.93, 1.62, 2.0), "mat": "accent"},
|
|
{"size": Vector3(0.03, 1.30, 2.7), "at": Vector3(-9.90, 1.62, 2.0), "mat": "trim"},
|
|
# department head's desk
|
|
{"size": Vector3(1.7, 0.08, 0.85), "at": Vector3(-7.4, 0.74, -5.4), "mat": "wood"},
|
|
{"size": Vector3(1.5, 0.66, 0.72), "at": Vector3(-7.4, 0.35, -5.4), "mat": "wood"},
|
|
],
|
|
"props": [
|
|
{"glb": "office-plant", "at": Vector3(-9.3, 0.0, 6.6)},
|
|
{"glb": "office-plant", "at": Vector3(3.2, 0.0, -2.6)},
|
|
{"glb": "office-plant", "at": Vector3(-9.3, 0.0, -1.0)},
|
|
{"glb": "sofa", "at": Vector3(-6.6, 0.0, 6.5), "yaw": PI},
|
|
{"glb": "vending-machine", "at": Vector3(9.2, 0.0, 6.6), "yaw": -PI / 2},
|
|
{"glb": "microwave", "at": Vector3(9.3, 0.90, 1.2), "yaw": -PI / 2},
|
|
{"glb": "store-aircon", "at": Vector3(-6.0, 2.30, -6.9)},
|
|
],
|
|
"chairs": [
|
|
{"at": Vector3(-7.4, 0.0, -4.2), "yaw": PI},
|
|
{"at": Vector3(-8.6, 0.0, -6.3), "yaw": 0.0},
|
|
{"at": Vector3(-3.5, 0.0, -6.3), "yaw": 0.0},
|
|
{"at": Vector3(-2.6, 0.0, -6.3), "yaw": 0.0},
|
|
{"at": Vector3(-1.7, 0.0, -6.3), "yaw": 0.0},
|
|
{"at": Vector3(-3.5, 0.0, -4.1), "yaw": PI},
|
|
{"at": Vector3(-2.6, 0.0, -4.1), "yaw": PI},
|
|
{"at": Vector3(-1.7, 0.0, -4.1), "yaw": PI},
|
|
{"at": Vector3(-4.0, 0.0, 6.4), "yaw": PI},
|
|
{"at": Vector3(0.4, 0.0, 6.5), "yaw": PI},
|
|
{"at": Vector3(1.2, 0.0, 6.5), "yaw": PI},
|
|
],
|
|
"copier": {"at": Vector3(6.4, 0.0, 2.4), "yaw": PI},
|
|
"smashables": [
|
|
# a cabinet is 0.80 m wide; a row of them 0.70 m apart is one cabinet
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(5.4, -3.2)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(6.26, -3.2)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(7.12, -3.2)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(7.98, -3.2)},
|
|
{"glb": "water-cooler", "kind": "steel", "at": Vector2(3.9, 4.2)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.6, -5.4)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.74, -5.26),
|
|
"sit_on": 0.40, "yaw": 0.12},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-4.8, 3.0)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(-8.8, -3.9)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(4.4, 0.9)},
|
|
{"glb": "guillotine", "kind": "steel", "at": Vector2(6.4, 3.6), "sit_on": 0.0},
|
|
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(-6.6, -5.4), "sit_on": 0.78},
|
|
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-7.6, -5.2), "sit_on": 0.78},
|
|
],
|
|
"records": [],
|
|
"gauntlet": {"at": Vector3(7.2, 0.0, -5.8), "yaw": PI,
|
|
"stand": Vector3(7.2, 0.0, -3.9), "shredder": Vector3(5.2, 0.0, -6.2)},
|
|
"spawn": {"at": Vector3(-8.0, 0.0, 6.6), "yaw": deg_to_rad(178.0)},
|
|
}
|
|
|
|
# ================================================================ LEVEL 03
|
|
static func house() -> Dictionary:
|
|
return {
|
|
"name": "THE INCUBATOR",
|
|
"subtitle": "seed round · someone's house",
|
|
"bounds": {"x0": -10.0, "x1": 10.0, "z0": -8.0, "z1": 8.0, "h": 3.05},
|
|
"palette": {
|
|
"floor": Color(0.46, 0.33, 0.20), "wall": Color(0.92, 0.91, 0.88),
|
|
"ceiling": Color(0.95, 0.95, 0.94), "trim": Color(0.18, 0.17, 0.17),
|
|
"partition": Color(0.72, 0.72, 0.70), "wood": Color(0.40, 0.27, 0.16),
|
|
"accent": Color(0.22, 0.44, 0.62),
|
|
},
|
|
"floor_style": "timber",
|
|
"ceiling": "flat",
|
|
"walls": [
|
|
# only two real walls: a bedroom-turned-office and the bathroom nobody cleans
|
|
{"a": "z", "at": -4.6, "from": -8.0, "to": -2.4, "door": -4.0},
|
|
{"a": "x", "at": -2.4, "from": -10.0, "to": -4.6, "door": -7.4},
|
|
{"a": "z", "at": 4.0, "from": -8.0, "to": -5.2, "door": -6.6},
|
|
# the kitchen island's back wall
|
|
{"a": "x", "at": -1.0, "from": 4.0, "to": 10.0, "style": "half"},
|
|
],
|
|
"windows": [{"a": "x", "at": 8.0, "from": -10.0, "to": 10.0,
|
|
"y0": 0.35, "y1": 2.70, "blinds": false}],
|
|
"lights": {"xs": [-6.5, -1.5, 3.5, 7.5], "zs": [-5.0, 0.0, 5.0],
|
|
"color": Color(1.0, 0.90, 0.76), "energy": 2.4, "style": "pendant",
|
|
"key_energy": 1.7},
|
|
"sun": {"color": Color(1.0, 0.94, 0.80), "energy": 1.5,
|
|
"rot": Vector3(-46, -96, 0)},
|
|
"env": {"bg": Color(0.70, 0.80, 0.90), "ambient": Color(0.56, 0.55, 0.52),
|
|
"ambient_energy": 0.62, "exposure": 1.05},
|
|
# nobody has a desk; they work at the big table
|
|
"clusters": [Vector2(1.6, -1.4), Vector2(1.6, 2.6)],
|
|
"cluster_gap": 0.80,
|
|
"slabs": [
|
|
# kitchen island + counter
|
|
{"size": Vector3(2.6, 0.92, 1.05), "at": Vector3(4.2, 0.46, 5.6), "mat": "wood"},
|
|
{"size": Vector3(2.8, 0.08, 1.20), "at": Vector3(4.2, 0.95, 5.6), "mat": "ceiling"},
|
|
{"size": Vector3(6.0, 0.90, 0.65), "at": Vector3(5.0, 0.45, 7.5), "mat": "ceiling"},
|
|
{"size": Vector3(6.2, 0.06, 0.75), "at": Vector3(5.0, 0.92, 7.5), "mat": "trim"},
|
|
# the long table everyone actually works at
|
|
{"size": Vector3(1.5, 0.09, 4.6), "at": Vector3(-4.4, 0.75, 0.6), "mat": "wood"},
|
|
{"size": Vector3(0.12, 0.72, 0.12), "at": Vector3(-4.9, 0.36, -1.4), "mat": "trim"},
|
|
{"size": Vector3(0.12, 0.72, 0.12), "at": Vector3(-3.9, 0.36, 2.6), "mat": "trim"},
|
|
# whiteboard, obviously
|
|
{"size": Vector3(0.05, 1.40, 3.0), "at": Vector3(-9.93, 1.75, 1.0), "mat": "ceiling"},
|
|
{"size": Vector3(0.03, 1.50, 3.1), "at": Vector3(-9.90, 1.75, 1.0), "mat": "trim"},
|
|
# a low bookshelf run
|
|
{"size": Vector3(0.42, 0.90, 3.2), "at": Vector3(-9.7, 0.45, -5.0), "mat": "wood"},
|
|
# pool decking visible through the glass
|
|
{"size": Vector3(7.0, 0.10, 16.0), "at": Vector3(12.6, -0.05, 0.0),
|
|
"mat": "ceiling", "collide": false},
|
|
],
|
|
"props": [
|
|
{"glb": "sofa", "at": Vector3(-6.6, 0.0, 5.4), "yaw": -PI / 2},
|
|
{"glb": "sofa", "at": Vector3(-2.0, 0.0, 6.6), "yaw": PI},
|
|
{"glb": "office-plant", "at": Vector3(-0.6, 0.0, 3.2)},
|
|
{"glb": "office-plant", "at": Vector3(7.2, 0.0, -6.4)},
|
|
{"glb": "office-plant", "at": Vector3(-9.2, 0.0, 7.2)},
|
|
{"glb": "microwave", "at": Vector3(5.4, 0.95, 7.4), "yaw": PI},
|
|
{"glb": "server-rack", "at": Vector3(9.0, 0.0, -6.8), "yaw": -PI / 2},
|
|
],
|
|
"chairs": [
|
|
{"at": Vector3(-6.0, 0.0, -1.4), "yaw": -PI / 2},
|
|
{"at": Vector3(-6.0, 0.0, -0.2), "yaw": -PI / 2},
|
|
{"at": Vector3(-6.0, 0.0, 1.4), "yaw": -PI / 2},
|
|
{"at": Vector3(-6.0, 0.0, 2.6), "yaw": -PI / 2},
|
|
{"at": Vector3(-2.8, 0.0, -1.0), "yaw": PI / 2},
|
|
{"at": Vector3(-2.8, 0.0, 1.8), "yaw": PI / 2},
|
|
{"at": Vector3(4.2, 0.0, 4.2), "yaw": 0.0},
|
|
{"at": Vector3(5.2, 0.0, 4.2), "yaw": 0.0},
|
|
{"at": Vector3(-7.6, 0.0, -6.4), "yaw": 0.2},
|
|
],
|
|
"copier": {"at": Vector3(-8.6, 0.0, -3.4), "yaw": PI / 2},
|
|
"smashables": [
|
|
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-4.4, -2.2)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.4, 2.2)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.54, 2.06),
|
|
"sit_on": 0.40, "yaw": -0.14},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-8.2, 6.4)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(-2.6, -3.2)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(6.2, 3.0)},
|
|
{"glb": "water-cooler", "kind": "steel", "at": Vector2(1.0, 6.8)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(-9.3, -1.6)},
|
|
{"glb": "turntable", "kind": "wood", "at": Vector2(-9.4, -6.6)},
|
|
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(4.0, 5.6), "sit_on": 0.99},
|
|
{"glb": "guillotine", "kind": "steel", "at": Vector2(-9.4, -3.4)},
|
|
],
|
|
"records": [Vector2(-8.4, -7.0), Vector2(-7.6, -7.0)],
|
|
"gauntlet": {"at": Vector3(-7.2, 0.0, -7.2), "yaw": PI,
|
|
"stand": Vector3(-7.2, 0.0, -5.2), "shredder": Vector3(-5.0, 0.0, -6.8)},
|
|
"spawn": {"at": Vector3(-1.0, 0.0, 7.0), "yaw": deg_to_rad(186.0)},
|
|
}
|
|
|
|
# ================================================================ LEVEL 04
|
|
static func basement() -> Dictionary:
|
|
return {
|
|
"name": "SUB-LEVEL 4",
|
|
"subtitle": "i.t. department · no windows",
|
|
"bounds": {"x0": -12.0, "x1": 12.0, "z0": -6.0, "z1": 6.0, "h": 2.90},
|
|
"palette": {
|
|
"floor": Color(0.22, 0.22, 0.23), "wall": Color(0.50, 0.53, 0.46),
|
|
"ceiling": Color(0.20, 0.20, 0.21), "trim": Color(0.10, 0.10, 0.10),
|
|
"partition": Color(0.34, 0.36, 0.32), "wood": Color(0.30, 0.20, 0.12),
|
|
"accent": Color(0.52, 0.10, 0.09),
|
|
},
|
|
"floor_style": "concrete",
|
|
"ceiling": "flat",
|
|
"walls": [
|
|
# the boss's glass box, bolted into a room that never wanted one
|
|
{"a": "z", "at": 4.6, "from": -6.0, "to": 1.2, "door": -1.0,
|
|
"style": "glass_top"},
|
|
{"a": "x", "at": 1.2, "from": 4.6, "to": 12.0},
|
|
# server room behind a security door
|
|
{"a": "z", "at": -6.4, "from": -6.0, "to": 6.0, "door": 1.0},
|
|
# a stub wall someone put up and nobody took down
|
|
{"a": "x", "at": -1.4, "from": -4.0, "to": -1.6},
|
|
],
|
|
"windows": [], # it is a basement
|
|
"lights": {"xs": [-9.0, -4.5, 0.0, 4.5, 9.0], "zs": [-3.6, 0.4, 4.0],
|
|
"color": Color(0.86, 0.96, 0.84), "energy": 2.0, "style": "strip",
|
|
"key_energy": 1.1},
|
|
"env": {"bg": Color(0.06, 0.07, 0.06), "ambient": Color(0.32, 0.36, 0.32),
|
|
"ambient_energy": 0.40, "exposure": 1.08},
|
|
"clusters": [Vector2(3.6, 0.0)],
|
|
"cluster_gap": 0.82,
|
|
"slabs": [
|
|
# exposed services, because there's no ceiling to hide them
|
|
{"size": Vector3(24.0, 0.22, 0.22), "at": Vector3(0.0, 2.70, -3.0),
|
|
"mat": "trim", "collide": false},
|
|
{"size": Vector3(24.0, 0.16, 0.16), "at": Vector3(0.0, 2.62, 1.4),
|
|
"mat": "trim", "collide": false},
|
|
{"size": Vector3(24.0, 0.30, 0.10), "at": Vector3(0.0, 2.78, 4.2),
|
|
"mat": "trim", "collide": false},
|
|
# boss's desk in the glass box
|
|
{"size": Vector3(1.8, 0.08, 0.86), "at": Vector3(8.0, 0.74, 5.4), "mat": "wood"},
|
|
{"size": Vector3(1.6, 0.66, 0.74), "at": Vector3(8.0, 0.35, 5.4), "mat": "wood"},
|
|
# workbench along the back
|
|
{"size": Vector3(5.0, 0.08, 0.80), "at": Vector3(-6.0, 0.90, -5.5), "mat": "trim"},
|
|
{"size": Vector3(0.10, 0.88, 0.70), "at": Vector3(-8.4, 0.44, -5.5), "mat": "trim"},
|
|
{"size": Vector3(0.10, 0.88, 0.70), "at": Vector3(-3.6, 0.44, -5.5), "mat": "trim"},
|
|
# the famous red door
|
|
{"size": Vector3(1.10, 2.10, 0.10), "at": Vector3(1.0, 1.05, -6.35),
|
|
"mat": "accent"},
|
|
],
|
|
"props": [
|
|
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, -4.4), "yaw": PI / 2},
|
|
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, -2.4), "yaw": PI / 2},
|
|
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, -0.4), "yaw": PI / 2},
|
|
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, 1.6), "yaw": PI / 2},
|
|
{"glb": "sofa", "at": Vector3(-4.0, 0.0, 3.4), "yaw": 0.15},
|
|
{"glb": "office-plant", "at": Vector3(11.2, 0.0, 5.4)},
|
|
{"glb": "vending-machine", "at": Vector3(-0.4, 0.0, 5.4), "yaw": PI},
|
|
{"glb": "microwave", "at": Vector3(-5.0, 0.95, -5.5), "yaw": PI},
|
|
],
|
|
"chairs": [
|
|
{"at": Vector3(8.0, 0.0, 4.1), "yaw": PI},
|
|
{"at": Vector3(6.6, 0.0, 5.9), "yaw": 0.3},
|
|
{"at": Vector3(-6.6, 0.0, -4.2), "yaw": PI},
|
|
{"at": Vector3(-1.0, 0.0, 2.2), "yaw": 1.2},
|
|
],
|
|
"copier": {"at": Vector3(0.4, 0.0, -4.4), "yaw": 0.0},
|
|
"smashables": [
|
|
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-7.0, -5.5), "sit_on": 0.94},
|
|
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-5.0, -4.6)},
|
|
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-8.4, -3.2)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(10.8, 3.0)},
|
|
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(10.8, 2.2)},
|
|
# nobody in an IT basement puts a box down on its own
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.6, -4.8)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.46, -4.92),
|
|
"sit_on": 0.40, "yaw": 0.09},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.58, -4.70),
|
|
"sit_on": 0.80, "yaw": -0.16},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(5.6, -4.8)},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(5.72, -4.94),
|
|
"sit_on": 0.40, "yaw": -0.11},
|
|
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(5.58, -4.68),
|
|
"sit_on": 0.80, "yaw": 0.20},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(2.0, 2.6)},
|
|
{"glb": "wastebin", "kind": "plastic", "at": Vector2(9.0, 4.4)},
|
|
{"glb": "water-cooler", "kind": "steel", "at": Vector2(0.6, 2.2)},
|
|
{"glb": "shredder", "kind": "plastic", "at": Vector2(-2.2, -2.0)},
|
|
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(8.3, 5.4), "sit_on": 0.78},
|
|
{"glb": "turntable", "kind": "wood", "at": Vector2(-9.0, 4.4)},
|
|
{"glb": "guillotine", "kind": "steel", "at": Vector2(-6.0, -4.2)},
|
|
],
|
|
"records": [Vector2(-8.2, 5.0), Vector2(-7.5, 5.0)],
|
|
"gauntlet": {"at": Vector3(-3.4, 0.0, -3.4), "yaw": PI,
|
|
"stand": Vector3(-3.4, 0.0, -1.5), "shredder": Vector3(-2.2, 0.0, -2.0)},
|
|
"spawn": {"at": Vector3(1.0, 0.0, -5.2), "yaw": deg_to_rad(0.0)},
|
|
}
|
|
|
|
# ================================================================ LEVEL 00
|
|
## THE BACKROOMS. The only site whose walls are GENERATED rather than authored, which is
|
|
## both the honest test of the Floorplan spec and the right call for the fiction: this
|
|
## place is supposed to feel endless and samey, and a seeded RNG produces that better
|
|
## than a human placing slabs ever would.
|
|
##
|
|
## Layout follows the reference photographs rather than a maze algorithm — the Backrooms
|
|
## aren't corridors, they're big offset SLABS sitting in an open floor with gaps between
|
|
## them, so that's what this emits: partial wall runs along a coarse grid, plus pillars.
|
|
## A proper maze would be legible; this is deliberately not.
|
|
##
|
|
## Everything smashable here is INVISIBLE (see Dust.gd). You find it by walking into it.
|
|
const BR_SEED := 0xBACC
|
|
const BR_HALF := 19.0
|
|
const BR_H := 2.55 # low. lower than any real office.
|
|
|
|
static func backrooms() -> Dictionary:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = BR_SEED
|
|
|
|
var walls: Array = []
|
|
# Wall runs on a 4.6 m lattice. Each line gets a few segments with gaps between, so
|
|
# you can always get through somewhere but never see far.
|
|
# Density matters more than it looks. The first pass put a run on every lattice line
|
|
# in both axes and the result had NO sightline longer than a few metres — which kills
|
|
# the entity outright, because a stalker you can never see can never be stared at. The
|
|
# reference photographs are mostly open floor with occasional slabs, so: skip roughly
|
|
# a third of the lines, and leave big gaps.
|
|
var lines := [-13.8, -9.2, -4.6, 0.0, 4.6, 9.2, 13.8]
|
|
for axis in ["x", "z"]:
|
|
for at in lines:
|
|
if rng.randf() < 0.34:
|
|
continue # this line is simply open floor
|
|
var cursor := -BR_HALF
|
|
while cursor < BR_HALF - 2.0:
|
|
var gap := rng.randf_range(4.5, 12.0)
|
|
cursor += gap
|
|
if cursor >= BR_HALF - 2.0:
|
|
break
|
|
var run := rng.randf_range(3.0, 7.5)
|
|
var end: float = minf(cursor + run, BR_HALF)
|
|
if end - cursor > 1.4:
|
|
walls.append({"a": axis, "at": at, "from": cursor, "to": end})
|
|
cursor = end
|
|
|
|
# freestanding pillars, because every Backrooms photo has one in the way
|
|
var slabs: Array = []
|
|
for i in 16:
|
|
var px := rng.randf_range(-BR_HALF + 2.0, BR_HALF - 2.0)
|
|
var pz := rng.randf_range(-BR_HALF + 2.0, BR_HALF - 2.0)
|
|
slabs.append({"size": Vector3(0.62, BR_H, 0.62),
|
|
"at": Vector3(px, BR_H * 0.5, pz), "mat": "wall"})
|
|
# skirting-height trim run around the outer wall, the one detail the photos all share
|
|
for s in [-1.0, 1.0]:
|
|
slabs.append({"size": Vector3(BR_HALF * 2.0, 0.12, 0.06),
|
|
"at": Vector3(0.0, 0.06, s * (BR_HALF - 0.03)), "mat": "trim",
|
|
"collide": false})
|
|
slabs.append({"size": Vector3(0.06, 0.12, BR_HALF * 2.0),
|
|
"at": Vector3(s * (BR_HALF - 0.03), 0.06, 0.0), "mat": "trim",
|
|
"collide": false})
|
|
|
|
# a dense grid of buzzing troffers — the light IS the horror
|
|
var lx: Array = []
|
|
var lz: Array = []
|
|
var v := -BR_HALF + 2.3
|
|
while v < BR_HALF:
|
|
lx.append(v)
|
|
lz.append(v)
|
|
v += 3.05
|
|
|
|
# sparse, wrong office objects. All invisible until you dust them.
|
|
var smash: Array = []
|
|
var kinds := [["office-chair", "plastic"], ["wastebin", "plastic"],
|
|
["cardboard-box", "cardboard"], ["filing-cabinet", "steel"],
|
|
["crt-monitor", "glass"], ["desk-phone", "plastic"],
|
|
["water-cooler", "steel"], ["office-plant", "wood"]]
|
|
# dense enough that a single cloud finds you two or three things — the level is
|
|
# a search, and a search with nothing in it is just a walk
|
|
# Rejection sampling: the widest thing on this list is a 1.35 m cardboard box, so
|
|
# anything landing inside 1.6 m of a spot already taken gets redrawn. Two props
|
|
# spawned inside each other are a filing cabinet launched across the maze on frame
|
|
# one, which in a level built on silence is not the surprise you wanted.
|
|
var taken: Array = []
|
|
var tries := 0
|
|
while smash.size() < 78 and tries < 4000:
|
|
tries += 1
|
|
var spot := Vector2(rng.randf_range(-BR_HALF + 2.5, BR_HALF - 2.5),
|
|
rng.randf_range(-BR_HALF + 2.5, BR_HALF - 2.5))
|
|
var clear := true
|
|
for t in taken:
|
|
if (spot - (t as Vector2)).length() < 1.6:
|
|
clear = false
|
|
break
|
|
if not clear:
|
|
continue
|
|
taken.append(spot)
|
|
var k: Array = kinds[rng.randi() % kinds.size()]
|
|
smash.append({
|
|
"glb": String(k[0]), "kind": String(k[1]), "at": spot,
|
|
"yaw": rng.randf_range(0.0, TAU),
|
|
})
|
|
|
|
return {
|
|
"name": "LEVEL 0",
|
|
"subtitle": "you noclipped out of reality",
|
|
"invisible": true, # Dust.gd hides every smashable here
|
|
"bounds": {"x0": -BR_HALF, "x1": BR_HALF, "z0": -BR_HALF, "z1": BR_HALF,
|
|
"h": BR_H},
|
|
"palette": {
|
|
# the whole point is that there is only one colour
|
|
"floor": Color(0.52, 0.47, 0.30), "wall": Color(0.76, 0.72, 0.42),
|
|
"ceiling": Color(0.80, 0.77, 0.55), "trim": Color(0.62, 0.58, 0.34),
|
|
"partition": Color(0.74, 0.70, 0.40), "wood": Color(0.62, 0.55, 0.32),
|
|
"accent": Color(0.70, 0.66, 0.38),
|
|
},
|
|
"floor_style": "carpet",
|
|
"ceiling": "tile",
|
|
"walls": walls,
|
|
"windows": [], # there is no outside
|
|
"slabs": slabs,
|
|
"lights": {"xs": lx, "zs": lz, "color": Color(1.0, 0.97, 0.80),
|
|
"energy": 1.15, "style": "troffer", "key_energy": 0.9},
|
|
"env": {"bg": Color(0.30, 0.28, 0.16), "ambient": Color(0.62, 0.58, 0.36),
|
|
"ambient_energy": 0.70, "exposure": 1.12},
|
|
"clusters": [],
|
|
"props": [],
|
|
"chairs": [],
|
|
"smashables": smash,
|
|
"records": [],
|
|
"gauntlet": {"at": Vector3(0.0, 0.0, -2.2), "yaw": PI,
|
|
"stand": Vector3(0.0, 0.0, 0.2), "shredder": Vector3(3.0, 0.0, -1.0)},
|
|
"spawn": {"at": Vector3(0.0, 0.0, 16.5), "yaw": PI},
|
|
}
|
|
|
|
# ================================================================ THE GROCER
|
|
## A greengrocer, and the only site whose contents are mostly SOFT.
|
|
##
|
|
## Everything else in the game shatters; here the stock bursts. That one material change
|
|
## rewrites the level: produce squashes instead of breaking, a pyramid crushes its own
|
|
## bottom layer as it collapses, and the floor fills with juice you then slip in. The
|
|
## glass bottles behind the juice bar are the exception, and the contrast is the point —
|
|
## one shelf in the room still rewards a proper swing.
|
|
##
|
|
## Bright, clean, warm track lighting on a pale floor, because the whole level is about
|
|
## making a mess and a mess needs somewhere clean to happen.
|
|
static func grocer() -> Dictionary:
|
|
var stacks: Array = []
|
|
# Angled island displays. `at` is the TOP surface a pyramid piles onto; Main builds
|
|
# the pyramid itself so the physics settles honestly rather than being placed.
|
|
var islands := [
|
|
[Vector3(-5.4, 0.95, -2.2), "apple", 5],
|
|
[Vector3(-5.4, 0.95, 1.4), "orange", 5],
|
|
[Vector3(-2.0, 0.95, -2.2), "tomato", 5],
|
|
[Vector3(-2.0, 0.95, 1.4), "apple", 4],
|
|
[Vector3(1.4, 0.95, -2.2), "cabbage", 3],
|
|
[Vector3(1.4, 0.95, 1.4), "orange", 4],
|
|
[Vector3(4.8, 0.80, -0.4), "melon", 3],
|
|
]
|
|
for i in islands:
|
|
stacks.append({"at": i[0], "item": String(i[1]), "rows": int(i[2])})
|
|
|
|
var slabs: Array = []
|
|
# the island tables themselves: a plinth, a timber top, and a raised back edge so a
|
|
# pyramid has something to fail against
|
|
for i in islands:
|
|
var p: Vector3 = i[0]
|
|
var big: bool = String(i[1]) == "melon"
|
|
var w := 2.4 if big else 1.5
|
|
var d := 1.9 if big else 1.5
|
|
var h: float = p.y
|
|
slabs.append({"size": Vector3(w, h - 0.10, d),
|
|
"at": Vector3(p.x, (h - 0.10) * 0.5, p.z), "mat": "trim"})
|
|
slabs.append({"size": Vector3(w + 0.14, 0.10, d + 0.14),
|
|
"at": Vector3(p.x, h - 0.05, p.z), "mat": "wood"})
|
|
for s in [-1.0, 1.0]:
|
|
slabs.append({"size": Vector3(w + 0.14, 0.10, 0.06),
|
|
"at": Vector3(p.x, h + 0.05, p.z + s * (d * 0.5 + 0.04)), "mat": "wood"})
|
|
slabs.append({"size": Vector3(0.06, 0.10, d + 0.14),
|
|
"at": Vector3(p.x + s * (w * 0.5 + 0.04), h + 0.05, p.z), "mat": "wood"})
|
|
|
|
# juice bar along the back, with shelving above it for the bottles
|
|
slabs.append({"size": Vector3(7.0, 1.05, 0.75), "at": Vector3(-3.0, 0.525, -7.4),
|
|
"mat": "trim"})
|
|
slabs.append({"size": Vector3(7.2, 0.09, 0.92), "at": Vector3(-3.0, 1.10, -7.4),
|
|
"mat": "wood"})
|
|
for k in range(3):
|
|
slabs.append({"size": Vector3(6.6, 0.06, 0.34),
|
|
"at": Vector3(-3.0, 1.62 + k * 0.46, -7.72), "mat": "wood"})
|
|
# chiller run down the right wall
|
|
slabs.append({"size": Vector3(0.80, 2.10, 8.0), "at": Vector3(10.4, 1.05, 1.0),
|
|
"mat": "trim"})
|
|
for k in range(4):
|
|
slabs.append({"size": Vector3(0.86, 0.05, 8.0),
|
|
"at": Vector3(10.3, 0.55 + k * 0.46, 1.0), "mat": "ceiling"})
|
|
|
|
var smash: Array = []
|
|
# bottles on the bar shelves — the one thing in here that still wants a proper swing
|
|
for k in range(3):
|
|
for b in range(11):
|
|
smash.append({"glb": "juice-bottle", "kind": "glass",
|
|
"at": Vector2(-6.0 + b * 0.60, -7.72), "sit_on": 1.65 + k * 0.46})
|
|
for b in range(9):
|
|
smash.append({"glb": "juice-bottle", "kind": "glass",
|
|
"at": Vector2(-5.6 + b * 0.62, -7.4), "sit_on": 1.15})
|
|
# loose crates of stock on the floor
|
|
for c in [Vector2(-8.6, 3.4), Vector2(-8.6, 4.4), Vector2(-8.0, 5.4),
|
|
Vector2(7.4, -4.6), Vector2(8.2, -4.0), Vector2(7.8, 5.2)]:
|
|
smash.append({"glb": "produce-crate", "kind": "wood", "at": c,
|
|
"yaw": randf_range(0.0, TAU)})
|
|
# a few bananas and cabbages just lying about
|
|
for b in [Vector2(-7.2, 0.4), Vector2(3.2, 4.2), Vector2(6.0, 2.0)]:
|
|
smash.append({"glb": "produce-banana", "kind": "produce", "at": b})
|
|
for c in [Vector2(-9.4, -2.0), Vector2(2.4, -5.6)]:
|
|
smash.append({"glb": "produce-cabbage", "kind": "produce", "at": c})
|
|
|
|
return {
|
|
"name": "THE GREENGROCER",
|
|
"subtitle": "everything here is soft except the bottles",
|
|
"bounds": {"x0": -11.0, "x1": 11.0, "z0": -8.0, "z1": 8.0, "h": 3.30},
|
|
"palette": {
|
|
"floor": Color(0.70, 0.68, 0.64), "wall": Color(0.90, 0.89, 0.85),
|
|
"ceiling": Color(0.20, 0.20, 0.21), "trim": Color(0.15, 0.15, 0.16),
|
|
"partition": Color(0.30, 0.44, 0.40), "wood": Color(0.52, 0.36, 0.20),
|
|
"accent": Color(0.20, 0.48, 0.42),
|
|
},
|
|
"floor_style": "lino",
|
|
"ceiling": "flat",
|
|
"walls": [],
|
|
"windows": [{"a": "z", "at": 8.0, "from": -11.0, "to": 11.0,
|
|
"y0": 0.35, "y1": 2.85, "blinds": false}],
|
|
"lights": {"xs": [-8.0, -4.0, 0.0, 4.0, 8.0], "zs": [-6.0, -2.0, 2.0, 6.0],
|
|
"color": Color(1.0, 0.94, 0.84), "energy": 2.4, "style": "strip",
|
|
"key_energy": 1.8},
|
|
"sun": {"color": Color(1.0, 0.96, 0.88), "energy": 1.1,
|
|
"rot": Vector3(-40, 8, 0)},
|
|
"env": {"bg": Color(0.76, 0.82, 0.88), "ambient": Color(0.60, 0.58, 0.55),
|
|
"ambient_energy": 0.66, "exposure": 1.04},
|
|
"clusters": [],
|
|
"chairs": [],
|
|
"props": [
|
|
{"glb": "office-plant", "at": Vector3(-10.2, 0.0, 7.0)},
|
|
{"glb": "office-plant", "at": Vector3(9.6, 0.0, 7.0)},
|
|
# the bag roll. Its post stands on the floor; the station is the roll itself.
|
|
{"glb": "bag-roll", "at": Vector3(-0.3, 0.0, 3.6), "yaw": PI},
|
|
],
|
|
# Brass shop scales hung down the aisles, dial at eye level and the dish at chest
|
|
# height — where a real one hangs, where you look INTO the pan rather than at its
|
|
# unlit underside, and where you keep walking into it.
|
|
"scales": [
|
|
Vector3(-3.7, 1.58, -0.4), Vector3(-0.3, 1.58, -0.4),
|
|
# the counter one hangs higher, so you can still see the bottle shelf
|
|
Vector3(3.1, 1.58, -0.4), Vector3(-3.0, 1.88, -6.5),
|
|
],
|
|
"stations": [
|
|
{"kind": "bags", "at": Vector3(-0.3, 1.1, 3.6), "label": "bag up the order"},
|
|
],
|
|
"slabs": slabs,
|
|
"smashables": smash,
|
|
"stacks": stacks,
|
|
"records": [],
|
|
"gauntlet": {"at": Vector3(-8.6, 0.0, -6.0), "yaw": PI,
|
|
"stand": Vector3(-8.6, 0.0, -4.0), "shredder": Vector3(-6.4, 0.0, -5.4)},
|
|
"spawn": {"at": Vector3(0.0, 0.0, 6.4), "yaw": PI},
|
|
}
|
|
|
|
# ================================================================ SITE 7
|
|
## THE MONSTER ROBOT PARTY — the shop the whole game started as.
|
|
##
|
|
## The only site that is IMPORTED rather than authored. The live storefront at
|
|
## robotmonster.party/store/ is assembled procedurally from Postgres (the wowplatter
|
|
## virtual_* schema, served by recordgod), and this game's sites are data, so
|
|
## tools/import_store.py reads the real shop and emits assets/store/shop_floor.json:
|
|
## 35 racks, 278 bins carrying their real genre labels, and 21,159 records of stock.
|
|
##
|
|
## The shop's schema IS the cascade. virtual_crate.rack_id already records which rack
|
|
## holds which bin, so every crate arrives frozen and `supported_by` its rack — knock a
|
|
## rack over and the actual bins that actually sit on it actually go. None of that is
|
|
## authored; it comes off the shop floor.
|
|
##
|
|
## Re-run the importer when the real shop is rearranged and this level follows.
|
|
const STORE_JSON := "res://assets/store/shop_floor.json"
|
|
|
|
static func record_store() -> Dictionary:
|
|
var raw := FileAccess.get_file_as_string(STORE_JSON)
|
|
var data = JSON.parse_string(raw) if raw != "" else null
|
|
if data == null:
|
|
push_warning("[store] %s missing — run tools/import_store.py" % STORE_JSON)
|
|
data = {"room": {"w": 6.66, "d": 9.51, "h": 2.89}, "smashables": [], "slabs": [],
|
|
"spawn": {"at": [0.0, 3.0]}}
|
|
|
|
var room: Dictionary = data.get("room", {})
|
|
var w: float = float(room.get("w", 6.66))
|
|
var dp: float = float(room.get("d", 9.51))
|
|
var h: float = float(room.get("h", 2.89))
|
|
|
|
# JSON has no Vector types — rehydrate into what Floorplan/Main expect.
|
|
var smash: Array = []
|
|
for e in data.get("smashables", []):
|
|
var s: Dictionary = e.duplicate()
|
|
var a: Array = s["at"]
|
|
s["at"] = Vector2(float(a[0]), float(a[1]))
|
|
smash.append(s)
|
|
var slabs: Array = []
|
|
for e in data.get("slabs", []):
|
|
var sz: Array = e["size"]
|
|
var at: Array = e["at"]
|
|
slabs.append({"size": Vector3(float(sz[0]), float(sz[1]), float(sz[2])),
|
|
"at": Vector3(float(at[0]), float(at[1]), float(at[2])),
|
|
"mat": String(e.get("mat", "wood"))})
|
|
var sp: Array = data.get("spawn", {}).get("at", [0.0, 3.0])
|
|
# The interior walls between the four rooms, with a gap wherever a doorway pierces
|
|
# one — the importer derives them from the shop's own portal graph.
|
|
var walls: Array = []
|
|
for e in data.get("walls", []):
|
|
var wl: Dictionary = {"a": String(e.get("a", "x")), "at": float(e.get("at", 0.0)),
|
|
"from": float(e.get("from", 0.0)), "to": float(e.get("to", 0.0))}
|
|
if e.has("door"):
|
|
wl["door"] = float(e["door"])
|
|
walls.append(wl)
|
|
# The street glazing goes on the entry hall's outer wall, because that is the way in.
|
|
var windows: Array = []
|
|
for r in data.get("rooms", []):
|
|
if not String(r.get("name", "")).begins_with("entry"):
|
|
continue
|
|
var at: Array = r["at"]
|
|
var size: Array = r["size"]
|
|
var rx0: float = float(at[0])
|
|
var rz0: float = float(at[1])
|
|
if absf(rz0 - (-dp * 0.5)) < 0.2:
|
|
windows.append({"a": "z", "at": -dp * 0.5, "from": rx0 + 0.4,
|
|
"to": rx0 + float(size[0]) - 0.4, "y0": 0.6, "y1": 2.3, "blinds": false})
|
|
|
|
# a light every ~3 m across whatever footprint the four rooms came out as
|
|
var lx: Array = []
|
|
var lz: Array = []
|
|
var gx: float = -w * 0.5 + 1.6
|
|
while gx < w * 0.5 - 0.6:
|
|
lx.append(gx)
|
|
gx += 3.0
|
|
var gz: float = -dp * 0.5 + 1.6
|
|
while gz < dp * 0.5 - 0.6:
|
|
lz.append(gz)
|
|
gz += 3.0
|
|
|
|
return {
|
|
"name": "MONSTER ROBOT PARTY",
|
|
"subtitle": "your own shop. you know exactly what everything cost.",
|
|
"bounds": {"x0": -w * 0.5, "x1": w * 0.5, "z0": -dp * 0.5, "z1": dp * 0.5, "h": h},
|
|
"palette": {
|
|
"floor": Color(0.42, 0.31, 0.22), "wall": Color(0.93, 0.92, 0.90),
|
|
"ceiling": Color(0.17, 0.16, 0.19), "trim": Color(0.19, 0.18, 0.21),
|
|
"partition": Color(0.35, 0.20, 0.34), "wood": Color(0.52, 0.36, 0.20),
|
|
"accent": Color(1.0, 0.36, 0.62), # the shop's own pink
|
|
},
|
|
"floor_style": "timber",
|
|
"ceiling": "flat",
|
|
"walls": walls,
|
|
"windows": windows,
|
|
"lights": {"xs": lx, "zs": lz,
|
|
"color": Color(1.0, 0.93, 0.86), "energy": 2.1, "style": "strip",
|
|
"key_energy": 1.5},
|
|
"sun": {"color": Color(1.0, 0.95, 0.88), "energy": 0.9, "rot": Vector3(-38, 14, 0)},
|
|
"env": {"bg": Color(0.72, 0.76, 0.82), "ambient": Color(0.58, 0.55, 0.56),
|
|
"ambient_energy": 0.62, "exposure": 1.02},
|
|
"clusters": [],
|
|
"chairs": [],
|
|
"props": [],
|
|
"slabs": slabs,
|
|
"smashables": smash,
|
|
"stacks": [],
|
|
# a few playable records loose on the counter side, for the ritual
|
|
"records": [],
|
|
"spawn": {"at": Vector3(float(sp[0]), 0.0, float(sp[1])), "yaw": PI},
|
|
}
|