The game had no room. Props sat on a grey disc in a black void, which quietly broke the premise: a game about wrecking your workplace needs a workplace. Office.gd — LEVEL 01 - Open-plan office laid out after The Office (US) floor plan: bullpen of facing desk pairs behind cubicle partitions, reception, glass-walled manager's office, conference room, break room with vending machines, copier alcove, warehouse roller door. - Drop ceiling on a T-bar grid with fluorescent troffers that ARE the light sources (the old scene lit an interior with one outdoor directional lamp, which is exactly why it read as "props on a plane"), window wall with blinds, magnolia and carpet. - Office owns the shell + static fittings; Main._populate() owns everything smashable and asks Office where things go. Walls/desks/counters are boxes because they ARE boxes; chairs, vending machines, microwave and plant come from a new Blender generator (tools/gen_office_props.py) because a box reads as wrong for those. The level no longer falls over on its own - Records were stacked 3 cm apart vertically while being 30 cm TALL, so Jolt resolved 27 cm of interpenetration explosively on frame one and shoved the furniture over before the player touched anything. They now stand side by side. - Per-material mass (MASSES): everything was 1 kg, so a thrown record could tip a filing cabinet. - Spawn guard: placing ~40 props by formula guarantees an occasional overlap, and depenetration is violent (a chair left the building at 500 m/s). Dynamic bodies are speed-limited for 0.75 s, and each offender is named once with the position it was PLACED at, so the cause stays visible instead of being papered over. It then found the real bug: bullpen rows 3.6 m apart left the two rows' chairs meeting back-to-back with 3 cm to spare. Rows are now 4.8 m apart. - dev/DemoDriver.gd act 0 touches nothing for 5 s and prints total body speed. Now reads 0.00 m/s with zero spawn warnings. Two real melee bugs found while testing the level - The hit test was a SPHERE parked at `reach`, so anything CLOSER than the weapon's reach fell in front of it and was missed — you could stand against the printer with a sledgehammer and swing straight through it. Now a capsule swept from the camera. - Swings started at eye height (1.6 m), so a carton on the floor was ~1.5 m away even standing over it and short-reach weapons could never touch anything on the ground. Swings now originate at hand height. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
455 lines
17 KiB
GDScript
455 lines
17 KiB
GDScript
extends Node3D
|
|
class_name Office
|
|
|
|
## LEVEL 01 — the open-plan office.
|
|
##
|
|
## The prototype ran on a grey disc in a black void, which quietly broke the premise: a
|
|
## game about wrecking your workplace needs a workplace. This is that workplace, laid out
|
|
## after the Dunder Mifflin floor plan — the shape everyone already knows.
|
|
##
|
|
## z- (back)
|
|
## +--------------------------------------------------+
|
|
## | MANAGER | CONFERENCE | | WAREHOUSE |
|
|
## | (glass) | | | door |
|
|
## |----------+---------------+ +-----------|
|
|
## | | |
|
|
## | B U L L P E N | BREAK | x+
|
|
## | (4 facing desk pairs) | ROOM |
|
|
## | [copier] | |
|
|
## |----------+ +-----------|
|
|
## | RECEPTION| entrance |
|
|
## +--------------------------------------------------+
|
|
## z+ (front / street)
|
|
##
|
|
## Walls, desks, partitions and counters are boxes built in code — they ARE boxes, so
|
|
## modelling them would be wasted effort. Anything with a silhouette a box can't fake
|
|
## (chairs, vending machines, the plant) comes from tools/gen_office_props.py.
|
|
##
|
|
## This script owns the SHELL and the FITTINGS: everything static and non-smashable, plus
|
|
## the lighting. Smashable furniture stays Main's business, so the two can be tuned
|
|
## independently — Main asks this class where things go via the `spot_*` helpers.
|
|
##
|
|
## Godot 4.7 GDScript 2.0.
|
|
|
|
# ---------------------------------------------------------------- floor plan
|
|
const X_MIN := -11.0
|
|
const X_MAX := 11.0
|
|
const Z_BACK := -8.0
|
|
const Z_FRONT := 8.0
|
|
const H := 2.75 # drop ceiling
|
|
const WALL_T := 0.16
|
|
|
|
# interior room boundaries
|
|
const MGR_X := -6.6 # manager's office: X_MIN..MGR_X, Z_BACK..MGR_Z
|
|
const MGR_Z := -4.4
|
|
const CONF_X0 := -6.6 # conference room
|
|
const CONF_X1 := -0.4
|
|
const CONF_Z := -4.4
|
|
const BREAK_X := 5.4 # break room: BREAK_X..X_MAX, BREAK_Z..Z_FRONT
|
|
const BREAK_Z := -1.2
|
|
const RECEP_Z := 4.6 # reception partition
|
|
|
|
const DOOR_W := 1.1
|
|
|
|
const P := "res://assets/store/"
|
|
|
|
var _spots_desk: Array[Transform3D] = [] # where Main should put smashable desks
|
|
var _spots_chair: Array[Transform3D] = []
|
|
var _spot_copier := Transform3D.IDENTITY
|
|
|
|
# ---------------------------------------------------------------- materials
|
|
var _m_carpet: StandardMaterial3D
|
|
var _m_wall: StandardMaterial3D
|
|
var _m_ceil: StandardMaterial3D
|
|
var _m_trim: StandardMaterial3D
|
|
var _m_glass: StandardMaterial3D
|
|
var _m_part: StandardMaterial3D
|
|
var _m_wood: StandardMaterial3D
|
|
|
|
func build() -> void:
|
|
_materials()
|
|
_shell()
|
|
_interior_walls()
|
|
_ceiling_grid()
|
|
_lights_rig()
|
|
_bullpen()
|
|
_reception()
|
|
_conference()
|
|
_manager()
|
|
_break_room()
|
|
_dressing()
|
|
|
|
func _materials() -> void:
|
|
_m_carpet = StandardMaterial3D.new()
|
|
_m_carpet.albedo_color = Color(0.30, 0.30, 0.32) # the grey office carpet
|
|
_m_carpet.roughness = 1.0
|
|
|
|
_m_wall = StandardMaterial3D.new()
|
|
_m_wall.albedo_color = Color(0.80, 0.78, 0.72) # magnolia, of course
|
|
_m_wall.roughness = 0.96
|
|
|
|
_m_ceil = StandardMaterial3D.new()
|
|
_m_ceil.albedo_color = Color(0.88, 0.88, 0.86)
|
|
_m_ceil.roughness = 0.98
|
|
|
|
_m_trim = StandardMaterial3D.new()
|
|
_m_trim.albedo_color = Color(0.13, 0.12, 0.13)
|
|
_m_trim.roughness = 0.7
|
|
|
|
_m_part = StandardMaterial3D.new()
|
|
_m_part.albedo_color = Color(0.44, 0.44, 0.40) # cubicle partition fabric
|
|
_m_part.roughness = 1.0
|
|
|
|
_m_wood = StandardMaterial3D.new()
|
|
_m_wood.albedo_color = Color(0.42, 0.29, 0.17)
|
|
_m_wood.roughness = 0.55
|
|
|
|
_m_glass = StandardMaterial3D.new()
|
|
_m_glass.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
_m_glass.albedo_color = Color(0.66, 0.74, 0.78, 0.15)
|
|
_m_glass.roughness = 0.05
|
|
_m_glass.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
|
|
# ---------------------------------------------------------------- primitives
|
|
func _slab(size: Vector3, centre: Vector3, mat: Material, collide := true,
|
|
shadow := true) -> StaticBody3D:
|
|
var body := StaticBody3D.new()
|
|
var mi := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = size
|
|
mi.mesh = bm
|
|
mi.material_override = mat
|
|
if not shadow:
|
|
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
body.add_child(mi)
|
|
if collide:
|
|
var col := CollisionShape3D.new()
|
|
var sh := BoxShape3D.new()
|
|
sh.size = size
|
|
col.shape = sh
|
|
body.add_child(col)
|
|
add_child(body)
|
|
body.position = centre
|
|
return body
|
|
|
|
## A wall running along X (or Z) with a doorway punched in it.
|
|
func _wall_x(z: float, x0: float, x1: float, mat: Material, door_at := INF) -> void:
|
|
if door_at == INF:
|
|
_slab(Vector3(x1 - x0, H, WALL_T), Vector3((x0 + x1) * 0.5, H * 0.5, z), mat)
|
|
return
|
|
var d0 := door_at - DOOR_W * 0.5
|
|
var d1 := door_at + DOOR_W * 0.5
|
|
if d0 > x0:
|
|
_slab(Vector3(d0 - x0, H, WALL_T), Vector3((x0 + d0) * 0.5, H * 0.5, z), mat)
|
|
if x1 > d1:
|
|
_slab(Vector3(x1 - d1, H, WALL_T), Vector3((d1 + x1) * 0.5, H * 0.5, z), mat)
|
|
_slab(Vector3(DOOR_W, H - 2.05, WALL_T), Vector3(door_at, (H + 2.05) * 0.5, z), mat)
|
|
|
|
func _wall_z(x: float, z0: float, z1: float, mat: Material, door_at := INF) -> void:
|
|
if door_at == INF:
|
|
_slab(Vector3(WALL_T, H, z1 - z0), Vector3(x, H * 0.5, (z0 + z1) * 0.5), mat)
|
|
return
|
|
var d0 := door_at - DOOR_W * 0.5
|
|
var d1 := door_at + DOOR_W * 0.5
|
|
if d0 > z0:
|
|
_slab(Vector3(WALL_T, H, d0 - z0), Vector3(x, H * 0.5, (z0 + d0) * 0.5), mat)
|
|
if z1 > d1:
|
|
_slab(Vector3(WALL_T, H, z1 - d1), Vector3(x, H * 0.5, (d1 + z1) * 0.5), mat)
|
|
_slab(Vector3(WALL_T, H - 2.05, DOOR_W), Vector3(x, (H + 2.05) * 0.5, door_at), mat)
|
|
|
|
## Static set dressing from a GLB, with a box collider sized to its own mesh so you can't
|
|
## walk through the vending machine.
|
|
func _glb(path: String, pos: Vector3, yaw := 0.0) -> Node3D:
|
|
if not ResourceLoader.exists(path):
|
|
return null
|
|
var packed := load(path) as PackedScene
|
|
if packed == null:
|
|
return null
|
|
var body := StaticBody3D.new()
|
|
add_child(body)
|
|
body.position = pos
|
|
body.rotation.y = yaw
|
|
var n: Node3D = packed.instantiate()
|
|
body.add_child(n)
|
|
var ab := _mesh_aabb(n)
|
|
if ab.size != Vector3.ZERO:
|
|
var col := CollisionShape3D.new()
|
|
var sh := BoxShape3D.new()
|
|
sh.size = ab.size
|
|
col.shape = sh
|
|
col.position = ab.get_center()
|
|
body.add_child(col)
|
|
return body
|
|
|
|
func _mesh_aabb(root: Node3D) -> AABB:
|
|
var acc := AABB()
|
|
var started := false
|
|
for m in _meshes(root):
|
|
var mi := m as MeshInstance3D
|
|
var a := mi.get_aabb()
|
|
a = mi.transform * a
|
|
if not started:
|
|
acc = a
|
|
started = true
|
|
else:
|
|
acc = acc.merge(a)
|
|
return acc
|
|
|
|
func _meshes(n: Node, acc: Array = []) -> Array:
|
|
if n is MeshInstance3D:
|
|
acc.append(n)
|
|
for c in n.get_children():
|
|
_meshes(c, acc)
|
|
return acc
|
|
|
|
## Chairs are NOT placed here. They're the most satisfying thing in an office to send
|
|
## across the room, so they're spawned by Main as smashable rigid bodies; Office only
|
|
## says where they start.
|
|
func _chair(pos: Vector3, yaw: float) -> void:
|
|
_spots_chair.append(Transform3D(Basis(Vector3.UP, yaw), pos))
|
|
|
|
# ---------------------------------------------------------------- shell
|
|
func _shell() -> void:
|
|
var w := X_MAX - X_MIN
|
|
var d := Z_FRONT - Z_BACK
|
|
var cx := (X_MIN + X_MAX) * 0.5
|
|
var cz := (Z_BACK + Z_FRONT) * 0.5
|
|
_slab(Vector3(w + 1.0, 0.30, d + 1.0), Vector3(cx, -0.15, cz), _m_carpet)
|
|
# outer walls; the +X wall is the window wall, so it's built in _windows()
|
|
_wall_x(Z_BACK - WALL_T * 0.5, X_MIN, X_MAX, _m_wall)
|
|
_wall_x(Z_FRONT + WALL_T * 0.5, X_MIN, X_MAX, _m_wall, -8.6) # entrance
|
|
_wall_z(X_MIN - WALL_T * 0.5, Z_BACK, Z_FRONT, _m_wall)
|
|
_windows()
|
|
|
|
## The window wall. Dunder Mifflin's bullpen has that whole run of blinded windows down
|
|
## one side, and it's what stops an interior reading as a sealed box.
|
|
func _windows() -> void:
|
|
var x := X_MAX + WALL_T * 0.5
|
|
var y0 := 0.85
|
|
var y1 := 2.35
|
|
_slab(Vector3(WALL_T, y0, Z_FRONT - Z_BACK), Vector3(x, y0 * 0.5,
|
|
(Z_BACK + Z_FRONT) * 0.5), _m_wall)
|
|
_slab(Vector3(WALL_T, H - y1, Z_FRONT - Z_BACK), Vector3(x, (H + y1) * 0.5,
|
|
(Z_BACK + Z_FRONT) * 0.5), _m_wall)
|
|
# mullions every 2.4 m, glazing between
|
|
var z := Z_BACK
|
|
while z < Z_FRONT - 0.1:
|
|
var z1: float = minf(z + 2.4, Z_FRONT)
|
|
_slab(Vector3(0.05, y1 - y0, z1 - z - 0.12), Vector3(X_MAX - 0.02,
|
|
(y0 + y1) * 0.5, (z + z1) * 0.5), _m_glass, false, false)
|
|
_slab(Vector3(WALL_T, y1 - y0, 0.12), Vector3(x, (y0 + y1) * 0.5, z1), _m_trim)
|
|
# venetian blind, half-raised
|
|
_slab(Vector3(0.06, 0.55, z1 - z - 0.14), Vector3(X_MAX - 0.10, y1 - 0.28,
|
|
(z + z1) * 0.5), _m_ceil, false, false)
|
|
z = z1
|
|
|
|
func _ceiling_grid() -> void:
|
|
var w := X_MAX - X_MIN
|
|
var d := Z_FRONT - Z_BACK
|
|
var cx := (X_MIN + X_MAX) * 0.5
|
|
var cz := (Z_BACK + Z_FRONT) * 0.5
|
|
_slab(Vector3(w, 0.16, d), Vector3(cx, H + 0.08, cz), _m_ceil, true, false)
|
|
# suspended-tile T-bar, purely visual — it's the texture of an office ceiling
|
|
var gx := X_MIN
|
|
while gx <= X_MAX + 0.01:
|
|
_slab(Vector3(0.05, 0.03, d), Vector3(gx, H - 0.02, cz), _m_trim, false, false)
|
|
gx += 1.22
|
|
var gz := Z_BACK
|
|
while gz <= Z_FRONT + 0.01:
|
|
_slab(Vector3(w, 0.03, 0.05), Vector3(cx, H - 0.02, gz), _m_trim, false, false)
|
|
gz += 1.22
|
|
|
|
# ---------------------------------------------------------------- interior walls
|
|
func _interior_walls() -> void:
|
|
# manager's office — half solid, half glass so you can be watched through it
|
|
_wall_x(MGR_Z, X_MIN, MGR_X, _m_wall, -9.2)
|
|
_slab(Vector3(WALL_T, 1.0, MGR_Z - Z_BACK), Vector3(MGR_X, 0.5,
|
|
(Z_BACK + MGR_Z) * 0.5), _m_wall)
|
|
_slab(Vector3(0.06, H - 1.05, MGR_Z - Z_BACK), Vector3(MGR_X, (H + 1.0) * 0.5,
|
|
(Z_BACK + MGR_Z) * 0.5), _m_glass, true, false)
|
|
|
|
# conference room
|
|
_wall_x(CONF_Z, CONF_X0, CONF_X1, _m_wall, -3.4)
|
|
_wall_z(CONF_X1, Z_BACK, CONF_Z, _m_wall)
|
|
|
|
# break room
|
|
_wall_z(BREAK_X, BREAK_Z, Z_FRONT, _m_wall, 2.2)
|
|
_wall_x(BREAK_Z, BREAK_X, X_MAX, _m_wall)
|
|
|
|
# warehouse roller door on the back wall
|
|
_slab(Vector3(2.6, 2.4, 0.12), Vector3(7.8, 1.2, Z_BACK + 0.06), _m_trim)
|
|
for i in range(9):
|
|
_slab(Vector3(2.5, 0.06, 0.05), Vector3(7.8, 0.18 + i * 0.26,
|
|
Z_BACK + 0.14), _m_ceil, false, false)
|
|
|
|
# ---------------------------------------------------------------- lighting
|
|
## Recessed fluorescent troffers in the ceiling grid. These ARE the light: the old scene
|
|
## lit everything with one outdoor directional lamp, which is exactly why it read as
|
|
## "props on a plane" rather than "inside somewhere".
|
|
func _lights_rig() -> void:
|
|
var panel := StandardMaterial3D.new()
|
|
panel.albedo_color = Color(0.97, 0.97, 0.93)
|
|
panel.emission_enabled = true
|
|
panel.emission = Color(1.0, 0.98, 0.92)
|
|
panel.emission_energy_multiplier = 1.5
|
|
|
|
var xs := [-8.5, -4.6, -0.7, 3.2, 7.1]
|
|
var zs := [-6.2, -2.6, 1.0, 4.6]
|
|
for xc in xs:
|
|
for zc in zs:
|
|
var mi := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = Vector3(1.14, 0.05, 0.58)
|
|
mi.mesh = bm
|
|
mi.material_override = panel
|
|
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
add_child(mi)
|
|
mi.position = Vector3(xc, H - 0.06, zc)
|
|
|
|
var l := OmniLight3D.new()
|
|
l.light_color = Color(1.0, 0.98, 0.93)
|
|
l.light_energy = 1.9
|
|
l.omni_range = 6.5
|
|
l.omni_attenuation = 1.2
|
|
l.shadow_enabled = false # 20 shadow-casting omnis is not worth the cost
|
|
add_child(l)
|
|
l.position = Vector3(xc, H - 0.20, zc)
|
|
|
|
# two shadow-casters so props still sit in contact shadows
|
|
for zc in [-2.0, 3.0]:
|
|
var key := OmniLight3D.new()
|
|
key.light_color = Color(1.0, 0.97, 0.92)
|
|
key.light_energy = 1.5
|
|
key.omni_range = 13.0
|
|
key.shadow_enabled = true
|
|
add_child(key)
|
|
key.position = Vector3(-1.0, H - 0.30, zc)
|
|
|
|
# daylight through the window wall
|
|
var sun := DirectionalLight3D.new()
|
|
sun.light_color = Color(0.82, 0.88, 1.0)
|
|
sun.light_energy = 0.7
|
|
sun.rotation_degrees = Vector3(-30, -104, 0)
|
|
sun.shadow_enabled = true
|
|
add_child(sun)
|
|
|
|
func configure_environment(env: Environment) -> void:
|
|
env.background_mode = Environment.BG_COLOR
|
|
env.background_color = Color(0.55, 0.62, 0.72) # daylight past the glass
|
|
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
|
env.ambient_light_color = Color(0.50, 0.50, 0.50)
|
|
env.ambient_light_energy = 0.5
|
|
env.ssao_enabled = true
|
|
env.ssao_radius = 0.8
|
|
env.ssao_intensity = 1.5
|
|
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
|
env.tonemap_exposure = 1.0
|
|
|
|
# ---------------------------------------------------------------- bullpen
|
|
## Four pairs of desks facing each other across a low partition — the Jim-and-Dwight
|
|
## arrangement, which is the single most recognisable thing about the floor plan.
|
|
## Rows are 4.8 m apart, not 3.6. At 3.6 the two rows' chairs met back-to-back in the
|
|
## middle of the aisle with 3 cm to spare, which Jolt resolved by firing them out of the
|
|
## building — and it left no room to walk between the rows either.
|
|
const CLUSTERS := [
|
|
Vector2(-4.6, -2.0), Vector2(-4.6, 2.8),
|
|
Vector2(-0.4, -2.0), Vector2(-0.4, 2.8),
|
|
]
|
|
|
|
func _bullpen() -> void:
|
|
for c in CLUSTERS:
|
|
_partition(Vector3(c.x, 0.0, c.y), 1.9)
|
|
for s in [-1.0, 1.0]:
|
|
var t := Transform3D(Basis(Vector3.UP, 0.0 if s > 0.0 else PI),
|
|
Vector3(c.x, 0.0, c.y + s * 0.78))
|
|
_spots_desk.append(t)
|
|
# NOTE: the bullpen chairs are NOT listed here. Their offset depends on how
|
|
# deep office-desk.glb actually is, which only Main knows once it has measured
|
|
# the mesh — guessing a constant here put chairs inside desks.
|
|
# the copier alcove, between the bullpen and the break room
|
|
_slab(Vector3(WALL_T, H, 2.6), Vector3(3.6, H * 0.5, -0.3), _m_wall)
|
|
_spot_copier = Transform3D(Basis(Vector3.UP, deg_to_rad(-90.0)),
|
|
Vector3(3.0, 0.0, -0.3))
|
|
|
|
## A cubicle divider: fabric panel on a dark base rail.
|
|
func _partition(at: Vector3, length: float) -> void:
|
|
_slab(Vector3(length, 1.18, 0.07), at + Vector3(0, 0.62, 0), _m_part)
|
|
_slab(Vector3(length + 0.06, 0.09, 0.11), at + Vector3(0, 1.24, 0), _m_trim)
|
|
for s in [-1.0, 1.0]:
|
|
_slab(Vector3(0.09, 1.24, 0.30), at + Vector3(s * length * 0.5, 0.62, 0), _m_trim)
|
|
|
|
func desk_spots() -> Array[Transform3D]:
|
|
return _spots_desk
|
|
|
|
func chair_spots() -> Array[Transform3D]:
|
|
return _spots_chair
|
|
|
|
func copier_spot() -> Transform3D:
|
|
return _spot_copier
|
|
|
|
# ---------------------------------------------------------------- rooms
|
|
func _reception() -> void:
|
|
# the counter you meet on the way in, with the waiting-area chairs
|
|
_slab(Vector3(0.70, 1.10, 3.0), Vector3(-9.9, 0.55, RECEP_Z + 1.0), _m_wood)
|
|
_slab(Vector3(0.92, 0.08, 3.2), Vector3(-9.9, 1.14, RECEP_Z + 1.0), _m_trim)
|
|
_slab(Vector3(1.8, 1.10, 0.70), Vector3(-9.6, 0.55, RECEP_Z - 0.15), _m_wood)
|
|
_wall_z(-6.9, RECEP_Z, Z_FRONT, _m_wall, 6.8)
|
|
for i in range(3):
|
|
_chair(Vector3(-7.6, 0.0, 5.5 + i * 0.85), deg_to_rad(-90))
|
|
_glb(P + "office-plant.glb", Vector3(-6.4, 0.0, 6.6))
|
|
|
|
func _conference() -> void:
|
|
var cx := (CONF_X0 + CONF_X1) * 0.5
|
|
var cz := (Z_BACK + CONF_Z) * 0.5
|
|
# the long table everyone dreads
|
|
_slab(Vector3(3.9, 0.09, 1.35), Vector3(cx, 0.74, cz), _m_wood)
|
|
for s in [-1.0, 1.0]:
|
|
_slab(Vector3(0.16, 0.72, 0.90), Vector3(cx + s * 1.6, 0.36, cz), _m_trim)
|
|
for i in range(4):
|
|
var x := cx - 1.35 + i * 0.9
|
|
_chair(Vector3(x, 0.0, cz - 1.15), 0.0)
|
|
_chair(Vector3(x, 0.0, cz + 1.15), PI)
|
|
# whiteboard
|
|
_slab(Vector3(2.4, 1.15, 0.05), Vector3(cx, 1.6, Z_BACK + 0.10), _m_ceil)
|
|
_slab(Vector3(2.5, 1.25, 0.03), Vector3(cx, 1.6, Z_BACK + 0.07), _m_trim)
|
|
|
|
func _manager() -> void:
|
|
var cx := (X_MIN + MGR_X) * 0.5
|
|
var cz := (Z_BACK + MGR_Z) * 0.5
|
|
_chair(Vector3(cx, 0.0, cz - 0.9), 0.0)
|
|
_chair(Vector3(cx - 0.8, 0.0, cz + 1.1), PI)
|
|
_glb(P + "office-plant.glb", Vector3(X_MIN + 0.7, 0.0, MGR_Z - 0.8))
|
|
|
|
func _break_room() -> void:
|
|
var cz := (BREAK_Z + Z_FRONT) * 0.5
|
|
# kitchen counter along the window wall
|
|
_slab(Vector3(0.62, 0.88, 3.4), Vector3(X_MAX - 0.42, 0.44, cz + 0.6), _m_wood)
|
|
_slab(Vector3(0.70, 0.06, 3.5), Vector3(X_MAX - 0.42, 0.90, cz + 0.6), _m_trim)
|
|
_glb(P + "microwave.glb", Vector3(X_MAX - 0.45, 0.93, cz + 1.7), deg_to_rad(-90))
|
|
_glb(P + "vending-machine.glb", Vector3(BREAK_X + 0.9, 0.0, Z_FRONT - 0.6), PI)
|
|
_glb(P + "vending-machine.glb", Vector3(BREAK_X + 1.9, 0.0, Z_FRONT - 0.6), PI)
|
|
# two round tables with chairs
|
|
for i in range(2):
|
|
var tx := 7.4
|
|
var tz := BREAK_Z + 1.6 + i * 2.6
|
|
_slab(Vector3(1.05, 0.07, 1.05), Vector3(tx, 0.73, tz), _m_ceil)
|
|
_slab(Vector3(0.12, 0.70, 0.12), Vector3(tx, 0.36, tz), _m_trim)
|
|
_slab(Vector3(0.62, 0.05, 0.62), Vector3(tx, 0.03, tz), _m_trim)
|
|
for a in range(3):
|
|
var ang := deg_to_rad(a * 120.0 + 40.0)
|
|
_chair(Vector3(tx + sin(ang) * 0.95, 0.0, tz + cos(ang) * 0.95), ang + PI)
|
|
|
|
func _dressing() -> void:
|
|
_glb(P + "office-plant.glb", Vector3(2.9, 0.0, 6.4))
|
|
_glb(P + "office-plant.glb", Vector3(-0.9, 0.0, -3.6))
|
|
# wall clock, because every office has one you stare at
|
|
_slab(Vector3(0.42, 0.42, 0.05), Vector3(-3.4, 2.15, Z_FRONT - 0.02), _m_ceil)
|
|
_slab(Vector3(0.48, 0.48, 0.03), Vector3(-3.4, 2.15, Z_FRONT - 0.01), _m_trim)
|
|
|
|
## Where the player should start: just inside the entrance, looking into the bullpen.
|
|
func spawn_point() -> Vector3:
|
|
return Vector3(-8.6, 0.0, 7.2)
|
|
|
|
func spawn_yaw() -> float:
|
|
return deg_to_rad(196.0)
|