diff --git a/README.md b/README.md index 1ddd937..99b0c11 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Burnout Infinity: Shitbox +# Shitbox Infinity -Burnout 3-style arcade racer, Aussie shitboxes, Woolies carparks. Godot 4.6 + Blender, native Apple Silicon. +**Shitbox Infinity** (nee Burnout Infinity: Shitbox) — Burnout 3-style arcade racer, Aussie shitboxes, Woolies carparks. Godot 4.6 + Blender, native Apple Silicon. ## Run @@ -150,6 +150,15 @@ a complete toolless gearbox swap headless. Purchases are per-car (tools are global) and persist in `user://garage.save`, which also carries the drag **ladder rung**, per-car **quarter-mile PBs** and outstanding **faults**. + +**The Shed is also a place** (`levels/the_shed`): a drivable suburban home base +— brick house, paling fence, hills hoist mid-spin with the washing still on it, +wheelie bins at the gate, and the corrugated double shed on its slab. The tools +you own stand in it as the real TRELLIS'd cashies models (unowned tools leave +an oil-stained outline where they'll go), the rest of the library is permanent +clutter, and the pegboard is FLUX. Roll into the painted bay in Cruise and hold +still a beat — the garage opens. No RacePath, no traffic: the one quiet level +in the game, on purpose. Upgrades apply to a duplicate of the CarStats resource -- the .tres is shared with traffic and parked spawns of the same model, and tuning your Lazza must not secretly tune every Lazza in the city. Bolt-ons attach after the deformer diff --git a/assets/textures/paling_fence.jpg b/assets/textures/paling_fence.jpg new file mode 100644 index 0000000..6841582 Binary files /dev/null and b/assets/textures/paling_fence.jpg differ diff --git a/assets/textures/paling_fence.jpg.import b/assets/textures/paling_fence.jpg.import new file mode 100644 index 0000000..8e94611 --- /dev/null +++ b/assets/textures/paling_fence.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b67bkapeh5dad" +path="res://.godot/imported/paling_fence.jpg-413cd2729baf979db92de2cdb1d58d9c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/paling_fence.jpg" +dest_files=["res://.godot/imported/paling_fence.jpg-413cd2729baf979db92de2cdb1d58d9c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/textures/pegboard.jpg b/assets/textures/pegboard.jpg new file mode 100644 index 0000000..60e0dd4 Binary files /dev/null and b/assets/textures/pegboard.jpg differ diff --git a/assets/textures/pegboard.jpg.import b/assets/textures/pegboard.jpg.import new file mode 100644 index 0000000..dc583b7 --- /dev/null +++ b/assets/textures/pegboard.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd2vnfcb68rlc" +path="res://.godot/imported/pegboard.jpg-79f8a70d390d1050db6a7667d1c09f84.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/pegboard.jpg" +dest_files=["res://.godot/imported/pegboard.jpg-79f8a70d390d1050db6a7667d1c09f84.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/levels/the_shed/level.tscn b/levels/the_shed/level.tscn new file mode 100644 index 0000000..e139a1a --- /dev/null +++ b/levels/the_shed/level.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://levels/the_shed/shed.gd" id="1"] + +[node name="TheShed" type="Node3D"] +script = ExtResource("1") + +[node name="Spawn" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, 0.5, 30) diff --git a/levels/the_shed/shed.gd b/levels/the_shed/shed.gd new file mode 100644 index 0000000..51d809e --- /dev/null +++ b/levels/the_shed/shed.gd @@ -0,0 +1,282 @@ +extends Node3D +## THE SHED -- home base. A suburban backyard on a quiet cul-de-sac: brick +## house, paling fence, hills hoist mid-spin, and the corrugated double shed +## on its slab. The tools you actually OWN (Garage.tools()) stand in it as the +## real TRELLIS'd cashies models; unowned tools are an oil-stained outline of +## where they'll go. Roll into the marked bay and stop -- game.gd opens the +## garage. Cruise mode only ever spawns here quietly: no RacePath, no traffic, +## just you, the lawn, and the neighbours' opinion of your exhaust. + +const TEX := "res://assets/textures/%s.jpg" + +## shed-catalogue tool id -> {model, pos, yaw}. Bench-top items carry y. +const TOOL_SPOTS := { + "ramps": {"model": "car_ramps", "pos": Vector3(10.5, 0.07, -9.6), "yaw": 0.1}, + "reader": {"model": "obd_reader", "pos": Vector3(9.0, 0.99, -19.8), "yaw": 0.4}, + "sockets": {"model": "socket_set", "pos": Vector3(11.5, 0.99, -19.8), "yaw": -0.2}, + "compressor": {"model": "compressor", "pos": Vector3(8.2, 0.07, -17.5), "yaw": 1.2}, + "hoist": {"model": "engine_crane", "pos": Vector3(15.2, 0.07, -17.0), "yaw": -0.5}, + "additives": {"model": "jerry_can", "pos": Vector3(8.0, 0.07, -13.5), "yaw": 2.1}, +} +## always-there garnish: the rest of the library, scattered like a real shed +const CLUTTER := [ + ["toolbox", Vector3(16.1, 0.07, -11.5), 1.9], + ["trolley_jack", Vector3(11.0, 0.07, -15.5), 0.3], + ["jack_stands", Vector3(15.8, 0.07, -9.8), 2.6], + ["welder", Vector3(7.8, 0.07, -15.8), 1.6], + ["batt_charger", Vector3(14.0, 0.99, -19.8), -0.3], + ["angle_grinder", Vector3(16.0, 0.99, -19.7), 0.9], + ["grease_gun", Vector3(13.0, 0.07, -16.5), 4.1], + ["chain_hoist", Vector3(7.4, 0.07, -19.3), 0.6], + ["engine_stand", Vector3(19.8, 0.0, -12.0), 5.4], +] + +var hoist: Node3D + +func _ready() -> void: + var rng := RandomNumberGenerator.new() + rng.seed = 4074 # the postcode of paradise + + _slab(Vector3(-3, -0.5, 5), Vector3(120, 1, 120), _mat("grass", 10.0)) # world + _slab(Vector3(0, 0.015, 31), Vector3(88, 0.03, 14), _mat("asphalt", 8.0), false) # street + _slab(Vector3(0, 0.02, 22), Vector3(88, 0.04, 2.2), _mat("footpath", 2.0), false) # footpath + _slab(Vector3(12, 0.025, 8), Vector3(8, 0.05, 32), _mat("concrete_ground", 3.0), false) # driveway + _fence() + _house() + _shed() + _hills_hoist() + _tools() + _neighbours() + _gums(rng) + + # wheelie bins by the gate, a spare shitbox on the driveway (game.gd fills) + for i in 2: + var m := Marker3D.new() + m.name = "WB_%d" % i + m.position = Vector3(18.8, 0, 17.0 - i * 1.4) + add_child(m) + var pp := Marker3D.new() + pp.name = "PP_drive" + pp.position = Vector3(12, 0, 3) + pp.rotate_y(0.06) + add_child(pp) + +func _process(delta: float) -> void: + if hoist: + hoist.rotate_y(delta * 0.35) # there is always a bit of wind + +## ---- the yard ------------------------------------------------------------- + +func _fence() -> void: + var pal := _mat("paling_fence", 0.55, 0.9) + for seg in [ + [Vector3(-30, 0.85, -4), Vector3(0.08, 1.7, 48)], # west + [Vector3(24, 0.85, -4), Vector3(0.08, 1.7, 48)], # east + [Vector3(-3, 0.85, -28), Vector3(54, 1.7, 0.08)], # back + [Vector3(-11.5, 0.85, 20), Vector3(37, 1.7, 0.08)], # front west of gate + [Vector3(20.5, 0.85, 20), Vector3(7, 1.7, 0.08)], # front east of gate + ]: + _slab(seg[0], seg[1], pal) + +func _house() -> void: + # single-storey: brick tiled SPARSE (0.045) or the place reads as an office + var brick := _mat("facade_brick", 0.045, 0.85) + _slab(Vector3(-12, 1.9, 8), Vector3(24, 3.8, 20), brick) + _slab(Vector3(-12, 1.7, 18.1), Vector3(18, 1.2, 0.15), _mat("facade_glass", 0.25, 0.3), false) + # corrugated hip-ish roof: two tilted sheets meeting at a ridge + for s in [-1.0, 1.0]: + var roof := MeshInstance3D.new() + var bm := BoxMesh.new() + bm.size = Vector3(25.2, 0.14, 11.0) + bm.material = _mat("corrugated_iron", 1.3, 0.6) + roof.mesh = bm + roof.position = Vector3(-12, 4.9, 8 + s * 4.9) + roof.rotation_degrees = Vector3(-s * 14.0, 0, 0) + add_child(roof) + # porch: slab, posts, and a light for coming home at night + _slab(Vector3(-6, 0.1, 19.3), Vector3(8, 0.2, 2.6), _mat("concrete_ground", 2.0), false) + for px in [-9.5, -2.5]: + _slab(Vector3(px, 1.5, 20.2), Vector3(0.14, 3.0, 0.14), _flat(Color(0.85, 0.83, 0.78), 0.7), false) + _slab(Vector3(-6, 3.05, 19.9), Vector3(0.3, 0.14, 0.3), _flat(Color(0.9, 0.87, 0.6), 0.4, Color(0.9, 0.8, 0.4)), false) + if Game.time_of_day == "night": + var ol := OmniLight3D.new() + ol.light_color = Color(1.0, 0.85, 0.55) + ol.light_energy = 1.4 + ol.omni_range = 10.0 + ol.position = Vector3(-6, 2.8, 19.6) + add_child(ol) + +func _shed() -> void: + ## Double garage, not a warehouse: 10 x 12 m, open front, slab, pegboard. + var iron := _mat("corrugated_iron", 1.4, 0.6) + _slab(Vector3(12, 0.035, -15), Vector3(10.6, 0.07, 12.6), _mat("concrete_ground", 2.5)) + _slab(Vector3(7, 1.7, -15), Vector3(0.1, 3.4, 12.6), iron) # west wall + _slab(Vector3(17, 1.7, -15), Vector3(0.1, 3.4, 12.6), iron) # east wall + _slab(Vector3(12, 1.85, -21.2), Vector3(10.2, 3.7, 0.1), iron) # back wall + _slab(Vector3(12, 2.55, -20.9), Vector3(9.6, 1.9, 0.06), _mat("pegboard", 0.4, 0.9), false) + # workbench along the back + _slab(Vector3(11.5, 0.45, -19.9), Vector3(8.8, 0.9, 1.2), _flat(Color(0.32, 0.26, 0.2), 0.8)) + _slab(Vector3(11.5, 0.93, -19.9), Vector3(9.0, 0.08, 1.35), _flat(Color(0.55, 0.48, 0.38), 0.6), false) + # skillion roof, high at the front so the ute fits + var roof := MeshInstance3D.new() + var bm := BoxMesh.new() + bm.size = Vector3(11.4, 0.12, 13.6) + bm.material = iron + roof.mesh = bm + roof.position = Vector3(12, 3.9, -15) + roof.rotation_degrees = Vector3(-3.2, 0, 0) + add_child(roof) + # fluoro batten under the roof -- and a real light at night + _slab(Vector3(12, 3.35, -15), Vector3(0.2, 0.08, 4.2), _flat(Color(0.95, 0.96, 0.9), 0.3, Color(0.9, 0.93, 0.85)), false) + if Game.time_of_day == "night": + var ol := OmniLight3D.new() + ol.light_color = Color(0.92, 0.95, 0.88) + ol.light_energy = 2.2 + ol.omni_range = 14.0 + ol.position = Vector3(12, 3.1, -15) + add_child(ol) + # the bay: painted outline + the marker game.gd watches + var paint := _flat(Color(0.86, 0.86, 0.8), 0.7) + for sx in [9.5, 14.5]: + _slab(Vector3(sx, 0.075, -14.2), Vector3(0.12, 0.02, 6.4), paint, false) + _slab(Vector3(12, 0.075, -17.4), Vector3(5.1, 0.02, 0.12), paint, false) + var bay := Marker3D.new() + bay.name = "GarageBay" + bay.position = Vector3(12, 0, -14.2) + add_child(bay) + +func _hills_hoist() -> void: + var steel := _flat(Color(0.6, 0.61, 0.63), 0.4) + steel.metallic = 0.6 + _cyl(Vector3(-14, 1.3, -20), 0.05, 2.6, steel) + hoist = Node3D.new() + hoist.position = Vector3(-14, 2.55, -20) + add_child(hoist) + for i in 4: + var arm := MeshInstance3D.new() + var bm := BoxMesh.new() + bm.size = Vector3(3.4, 0.05, 0.05) + bm.material = steel + arm.mesh = bm + arm.rotation_degrees = Vector3(0, 45.0 + i * 90.0, 4.0) + hoist.add_child(arm) + for i in 2: # wire squares + var r := 1.0 + i * 0.55 + for j in 4: + var wire := MeshInstance3D.new() + var wm := BoxMesh.new() + wm.size = Vector3(r * 1.41, 0.015, 0.015) + wm.material = steel + wire.mesh = wm + wire.position = Vector3(0, 0.06 + i * 0.02, 0).rotated(Vector3.UP, 0) + wire.rotation_degrees = Vector3(0, j * 90.0, 0) + wire.position = Vector3(cos(deg_to_rad(j * 90.0 + 90.0)), 0.08, sin(deg_to_rad(j * 90.0 + 90.0))) * r * 0.0 + wire.position.y = 0.08 + wire.transform.origin += wire.basis.z * r * 0.7 + hoist.add_child(wire) + # the washing: towels that have seen some things + for c in [[Color(0.75, 0.5, 0.45), 0.0], [Color(0.5, 0.6, 0.75), 1.7], [Color(0.85, 0.82, 0.7), 3.6]]: + var tow := MeshInstance3D.new() + var tm := BoxMesh.new() + tm.size = Vector3(0.7, 0.55, 0.02) + tm.material = _flat(c[0], 0.9) + tow.mesh = tm + tow.rotation_degrees = Vector3(0, rad_to_deg(float(c[1])), 0) + tow.position = Vector3(cos(float(c[1])), -0.35, sin(float(c[1]))) * 1.2 + hoist.add_child(tow) + +func _tools() -> void: + ## Owned catalogue tools materialise as the real models; unowned leave an + ## oil-stained promise on the slab. Clutter is permanent, like real clutter. + for id in TOOL_SPOTS: + var spot: Dictionary = TOOL_SPOTS[id] + if Garage.has_tool(id): + ToolProps.spawn(self, spot["model"], spot["pos"], float(spot["yaw"]), "shed_tool", id) + else: + _slab(Vector3(spot["pos"].x, 0.078, spot["pos"].z), Vector3(0.7, 0.01, 0.5), + _flat(Color(0.09, 0.08, 0.06), 0.3), false) + for c in CLUTTER: + ToolProps.spawn(self, c[0], c[1], float(c[2])) + +func _neighbours() -> void: + ## Across the road: enough suburbia to feel watched. + for i in 3: + var x := -34.0 + i * 30.0 + _slab(Vector3(x, 1.8, 47), Vector3(18, 3.6, 14), + _mat("facade_brick" if i != 1 else "facade_concrete", 0.045, 0.85)) + var roof := MeshInstance3D.new() + var bm := BoxMesh.new() + bm.size = Vector3(19.5, 0.14, 15.5) + bm.material = _mat("corrugated_iron", 1.3, 0.6) + roof.mesh = bm + roof.position = Vector3(x, 4.2, 47) + roof.rotation_degrees = Vector3(6, 0, 0) + add_child(roof) + _slab(Vector3(x, 0.85, 39.2), Vector3(18, 1.7, 0.08), _mat("paling_fence", 0.55, 0.9)) + +func _gums(rng: RandomNumberGenerator) -> void: + for at in [Vector3(-26, 0, -22), Vector3(20, 0, -25), Vector3(-27, 0, 14), + Vector3(-38, 0, 24), Vector3(34, 0, 23)]: + var h := rng.randf_range(5.5, 8.5) + _cyl(at + Vector3(0, h * 0.5, 0), 0.2, h, _mat("bark", 1.4)) + var crown := MeshInstance3D.new() + var sm := SphereMesh.new() + sm.radius = rng.randf_range(2.4, 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, h + sm.radius * 0.35, 0) + add_child(crown) + +## ---- helpers -------------------------------------------------------------- + +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, emit := Color.BLACK) -> StandardMaterial3D: + var m := StandardMaterial3D.new() + m.albedo_color = color + m.roughness = rough + m.metallic = 0.0 + if emit != Color.BLACK: + m.emission_enabled = true + m.emission = emit + m.emission_energy_multiplier = 1.2 + return m + +func _slab(pos: Vector3, size: Vector3, mat: Material, collide := true) -> void: + var body := 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 _cyl(pos: Vector3, r: float, h: float, mat: Material) -> void: + var mi := MeshInstance3D.new() + var cm := CylinderMesh.new() + cm.top_radius = r + cm.bottom_radius = r * 1.2 + cm.height = h + cm.radial_segments = 8 + cm.material = mat + mi.mesh = cm + mi.position = pos + add_child(mi) diff --git a/levels/the_shed/shed.gd.uid b/levels/the_shed/shed.gd.uid new file mode 100644 index 0000000..7e08274 --- /dev/null +++ b/levels/the_shed/shed.gd.uid @@ -0,0 +1 @@ +uid://b3hopg6xetmiy diff --git a/levels/willowbank_dragway/dragway.gd b/levels/willowbank_dragway/dragway.gd index ba62300..0e236da 100644 --- a/levels/willowbank_dragway/dragway.gd +++ b/levels/willowbank_dragway/dragway.gd @@ -14,26 +14,7 @@ const STRIP_HALF := 12.0 # wall-to-wall half width const FINISH := -402.0 const SHUTDOWN_END := -640.0 -## Real Cashies stock, TRELLIS'd into GLBs (tools/cc_to_glb.py). Scale is the -## longest dimension in metres; mass makes clipping an engine crane at 80 -## a different event to punting a grease gun. -const TOOL_SPECS := { - "angle_grinder": {"size": 0.35, "mass": 3.0}, - "batt_charger": {"size": 0.28, "mass": 4.0}, - "car_ramps": {"size": 0.95, "mass": 9.0}, - "chain_hoist": {"size": 0.45, "mass": 12.0}, - "compressor": {"size": 0.55, "mass": 14.0}, - "engine_crane": {"size": 1.6, "mass": 70.0}, - "engine_stand": {"size": 0.95, "mass": 30.0}, - "grease_gun": {"size": 0.4, "mass": 2.0}, - "jack_stands": {"size": 0.45, "mass": 8.0}, - "jerry_can": {"size": 0.48, "mass": 6.0}, - "obd_reader": {"size": 0.22, "mass": 1.0}, - "socket_set": {"size": 0.4, "mass": 5.0}, - "toolbox": {"size": 0.7, "mass": 18.0}, - "trolley_jack": {"size": 0.65, "mass": 20.0}, - "welder": {"size": 0.5, "mass": 16.0}, -} +## Cashies tool props live in src/tool_props.gd (shared with The Shed). func _ready() -> void: var rng := RandomNumberGenerator.new() @@ -270,54 +251,13 @@ func _gums(rng: RandomNumberGenerator) -> void: func _paddock_tools(rng: RandomNumberGenerator) -> void: ## Strew the paddock with the TRELLIS'd Cashies tool library -- real - ## secondhand clutter, every piece a shuntable RigidBody. Scale comes from - ## TOOL_SPECS (TRELLIS output is unit-ish), base sat on the ground. - var dir := DirAccess.open("res://assets/tools") - if dir == null: - return - var names: Array[String] = [] - for f in dir.get_files(): - if f.ends_with(".glb"): - names.append(f.get_basename()) - names.sort() # deterministic layout + ## secondhand clutter, every piece a shuntable RigidBody (ToolProps). var i := 0 - for n in names: - var scene := load("res://assets/tools/%s.glb" % n) as PackedScene - if scene == null: - continue - var spec: Dictionary = TOOL_SPECS.get(n, {"size": 0.5, "mass": 8.0}) - var model := scene.instantiate() as Node3D - add_child(model) # into the tree first so global AABBs are real - var aabb := AABB() - var first := true - for mi in model.find_children("*", "MeshInstance3D", true, false): - var b: AABB = (mi as MeshInstance3D).global_transform * (mi as MeshInstance3D).get_aabb() - aabb = b if first else aabb.merge(b) - first = false - remove_child(model) - if first: - model.queue_free() - continue - var s: float = float(spec["size"]) / maxf(aabb.size.x, maxf(aabb.size.y, aabb.size.z)) - var body := RigidBody3D.new() - body.mass = float(spec["mass"]) - body.set_meta("cc_tool", n) - var cs := CollisionShape3D.new() - var box := BoxShape3D.new() - box.size = (aabb.size * s).clamp(Vector3.ONE * 0.08, Vector3.ONE * 3.0) - cs.shape = box - cs.position = aabb.get_center() * s - body.add_child(cs) - model.scale = Vector3.ONE * s - body.add_child(model) - add_child(body) - var spot := Vector3(24.0 + (i % 4) * 4.5 + rng.randf_range(-0.8, 0.8), - 0.02 - aabb.position.y * s, + 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)) - body.global_position = spot - body.rotate_y(rng.randf() * TAU) - body.sleeping = true - i += 1 + 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 diff --git a/project.godot b/project.godot index 61a3f8d..5cb99a3 100644 --- a/project.godot +++ b/project.godot @@ -2,7 +2,7 @@ config_version=5 [application] -config/name="Burnout Infinity: Shitbox" +config/name="Shitbox Infinity" run/main_scene="res://src/main.tscn" config/features=PackedStringArray("4.6", "Forward Plus") diff --git a/push19.log b/push19.log new file mode 100644 index 0000000..d9c3a4a --- /dev/null +++ b/push19.log @@ -0,0 +1,11 @@ +Enumerating objects: 113, done. +Counting objects: 0% (1/113) Counting objects: 1% (2/113) Counting objects: 2% (3/113) Counting objects: 3% (4/113) Counting objects: 4% (5/113) Counting objects: 5% (6/113) Counting objects: 6% (7/113) Counting objects: 7% (8/113) Counting objects: 8% (10/113) Counting objects: 9% (11/113) Counting objects: 10% (12/113) Counting objects: 11% (13/113) Counting objects: 12% (14/113) Counting objects: 13% (15/113) Counting objects: 14% (16/113) Counting objects: 15% (17/113) Counting objects: 16% (19/113) Counting objects: 17% (20/113) Counting objects: 18% (21/113) Counting objects: 19% (22/113) Counting objects: 20% (23/113) Counting objects: 21% (24/113) Counting objects: 22% (25/113) Counting objects: 23% (26/113) Counting objects: 24% (28/113) Counting objects: 25% (29/113) Counting objects: 26% (30/113) Counting objects: 27% (31/113) Counting objects: 28% (32/113) Counting objects: 29% (33/113) Counting objects: 30% (34/113) Counting objects: 31% (36/113) Counting objects: 32% (37/113) Counting objects: 33% (38/113) Counting objects: 34% (39/113) Counting objects: 35% (40/113) Counting objects: 36% (41/113) Counting objects: 37% (42/113) Counting objects: 38% (43/113) Counting objects: 39% (45/113) Counting objects: 40% (46/113) Counting objects: 41% (47/113) Counting objects: 42% (48/113) Counting objects: 43% (49/113) Counting objects: 44% (50/113) Counting objects: 45% (51/113) Counting objects: 46% (52/113) Counting objects: 47% (54/113) Counting objects: 48% (55/113) Counting objects: 49% (56/113) Counting objects: 50% (57/113) Counting objects: 51% (58/113) Counting objects: 52% (59/113) Counting objects: 53% (60/113) Counting objects: 54% (62/113) Counting objects: 55% (63/113) Counting objects: 56% (64/113) Counting objects: 57% (65/113) Counting objects: 58% (66/113) Counting objects: 59% (67/113) Counting objects: 60% (68/113) Counting objects: 61% (69/113) Counting objects: 62% (71/113) Counting objects: 63% (72/113) Counting objects: 64% (73/113) Counting objects: 65% (74/113) Counting objects: 66% (75/113) Counting objects: 67% (76/113) Counting objects: 68% (77/113) Counting objects: 69% (78/113) Counting objects: 70% (80/113) Counting objects: 71% (81/113) Counting objects: 72% (82/113) Counting objects: 73% (83/113) Counting objects: 74% (84/113) Counting objects: 75% (85/113) Counting objects: 76% (86/113) Counting objects: 77% (88/113) Counting objects: 78% (89/113) Counting objects: 79% (90/113) Counting objects: 80% (91/113) Counting objects: 81% (92/113) Counting objects: 82% (93/113) Counting objects: 83% (94/113) Counting objects: 84% (95/113) Counting objects: 85% (97/113) Counting objects: 86% (98/113) Counting objects: 87% (99/113) Counting objects: 88% (100/113) Counting objects: 89% (101/113) Counting objects: 90% (102/113) Counting objects: 91% (103/113) Counting objects: 92% (104/113) Counting objects: 93% (106/113) Counting objects: 94% (107/113) Counting objects: 95% (108/113) Counting objects: 96% (109/113) Counting objects: 97% (110/113) Counting objects: 98% (111/113) Counting objects: 99% (112/113) Counting objects: 100% (113/113) Counting objects: 100% (113/113), done. +Delta compression using up to 32 threads +Compressing objects: 0% (1/104) Compressing objects: 1% (2/104) Compressing objects: 2% (3/104) Compressing objects: 3% (4/104) Compressing objects: 4% (5/104) Compressing objects: 5% (6/104) Compressing objects: 6% (7/104) Compressing objects: 7% (8/104) Compressing objects: 8% (9/104) Compressing objects: 9% (10/104) Compressing objects: 10% (11/104) Compressing objects: 11% (12/104) Compressing objects: 12% (13/104) Compressing objects: 13% (14/104) Compressing objects: 14% (15/104) Compressing objects: 15% (16/104) Compressing objects: 16% (17/104) Compressing objects: 17% (18/104) Compressing objects: 18% (19/104) Compressing objects: 19% (20/104) Compressing objects: 20% (21/104) Compressing objects: 21% (22/104) Compressing objects: 22% (23/104) Compressing objects: 23% (24/104) Compressing objects: 24% (25/104) Compressing objects: 25% (26/104) Compressing objects: 26% (28/104) Compressing objects: 27% (29/104) Compressing objects: 28% (30/104) Compressing objects: 29% (31/104) Compressing objects: 30% (32/104) Compressing objects: 31% (33/104) Compressing objects: 32% (34/104) Compressing objects: 33% (35/104) Compressing objects: 34% (36/104) Compressing objects: 35% (37/104) Compressing objects: 36% (38/104) Compressing objects: 37% (39/104) Compressing objects: 38% (40/104) Compressing objects: 39% (41/104) Compressing objects: 40% (42/104) Compressing objects: 41% (43/104) Compressing objects: 42% (44/104) Compressing objects: 43% (45/104) Compressing objects: 44% (46/104) Compressing objects: 45% (47/104) Compressing objects: 46% (48/104) Compressing objects: 47% (49/104) Compressing objects: 48% (50/104) Compressing objects: 49% (51/104) Compressing objects: 50% (52/104) Compressing objects: 51% (54/104) Compressing objects: 52% (55/104) Compressing objects: 53% (56/104) Compressing objects: 54% (57/104) Compressing objects: 55% (58/104) Compressing objects: 56% (59/104) Compressing objects: 57% (60/104) Compressing objects: 58% (61/104) Compressing objects: 59% (62/104) Compressing objects: 60% (63/104) Compressing objects: 61% (64/104) Compressing objects: 62% (65/104) Compressing objects: 63% (66/104) Compressing objects: 64% (67/104) Compressing objects: 65% (68/104) Compressing objects: 66% (69/104) Compressing objects: 67% (70/104) Compressing objects: 68% (71/104) Compressing objects: 69% (72/104) Compressing objects: 70% (73/104) Compressing objects: 71% (74/104) Compressing objects: 72% (75/104) Compressing objects: 73% (76/104) Compressing objects: 74% (77/104) Compressing objects: 75% (78/104) Compressing objects: 76% (80/104) Compressing objects: 77% (81/104) Compressing objects: 78% (82/104) Compressing objects: 79% (83/104) Compressing objects: 80% (84/104) Compressing objects: 81% (85/104) Compressing objects: 82% (86/104) Compressing objects: 83% (87/104) Compressing objects: 84% (88/104) Compressing objects: 85% (89/104) Compressing objects: 86% (90/104) Compressing objects: 87% (91/104) Compressing objects: 88% (92/104) Compressing objects: 89% (93/104) Compressing objects: 90% (94/104) Compressing objects: 91% (95/104) Compressing objects: 92% (96/104) Compressing objects: 93% (97/104) Compressing objects: 94% (98/104) Compressing objects: 95% (99/104) Compressing objects: 96% (100/104) Compressing objects: 97% (101/104) Compressing objects: 98% (102/104) Compressing objects: 99% (103/104) Compressing objects: 100% (104/104) Compressing objects: 100% (104/104), done. +Writing objects: 0% (1/104) Writing objects: 1% (2/104) Writing objects: 2% (3/104) Writing objects: 3% (4/104) Writing objects: 4% (5/104) Writing objects: 5% (6/104) Writing objects: 7% (8/104) Writing objects: 8% (9/104) Writing objects: 10% (11/104) Writing objects: 11% (12/104) Writing objects: 12% (13/104) Writing objects: 13% (14/104) Writing objects: 14% (15/104) Writing objects: 15% (16/104) Writing objects: 16% (17/104) Writing objects: 17% (18/104) Writing objects: 18% (19/104), 3.06 MiB | 4.73 MiB/s Writing objects: 19% (20/104), 3.06 MiB | 4.73 MiB/s Writing objects: 20% (21/104), 3.06 MiB | 4.73 MiB/s Writing objects: 21% (22/104), 3.06 MiB | 4.73 MiB/s Writing objects: 22% (23/104), 3.06 MiB | 4.73 MiB/s Writing objects: 23% (24/104), 3.06 MiB | 4.73 MiB/s Writing objects: 23% (24/104), 5.37 MiB | 4.65 MiB/s Writing objects: 25% (26/104), 5.37 MiB | 4.65 MiB/s Writing objects: 27% (29/104), 5.37 MiB | 4.65 MiB/s Writing objects: 28% (30/104), 5.37 MiB | 4.65 MiB/s Writing objects: 29% (31/104), 5.37 MiB | 4.65 MiB/s Writing objects: 29% (31/104), 8.62 MiB | 3.77 MiB/s Writing objects: 30% (32/104), 8.62 MiB | 3.77 MiB/s Writing objects: 31% (33/104), 8.62 MiB | 3.77 MiB/s Writing objects: 32% (34/104), 8.62 MiB | 3.77 MiB/s Writing objects: 33% (35/104), 8.62 MiB | 3.77 MiB/s Writing objects: 34% (36/104), 9.25 MiB | 3.20 MiB/s Writing objects: 35% (37/104), 9.25 MiB | 3.20 MiB/s Writing objects: 35% (37/104), 9.87 MiB | 2.82 MiB/s Writing objects: 35% (37/104), 10.62 MiB | 2.61 MiB/s Writing objects: 35% (37/104), 12.12 MiB | 2.37 MiB/s Writing objects: 36% (38/104), 12.12 MiB | 2.37 MiB/s Writing objects: 37% (39/104), 12.12 MiB | 2.37 MiB/s Writing objects: 37% (39/104), 13.81 MiB | 1.69 MiB/s Writing objects: 38% (40/104), 13.81 MiB | 1.69 MiB/s Writing objects: 39% (41/104), 13.81 MiB | 1.69 MiB/s Writing objects: 40% (42/104), 13.81 MiB | 1.69 MiB/s Writing objects: 40% (42/104), 15.87 MiB | 1.47 MiB/s Writing objects: 41% (43/104), 15.87 MiB | 1.47 MiB/s Writing objects: 42% (44/104), 15.87 MiB | 1.47 MiB/s Writing objects: 43% (45/104), 15.87 MiB | 1.47 MiB/s Writing objects: 44% (46/104), 15.87 MiB | 1.47 MiB/s Writing objects: 45% (47/104), 15.87 MiB | 1.47 MiB/s Writing objects: 46% (48/104), 15.87 MiB | 1.47 MiB/s Writing objects: 46% (48/104), 17.87 MiB | 1.65 MiB/s Writing objects: 46% (48/104), 18.87 MiB | 1.38 MiB/s Writing objects: 47% (49/104), 19.31 MiB | 1.28 MiB/s Writing objects: 48% (50/104), 19.31 MiB | 1.28 MiB/s Writing objects: 48% (50/104), 19.68 MiB | 1.14 MiB/s Writing objects: 48% (50/104), 20.18 MiB | 1.08 MiB/s Writing objects: 48% (50/104), 21.31 MiB | 942.00 KiB/s Writing objects: 49% (51/104), 21.31 MiB | 942.00 KiB/s Writing objects: 50% (52/104), 21.31 MiB | 942.00 KiB/s Writing objects: 51% (54/104), 21.31 MiB | 942.00 KiB/s Writing objects: 52% (55/104), 22.43 MiB | 900.00 KiB/s Writing objects: 53% (56/104), 22.43 MiB | 900.00 KiB/s Writing objects: 54% (57/104), 22.43 MiB | 900.00 KiB/s Writing objects: 55% (58/104), 22.43 MiB | 900.00 KiB/s Writing objects: 56% (59/104), 22.43 MiB | 900.00 KiB/s Writing objects: 57% (60/104), 22.43 MiB | 900.00 KiB/s Writing objects: 58% (61/104), 23.68 MiB | 1019.00 KiB/s Writing objects: 59% (62/104), 23.68 MiB | 1019.00 KiB/s Writing objects: 60% (63/104), 24.30 MiB | 994.00 KiB/s Writing objects: 61% (64/104), 24.30 MiB | 994.00 KiB/s Writing objects: 62% (65/104), 24.30 MiB | 994.00 KiB/s Writing objects: 63% (66/104), 24.30 MiB | 994.00 KiB/s Writing objects: 63% (66/104), 24.99 MiB | 1.17 MiB/s Writing objects: 64% (67/104), 24.99 MiB | 1.17 MiB/s Writing objects: 65% (68/104), 24.99 MiB | 1.17 MiB/s Writing objects: 66% (69/104), 24.99 MiB | 1.17 MiB/s Writing objects: 67% (70/104), 24.99 MiB | 1.17 MiB/s Writing objects: 68% (71/104), 25.93 MiB | 1.29 MiB/s Writing objects: 69% (72/104), 25.93 MiB | 1.29 MiB/s Writing objects: 69% (72/104), 26.55 MiB | 1.40 MiB/s Writing objects: 69% (72/104), 27.05 MiB | 1.36 MiB/s Writing objects: 70% (73/104), 27.05 MiB | 1.36 MiB/s Writing objects: 71% (74/104), 27.05 MiB | 1.36 MiB/s Writing objects: 72% (75/104), 27.05 MiB | 1.36 MiB/s Writing objects: 73% (76/104), 27.05 MiB | 1.36 MiB/s Writing objects: 74% (77/104), 27.05 MiB | 1.36 MiB/s Writing objects: 75% (78/104), 27.05 MiB | 1.36 MiB/s Writing objects: 75% (78/104), 28.43 MiB | 1.38 MiB/s Writing objects: 76% (80/104), 28.43 MiB | 1.38 MiB/s Writing objects: 77% (81/104), 28.43 MiB | 1.38 MiB/s Writing objects: 78% (82/104), 28.43 MiB | 1.38 MiB/s Writing objects: 79% (83/104), 28.43 MiB | 1.38 MiB/s Writing objects: 79% (83/104), 29.93 MiB | 1.21 MiB/s Writing objects: 80% (84/104), 29.93 MiB | 1.21 MiB/s Writing objects: 81% (85/104), 29.93 MiB | 1.21 MiB/s Writing objects: 82% (86/104), 29.93 MiB | 1.21 MiB/s Writing objects: 83% (87/104), 29.93 MiB | 1.21 MiB/s Writing objects: 84% (88/104), 29.93 MiB | 1.21 MiB/s Writing objects: 85% (89/104), 29.93 MiB | 1.21 MiB/s Writing objects: 86% (90/104), 30.74 MiB | 1.26 MiB/s Writing objects: 87% (91/104), 30.74 MiB | 1.26 MiB/s Writing objects: 88% (92/104), 30.74 MiB | 1.26 MiB/s Writing objects: 89% (93/104), 30.74 MiB | 1.26 MiB/s Writing objects: 90% (94/104), 31.86 MiB | 1.36 MiB/s Writing objects: 91% (95/104), 31.86 MiB | 1.36 MiB/s Writing objects: 92% (96/104), 31.86 MiB | 1.36 MiB/s Writing objects: 93% (97/104), 31.86 MiB | 1.36 MiB/s Writing objects: 94% (98/104), 31.86 MiB | 1.36 MiB/s Writing objects: 95% (99/104), 31.86 MiB | 1.36 MiB/s Writing objects: 96% (100/104), 31.86 MiB | 1.36 MiB/s Writing objects: 97% (101/104), 31.86 MiB | 1.36 MiB/s Writing objects: 98% (102/104), 31.86 MiB | 1.36 MiB/s Writing objects: 99% (103/104), 31.86 MiB | 1.36 MiB/s Writing objects: 100% (104/104), 31.86 MiB | 1.36 MiB/s Writing objects: 100% (104/104), 31.87 MiB | 1.56 MiB/s, done. +Total 104 (delta 50), reused 0 (delta 0), pack-reused 0 (from 0) +remote: Resolving deltas: 0% (0/50) remote: Resolving deltas: 2% (1/50) remote: Resolving deltas: 4% (2/50) remote: Resolving deltas: 6% (3/50) remote: Resolving deltas: 8% (4/50) remote: Resolving deltas: 10% (5/50) remote: Resolving deltas: 12% (6/50) remote: Resolving deltas: 14% (7/50) remote: Resolving deltas: 16% (8/50) remote: Resolving deltas: 18% (9/50) remote: Resolving deltas: 20% (10/50) remote: Resolving deltas: 22% (11/50) remote: Resolving deltas: 24% (12/50) remote: Resolving deltas: 26% (13/50) remote: Resolving deltas: 28% (14/50) remote: Resolving deltas: 30% (15/50) remote: Resolving deltas: 32% (16/50) remote: Resolving deltas: 34% (17/50) remote: Resolving deltas: 36% (18/50) remote: Resolving deltas: 38% (19/50) remote: Resolving deltas: 40% (20/50) remote: Resolving deltas: 42% (21/50) remote: Resolving deltas: 44% (22/50) remote: Resolving deltas: 46% (23/50) remote: Resolving deltas: 48% (24/50) remote: Resolving deltas: 50% (25/50) remote: Resolving deltas: 52% (26/50) remote: Resolving deltas: 54% (27/50) remote: Resolving deltas: 56% (28/50) remote: Resolving deltas: 58% (29/50) remote: Resolving deltas: 60% (30/50) remote: Resolving deltas: 62% (31/50) remote: Resolving deltas: 64% (32/50) remote: Resolving deltas: 66% (33/50) remote: Resolving deltas: 68% (34/50) remote: Resolving deltas: 70% (35/50) remote: Resolving deltas: 72% (36/50) remote: Resolving deltas: 74% (37/50) remote: Resolving deltas: 76% (38/50) remote: Resolving deltas: 78% (39/50) remote: Resolving deltas: 80% (40/50) remote: Resolving deltas: 82% (41/50) remote: Resolving deltas: 84% (42/50) remote: Resolving deltas: 86% (43/50) remote: Resolving deltas: 88% (44/50) remote: Resolving deltas: 90% (45/50) remote: Resolving deltas: 92% (46/50) remote: Resolving deltas: 94% (47/50) remote: Resolving deltas: 96% (48/50) remote: Resolving deltas: 98% (49/50) remote: Resolving deltas: 100% (50/50) remote: Resolving deltas: 100% (50/50), completed with 7 local objects. +remote: . Processing 1 references +remote: Processed 1 references in total +To ssh://100.71.119.27:222/monster/burnoutinfinityshitbox.git + 4f91b34..8967972 main -> main diff --git a/src/game.gd b/src/game.gd index e68f78c..4517aca 100644 --- a/src/game.gd +++ b/src/game.gd @@ -135,6 +135,10 @@ var _road_off := 0.0 # player's tracked offset along the road line var _oncoming_run := 0.0 var _oncoming_toast := 0.0 +var garage_bay: Node3D = null # The Shed's bay: roll in, stop, garage opens +var bay_t := 0.0 +var garage_requested := false + var car: Car var cam: Camera3D var race_path: Path3D @@ -189,6 +193,8 @@ func _ready() -> void: level.add_child(path) race_path = path + garage_bay = level.find_child("GarageBay", true, false) + car = (load("res://src/car.tscn") as PackedScene).instantiate() car.stats = load(car_path) # place BEFORE add_child: teleporting a continuous_cd body after it enters @@ -710,6 +716,23 @@ func _physics_process(delta: float) -> void: _outrun_tick(delta) if mode == "cruise": _pursuit_tick(delta) + if mode == "cruise" and garage_bay != null and not car.wrecked: + # The Shed's bay: roll in, hold still a beat, and the garage opens + var bd := car.global_position.distance_to(garage_bay.global_position) + if bd < 4.0 and car.linear_velocity.length() < 1.5: + var was_t := bay_t + bay_t += delta + if was_t < 0.3 and bay_t >= 0.3: + _msg("HOLD STILL, GRABBING THE TOOLBOX...", 30) + if bay_t >= 1.4 and not garage_requested: + garage_requested = true + Garage.ui_stats_path = Game.car_path + Sfx.shot_2d("clunk", -4.0) + # headless suites have no current_scene to swap; the flag is the test + if get_tree().current_scene != null: + get_tree().change_scene_to_file("res://src/garage.tscn") + else: + bay_t = 0.0 if mode == "drag" and not over: _drag_tick(delta) if mode == "rage" and not over: diff --git a/src/main.tscn b/src/main.tscn index 7cb55c7..893477d 100644 --- a/src/main.tscn +++ b/src/main.tscn @@ -31,7 +31,7 @@ offset_bottom = -40.0 theme_override_constants/separation = 20 [node name="Title" type="Label" parent="VBox"] -text = "BURNOUT INFINITY: SHITBOX" +text = "SHITBOX INFINITY" horizontal_alignment = 1 theme_override_font_sizes/font_size = 52 theme_override_colors/font_color = Color(1, 0.82, 0.35, 1) diff --git a/src/tool_props.gd b/src/tool_props.gd new file mode 100644 index 0000000..aad4fc8 --- /dev/null +++ b/src/tool_props.gd @@ -0,0 +1,75 @@ +class_name ToolProps +## The TRELLIS'd cashies tool library (assets/tools/*.glb, tools/cc_to_glb.py) +## as spawnable props. TRELLIS output is unit-ish, so every class carries its +## real-world size (longest dimension, metres) and an honest mass -- clipping +## the 70 kg engine crane at speed is a different event to punting a grease gun. + +const SPECS := { + "angle_grinder": {"size": 0.35, "mass": 3.0}, + "batt_charger": {"size": 0.28, "mass": 4.0}, + "car_ramps": {"size": 0.95, "mass": 9.0}, + "chain_hoist": {"size": 0.45, "mass": 12.0}, + "compressor": {"size": 0.55, "mass": 14.0}, + "engine_crane": {"size": 1.6, "mass": 70.0}, + "engine_stand": {"size": 0.95, "mass": 30.0}, + "grease_gun": {"size": 0.4, "mass": 2.0}, + "jack_stands": {"size": 0.45, "mass": 8.0}, + "jerry_can": {"size": 0.48, "mass": 6.0}, + "obd_reader": {"size": 0.22, "mass": 1.0}, + "socket_set": {"size": 0.4, "mass": 5.0}, + "toolbox": {"size": 0.7, "mass": 18.0}, + "trolley_jack": {"size": 0.65, "mass": 20.0}, + "welder": {"size": 0.5, "mass": 16.0}, +} + +static func library() -> Array[String]: + var out: Array[String] = [] + var dir := DirAccess.open("res://assets/tools") + if dir == null: + return out + for f in dir.get_files(): + if f.ends_with(".glb"): + out.append(f.get_basename()) + out.sort() + return out + +static func spawn(parent: Node3D, name: String, pos: Vector3, yaw: float, + meta_key := "cc_tool", meta_val := "") -> RigidBody3D: + ## Instantiate one tool as a shuntable RigidBody, base sat on pos.y. + ## parent must already be inside the tree (global AABBs are measured live). + var path := "res://assets/tools/%s.glb" % name + if not ResourceLoader.exists(path): + return null + var scene := load(path) as PackedScene + if scene == null: + return null + var spec: Dictionary = SPECS.get(name, {"size": 0.5, "mass": 8.0}) + var model := scene.instantiate() as Node3D + parent.add_child(model) # into the tree first so global AABBs are real + var aabb := AABB() + var first := true + for mi in model.find_children("*", "MeshInstance3D", true, false): + var b: AABB = (mi as MeshInstance3D).global_transform * (mi as MeshInstance3D).get_aabb() + aabb = b if first else aabb.merge(b) + first = false + parent.remove_child(model) + if first: + model.queue_free() + return null + var s: float = float(spec["size"]) / maxf(aabb.size.x, maxf(aabb.size.y, aabb.size.z)) + var body := RigidBody3D.new() + body.mass = float(spec["mass"]) + body.set_meta(meta_key, meta_val if meta_val != "" else name) + var cs := CollisionShape3D.new() + var box := BoxShape3D.new() + box.size = (aabb.size * s).clamp(Vector3.ONE * 0.08, Vector3.ONE * 3.0) + cs.shape = box + cs.position = aabb.get_center() * s + body.add_child(cs) + model.scale = Vector3.ONE * s + body.add_child(model) + parent.add_child(body) + body.global_position = Vector3(pos.x, pos.y - aabb.position.y * s + 0.02, pos.z) + body.rotate_y(yaw) + body.sleeping = true + return body diff --git a/src/tool_props.gd.uid b/src/tool_props.gd.uid new file mode 100644 index 0000000..76f8a33 --- /dev/null +++ b/src/tool_props.gd.uid @@ -0,0 +1 @@ +uid://dbvs01y1dxiet diff --git a/tests/smoke.gd b/tests/smoke.gd index 3c7ea9b..b4bcb4a 100644 --- a/tests/smoke.gd +++ b/tests/smoke.gd @@ -101,6 +101,29 @@ func _run() -> void: _check(ui.done, "repair never finished (stuck on step %d)" % ui.step_index) _check(not Garage.faults("datto_120y").has("gearbox"), "DIY fix didn't clear the fault") ui.free() + # THE SHED home base: owned tools materialise (sockets were bought above, + # ramps were not), and rolling into the bay and holding still opens the garage + print(" .. shed") + Game.level_path = levels["the_shed"] + gg = await _boot("cruise") + _check(gg.garage_bay != null, "shed has no GarageBay") + var owned_props: Array = gg.find_children("*", "RigidBody3D", true, false).filter( + func(n): return n.has_meta("shed_tool")) + _check(owned_props.size() == 1 and owned_props[0].get_meta("shed_tool") == "sockets", + "owned-tool display wrong (%d shed_tool props)" % owned_props.size()) + var bay_at: Vector3 = gg.garage_bay.global_position + var bay_xf := Transform3D(Basis.IDENTITY, bay_at + Vector3.UP * 0.75) + PhysicsServer3D.body_set_state(gg.car.get_rid(), PhysicsServer3D.BODY_STATE_TRANSFORM, bay_xf) + gg.car.linear_velocity = Vector3.ZERO + gg.car._prev_lv = Vector3.ZERO + for i in 150: + await physics_frame + gg.car.linear_velocity = Vector3.ZERO + if gg.garage_requested: + break + _check(gg.garage_requested, "rolling into the bay never opened the garage") + gg.free() + Game.level_path = levels.get("woolies_carpark", levels.values()[0]) Garage._reset_for_test() # later stages must run stock # cruise: throttle 3s, car must move