The game is now called Shitbox Infinity (project name, window title, title screen, README). The Gitea repo keeps its name until John renames it -- gitea redirects, and the remote gets a set-url then. The Shed (levels/the_shed) is the payoff for the whole garage arc: a drivable suburban block on a quiet cul-de-sac. Single-storey brick house with a corrugated roof and a porch light that works at night; paling fence (new FLUX texture) with a gate gap for the driveway; hills hoist mid-spin with three towels on it; wheelie bins at the gate and a spare fleet shitbox parked on the street; gums; neighbours across the road so you feel watched. The shed itself is a 10x12 corrugated double garage on a slab: FLUX pegboard wall, workbench, fluoro batten (a real light at night), and a painted bay. The tools you OWN (Garage.tools()) stand in it as the real TRELLIS'd cashies models -- sockets and the code reader ON the bench, the engine crane by the wall, ramps at the bay mouth -- while unowned tools leave an oil-stained outline of where they'll go. The rest of the tool library is permanent clutter via the new shared ToolProps helper (dragway.gd refactored onto it too). Roll into the bay in Cruise, hold still 1.4 s, and game.gd opens the garage -- diegetic shopping. Headless suites have no current_scene to swap, so the trigger sets garage_requested first and the smoke suite asserts on that: owned-tool display (sockets present, ramps absent on the test save), bay marker found, bay trigger fires. Suite covers 9 levels now. Scale lessons baked in: facade_brick at 0.14 tiling reads as a three-storey office (0.045 reads domestic), and corrugated iron at 0.35 has metre-wide ridges (1.4 reads like roofing). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
275 lines
10 KiB
GDScript
275 lines
10 KiB
GDScript
extends Node3D
|
|
## Willowbank-ish quarter mile, built in code like the carpark. A real 402 m of
|
|
## strip plus shutdown, sand trap and tyre wall, with a working christmas tree
|
|
## (game.gd drives the bulbs via their material_override emission).
|
|
##
|
|
## Geometry contract with game.gd's drag mode: DragStart sits at (0,0,0) in the
|
|
## .tscn and DragEnd at (0,0,-402); the player spawns at x=-2, the rival at
|
|
## x=+2, both facing -Z. The tree stands between the lanes at z=-6, visual only
|
|
## -- collision there would turn a wandering launch into a head-on with a pole.
|
|
|
|
const TEX := "res://assets/textures/%s.jpg"
|
|
|
|
const STRIP_HALF := 12.0 # wall-to-wall half width
|
|
const FINISH := -402.0
|
|
const SHUTDOWN_END := -640.0
|
|
|
|
## Cashies tool props live in src/tool_props.gd (shared with The Shed).
|
|
|
|
func _ready() -> void:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = 1320 # a respectable ET
|
|
|
|
_slab(Vector3(0, -0.5, -280), Vector3(420, 1, 900), _mat("grass", 14.0))
|
|
# the strip: asphalt base, concrete launch pad, rubbered lanes down-track
|
|
_slab(Vector3(0, -0.45, -290), Vector3(STRIP_HALF * 2, 1, 740), _mat("asphalt", 9.0))
|
|
_slab(Vector3(0, 0.065, -40), Vector3(STRIP_HALF * 2, 0.03, 80), _mat("concrete_ground", 4.0), false)
|
|
for lx in [-4.0, 4.0]:
|
|
_slab(Vector3(lx, 0.062, -320), Vector3(7.5, 0.02, 480), _mat("dragstrip", 5.0), false)
|
|
# centre line (double yellow) and edge lines
|
|
var yellow := _flat(Color(0.85, 0.72, 0.18), 0.7)
|
|
var white := _flat(Color(0.88, 0.88, 0.84), 0.7)
|
|
for cx in [-0.18, 0.18]:
|
|
_slab(Vector3(cx, 0.07, -305), Vector3(0.12, 0.02, 670), yellow, false)
|
|
for ex in [-10.8, 10.8]:
|
|
_slab(Vector3(ex, 0.07, -305), Vector3(0.14, 0.02, 670), white, false)
|
|
|
|
# concrete walls with alternating red/white caps, like every strip on earth
|
|
var conc := _mat("concrete_ground", 3.0)
|
|
for sx in [-1.0, 1.0]:
|
|
_slab(Vector3(sx * STRIP_HALF, 0.475, -315), Vector3(0.5, 0.95, 690), conc)
|
|
for i in 40:
|
|
var z := 20.0 - i * 17.0
|
|
_slab(Vector3(sx * STRIP_HALF, 0.99, z), Vector3(0.54, 0.08, 8.0),
|
|
_flat(Color(0.78, 0.12, 0.10) if i % 2 == 0 else Color(0.9, 0.9, 0.88), 0.6), false)
|
|
|
|
_tree()
|
|
_start_line()
|
|
_finish_line()
|
|
_boards()
|
|
_grandstand()
|
|
_tower()
|
|
_sand_trap()
|
|
|
|
# return road up the right-hand side, back to the staging paddock
|
|
_slab(Vector3(19.0, 0.04, -290), Vector3(8, 0.02, 700), _mat("asphalt", 9.0), false)
|
|
|
|
# staging paddock: PP_ markers become shuntable parked fleet cars (game.gd)
|
|
for i in 8:
|
|
var m := Marker3D.new()
|
|
m.name = "PP_pad%d" % i
|
|
m.position = Vector3(20.0 + 5.5 * (i % 4), 0.0, 22.0 + 8.0 * (i / 4))
|
|
m.rotate_y(PI * 0.5 + rng.randf_range(-0.08, 0.08))
|
|
add_child(m)
|
|
# lighting towers as SL_ markers: game.gd builds them as smashable street
|
|
# lights that really cast light at night. Yes, you can flatten them.
|
|
var sl := 0
|
|
for z in [-80.0, -220.0, -360.0, -500.0]:
|
|
for sx in [-1.0, 1.0]:
|
|
var m := Marker3D.new()
|
|
m.name = "SL_%d" % sl
|
|
sl += 1
|
|
m.position = Vector3(sx * (STRIP_HALF + 2.0), 0.0, z)
|
|
add_child(m)
|
|
# wheelie bins behind the wall because there is always a row of wheelie bins
|
|
for i in 6:
|
|
var m := Marker3D.new()
|
|
m.name = "WB_%d" % i
|
|
m.position = Vector3(14.5, 0.0, 8.0 - i * 3.0)
|
|
add_child(m)
|
|
|
|
_gums(rng)
|
|
_paddock_tools(rng)
|
|
_race_path()
|
|
|
|
func _mat(tex: String, scale: float, rough := 0.9) -> StandardMaterial3D:
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_texture = load(TEX % tex)
|
|
m.uv1_scale = Vector3(scale, scale, scale)
|
|
m.uv1_triplanar = true
|
|
m.roughness = rough
|
|
m.metallic = 0.0
|
|
return m
|
|
|
|
func _flat(color: Color, rough := 0.85) -> StandardMaterial3D:
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_color = color
|
|
m.roughness = rough
|
|
m.metallic = 0.0
|
|
return m
|
|
|
|
func _slab(pos: Vector3, size: Vector3, mat: Material, collide := true) -> void:
|
|
var body: PhysicsBody3D = StaticBody3D.new()
|
|
var mi := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = size
|
|
bm.material = mat
|
|
mi.mesh = bm
|
|
body.add_child(mi)
|
|
if collide:
|
|
var shape := CollisionShape3D.new()
|
|
var bs := BoxShape3D.new()
|
|
bs.size = size
|
|
shape.shape = bs
|
|
body.add_child(shape)
|
|
body.position = pos
|
|
add_child(body)
|
|
|
|
func _tree() -> void:
|
|
## The christmas tree: pole + backboard + six named bulbs. game.gd finds
|
|
## them by name and flips material_override.emission_enabled. Visual only.
|
|
var steel := _flat(Color(0.22, 0.23, 0.25), 0.5)
|
|
_slab(Vector3(0, 1.1, -6), Vector3(0.14, 2.2, 0.14), steel, false)
|
|
_slab(Vector3(0, 2.0, -6), Vector3(0.55, 1.75, 0.10), _flat(Color(0.10, 0.10, 0.12), 0.6), false)
|
|
var bulbs := [
|
|
["TreeStage", 2.68, Color(0.9, 0.9, 0.8)],
|
|
["TreeAmber1", 2.38, Color(1.0, 0.68, 0.05)],
|
|
["TreeAmber2", 2.12, Color(1.0, 0.68, 0.05)],
|
|
["TreeAmber3", 1.86, Color(1.0, 0.68, 0.05)],
|
|
["TreeGreen", 1.58, Color(0.15, 0.95, 0.25)],
|
|
["TreeRed", 1.30, Color(0.95, 0.10, 0.08)],
|
|
]
|
|
for b in bulbs:
|
|
var mi := MeshInstance3D.new()
|
|
var sm := SphereMesh.new()
|
|
sm.radius = 0.105
|
|
sm.height = 0.21
|
|
sm.radial_segments = 12
|
|
sm.rings = 6
|
|
mi.mesh = sm
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_color = Color(b[2]) * 0.35 # dim housing until it fires
|
|
m.roughness = 0.4
|
|
m.emission_enabled = false
|
|
m.emission = Color(b[2])
|
|
m.emission_energy_multiplier = 3.5
|
|
mi.material_override = m
|
|
mi.name = String(b[0])
|
|
mi.position = Vector3(0, float(b[1]), -6.06)
|
|
add_child(mi)
|
|
|
|
func _start_line() -> void:
|
|
var white := _flat(Color(0.9, 0.9, 0.86), 0.7)
|
|
_slab(Vector3(0, 0.07, 0), Vector3(STRIP_HALF * 2 - 1.6, 0.02, 0.4), white, false)
|
|
# burnout box water patches behind the line
|
|
for lx in [-4.0, 4.0]:
|
|
_slab(Vector3(lx, 0.068, 8.0), Vector3(5.0, 0.015, 9.0),
|
|
_flat(Color(0.10, 0.11, 0.13), 0.15), false)
|
|
|
|
func _finish_line() -> void:
|
|
# checkered strip + scoreboard gantry
|
|
for row in 2:
|
|
for i in 11:
|
|
var c := Color(0.05, 0.05, 0.06) if (i + row) % 2 == 0 else Color(0.92, 0.92, 0.9)
|
|
_slab(Vector3(-10.0 + i * 2.0, 0.07, FINISH - row * 2.0),
|
|
Vector3(2.0, 0.02, 2.0), _flat(c, 0.7), false)
|
|
var steel := _flat(Color(0.25, 0.26, 0.28), 0.5)
|
|
for sx in [-1.0, 1.0]:
|
|
_slab(Vector3(sx * (STRIP_HALF + 0.8), 3.5, FINISH), Vector3(0.35, 7.0, 0.35), steel)
|
|
_slab(Vector3(0, 7.1, FINISH), Vector3(STRIP_HALF * 2 + 2.5, 0.45, 0.45), steel, false)
|
|
for sx in [-5.0, 5.0]:
|
|
_slab(Vector3(sx, 5.9, FINISH), Vector3(4.5, 2.0, 0.3), _mat("billboard_ad3", 0.22, 0.4), false)
|
|
|
|
func _boards() -> void:
|
|
# distance boards: 60ft, eighth, 1000ft -- the numbers that matter
|
|
var posts := _flat(Color(0.85, 0.85, 0.8), 0.6)
|
|
for z in [-18.3, -201.2, -305.0]:
|
|
for sx in [-1.0, 1.0]:
|
|
_slab(Vector3(sx * (STRIP_HALF + 1.2), 1.0, z), Vector3(0.16, 2.0, 0.16), posts, false)
|
|
_slab(Vector3(sx * (STRIP_HALF + 1.2), 2.2, z), Vector3(1.3, 0.9, 0.1), posts, false)
|
|
|
|
func _grandstand() -> void:
|
|
## Six stepped tiers with FLUX crowd panels on the risers and a white canopy.
|
|
var conc := _mat("concrete_ground", 3.0)
|
|
for i in 6:
|
|
var x := -16.5 - i * 2.6
|
|
var y := 0.55 + i * 0.85
|
|
_slab(Vector3(x, y * 0.5, -80), Vector3(2.6, y, 120), conc)
|
|
_slab(Vector3(x + 1.28, y + 0.75, -80), Vector3(0.08, 1.5, 118), _mat("crowd", 7.0, 0.9), false)
|
|
var steel := _flat(Color(0.3, 0.31, 0.33), 0.5)
|
|
for zi in 5:
|
|
_slab(Vector3(-24.0, 3.8, -128 + zi * 24.0), Vector3(0.3, 7.6, 0.3), steel, false)
|
|
_slab(Vector3(-25.5, 7.6, -80), Vector3(20.0, 0.25, 124), _mat("panel_white", 0.5, 0.5), false)
|
|
|
|
func _tower() -> void:
|
|
## The red control tower: every Queensland circuit building is this shade.
|
|
var red := _flat(Color(0.72, 0.10, 0.08), 0.55)
|
|
var glass := _mat("facade_glass", 0.2, 0.25)
|
|
_slab(Vector3(21.0, 6.0, -14), Vector3(9.0, 12.0, 8.0), red)
|
|
_slab(Vector3(21.0, 10.4, -9.95), Vector3(8.2, 2.6, 0.2), glass, false)
|
|
_slab(Vector3(21.0, 12.3, -14), Vector3(9.6, 0.4, 8.6), _flat(Color(0.16, 0.17, 0.19), 0.6), false)
|
|
_slab(Vector3(21.0, 7.4, -9.9), Vector3(8.2, 1.6, 0.15), _mat("billboard_ad", 0.3, 0.4), false)
|
|
# paddock awnings behind it
|
|
for i in 3:
|
|
_slab(Vector3(30.0, 2.9, -30.0 - i * 14.0), Vector3(7.0, 0.2, 10.0), _mat("panel_white", 0.5, 0.5), false)
|
|
for sx in [-3.0, 3.0]:
|
|
for sz in [-4.5, 4.5]:
|
|
_slab(Vector3(30.0 + sx, 1.45, -30.0 - i * 14.0 + sz), Vector3(0.15, 2.9, 0.15),
|
|
_flat(Color(0.5, 0.5, 0.52), 0.5), false)
|
|
|
|
func _sand_trap() -> void:
|
|
_slab(Vector3(0, 0.055, -668), Vector3(STRIP_HALF * 2, 0.03, 56), _mat("sand", 5.0), false)
|
|
# tyre wall: fat black cylinders in a row, then an honest collision slab
|
|
var rubber := _flat(Color(0.07, 0.07, 0.08), 0.85)
|
|
for i in 17:
|
|
var mi := MeshInstance3D.new()
|
|
var cm := CylinderMesh.new()
|
|
cm.top_radius = 0.62
|
|
cm.bottom_radius = 0.62
|
|
cm.height = 1.15
|
|
cm.radial_segments = 10
|
|
cm.material = rubber
|
|
mi.mesh = cm
|
|
mi.position = Vector3(-11.2 + i * 1.4, 0.58, -694)
|
|
add_child(mi)
|
|
_slab(Vector3(0, 1.0, -695.2), Vector3(STRIP_HALF * 2 + 2, 2.0, 1.0), rubber)
|
|
|
|
func _gums(rng: RandomNumberGenerator) -> void:
|
|
for i in 26:
|
|
var sx := -1.0 if i % 2 == 0 else 1.0
|
|
var at := Vector3(sx * rng.randf_range(30.0, 80.0), 0, rng.randf_range(-620.0, 40.0))
|
|
var trunk := MeshInstance3D.new()
|
|
var cm := CylinderMesh.new()
|
|
cm.top_radius = 0.14
|
|
cm.bottom_radius = 0.24
|
|
cm.height = rng.randf_range(5.0, 8.0)
|
|
cm.radial_segments = 7
|
|
cm.material = _mat("bark", 1.4)
|
|
trunk.mesh = cm
|
|
trunk.position = at + Vector3(0, cm.height * 0.5, 0)
|
|
add_child(trunk)
|
|
var crown := MeshInstance3D.new()
|
|
var sm := SphereMesh.new()
|
|
sm.radius = rng.randf_range(2.2, 3.4)
|
|
sm.height = sm.radius * 1.6
|
|
sm.radial_segments = 10
|
|
sm.rings = 6
|
|
sm.material = _mat("foliage", 0.55)
|
|
crown.mesh = sm
|
|
crown.position = at + Vector3(0, cm.height + sm.radius * 0.4, 0)
|
|
add_child(crown)
|
|
|
|
func _paddock_tools(rng: RandomNumberGenerator) -> void:
|
|
## Strew the paddock with the TRELLIS'd Cashies tool library -- real
|
|
## secondhand clutter, every piece a shuntable RigidBody (ToolProps).
|
|
var i := 0
|
|
for n in ToolProps.library():
|
|
var spot := Vector3(24.0 + (i % 4) * 4.5 + rng.randf_range(-0.8, 0.8), 0.0,
|
|
-16.0 - float(i / 4) * 7.0 + rng.randf_range(-0.8, 0.8))
|
|
if ToolProps.spawn(self, n, spot, rng.randf() * TAU) != null:
|
|
i += 1
|
|
|
|
func _race_path() -> void:
|
|
# loop for cruise traffic: down the strip, back up the return road
|
|
var path := Path3D.new()
|
|
path.name = "RacePath"
|
|
var c := Curve3D.new()
|
|
var pts := [Vector3(0, 0, 40), Vector3(0, 0, -620), Vector3(19, 0, -620), Vector3(19, 0, 40)]
|
|
for i in pts.size() + 1:
|
|
var p: Vector3 = pts[i % pts.size()]
|
|
var to_prev: Vector3 = (pts[(i - 1 + pts.size()) % pts.size()] - p).normalized() * 8.0
|
|
var to_next: Vector3 = (pts[(i + 1) % pts.size()] - p).normalized() * 8.0
|
|
c.add_point(p, to_prev, to_next)
|
|
path.curve = c
|
|
add_child(path)
|