SHITBOX INFINITY + The Shed: a drivable home base
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>
This commit is contained in:
parent
324dbdf625
commit
3e9a8616ab
13
README.md
13
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
|
## Run
|
||||||
|
|
||||||
@ -150,6 +150,15 @@ a complete toolless gearbox swap headless.
|
|||||||
Purchases are per-car (tools are global) and persist in `user://garage.save`,
|
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
|
which also carries the drag **ladder rung**, per-car **quarter-mile PBs** and
|
||||||
outstanding **faults**.
|
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
|
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
|
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
|
not secretly tune every Lazza in the city. Bolt-ons attach after the deformer
|
||||||
|
|||||||
BIN
assets/textures/paling_fence.jpg
Normal file
BIN
assets/textures/paling_fence.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
40
assets/textures/paling_fence.jpg.import
Normal file
40
assets/textures/paling_fence.jpg.import
Normal file
@ -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
|
||||||
BIN
assets/textures/pegboard.jpg
Normal file
BIN
assets/textures/pegboard.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
40
assets/textures/pegboard.jpg.import
Normal file
40
assets/textures/pegboard.jpg.import
Normal file
@ -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
|
||||||
9
levels/the_shed/level.tscn
Normal file
9
levels/the_shed/level.tscn
Normal file
@ -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)
|
||||||
282
levels/the_shed/shed.gd
Normal file
282
levels/the_shed/shed.gd
Normal file
@ -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)
|
||||||
1
levels/the_shed/shed.gd.uid
Normal file
1
levels/the_shed/shed.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://b3hopg6xetmiy
|
||||||
@ -14,26 +14,7 @@ const STRIP_HALF := 12.0 # wall-to-wall half width
|
|||||||
const FINISH := -402.0
|
const FINISH := -402.0
|
||||||
const SHUTDOWN_END := -640.0
|
const SHUTDOWN_END := -640.0
|
||||||
|
|
||||||
## Real Cashies stock, TRELLIS'd into GLBs (tools/cc_to_glb.py). Scale is the
|
## Cashies tool props live in src/tool_props.gd (shared with The Shed).
|
||||||
## 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},
|
|
||||||
}
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
var rng := RandomNumberGenerator.new()
|
var rng := RandomNumberGenerator.new()
|
||||||
@ -270,53 +251,12 @@ func _gums(rng: RandomNumberGenerator) -> void:
|
|||||||
|
|
||||||
func _paddock_tools(rng: RandomNumberGenerator) -> void:
|
func _paddock_tools(rng: RandomNumberGenerator) -> void:
|
||||||
## Strew the paddock with the TRELLIS'd Cashies tool library -- real
|
## Strew the paddock with the TRELLIS'd Cashies tool library -- real
|
||||||
## secondhand clutter, every piece a shuntable RigidBody. Scale comes from
|
## secondhand clutter, every piece a shuntable RigidBody (ToolProps).
|
||||||
## 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
|
|
||||||
var i := 0
|
var i := 0
|
||||||
for n in names:
|
for n in ToolProps.library():
|
||||||
var scene := load("res://assets/tools/%s.glb" % n) as PackedScene
|
var spot := Vector3(24.0 + (i % 4) * 4.5 + rng.randf_range(-0.8, 0.8), 0.0,
|
||||||
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,
|
|
||||||
-16.0 - float(i / 4) * 7.0 + rng.randf_range(-0.8, 0.8))
|
-16.0 - float(i / 4) * 7.0 + rng.randf_range(-0.8, 0.8))
|
||||||
body.global_position = spot
|
if ToolProps.spawn(self, n, spot, rng.randf() * TAU) != null:
|
||||||
body.rotate_y(rng.randf() * TAU)
|
|
||||||
body.sleeping = true
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
func _race_path() -> void:
|
func _race_path() -> void:
|
||||||
|
|||||||
@ -2,7 +2,7 @@ config_version=5
|
|||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Burnout Infinity: Shitbox"
|
config/name="Shitbox Infinity"
|
||||||
run/main_scene="res://src/main.tscn"
|
run/main_scene="res://src/main.tscn"
|
||||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||||
|
|
||||||
|
|||||||
11
push19.log
Normal file
11
push19.log
Normal file
File diff suppressed because one or more lines are too long
23
src/game.gd
23
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_run := 0.0
|
||||||
var _oncoming_toast := 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 car: Car
|
||||||
var cam: Camera3D
|
var cam: Camera3D
|
||||||
var race_path: Path3D
|
var race_path: Path3D
|
||||||
@ -189,6 +193,8 @@ func _ready() -> void:
|
|||||||
level.add_child(path)
|
level.add_child(path)
|
||||||
race_path = path
|
race_path = path
|
||||||
|
|
||||||
|
garage_bay = level.find_child("GarageBay", true, false)
|
||||||
|
|
||||||
car = (load("res://src/car.tscn") as PackedScene).instantiate()
|
car = (load("res://src/car.tscn") as PackedScene).instantiate()
|
||||||
car.stats = load(car_path)
|
car.stats = load(car_path)
|
||||||
# place BEFORE add_child: teleporting a continuous_cd body after it enters
|
# 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)
|
_outrun_tick(delta)
|
||||||
if mode == "cruise":
|
if mode == "cruise":
|
||||||
_pursuit_tick(delta)
|
_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:
|
if mode == "drag" and not over:
|
||||||
_drag_tick(delta)
|
_drag_tick(delta)
|
||||||
if mode == "rage" and not over:
|
if mode == "rage" and not over:
|
||||||
|
|||||||
@ -31,7 +31,7 @@ offset_bottom = -40.0
|
|||||||
theme_override_constants/separation = 20
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="VBox"]
|
[node name="Title" type="Label" parent="VBox"]
|
||||||
text = "BURNOUT INFINITY: SHITBOX"
|
text = "SHITBOX INFINITY"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
theme_override_font_sizes/font_size = 52
|
theme_override_font_sizes/font_size = 52
|
||||||
theme_override_colors/font_color = Color(1, 0.82, 0.35, 1)
|
theme_override_colors/font_color = Color(1, 0.82, 0.35, 1)
|
||||||
|
|||||||
75
src/tool_props.gd
Normal file
75
src/tool_props.gd
Normal file
@ -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
|
||||||
1
src/tool_props.gd.uid
Normal file
1
src/tool_props.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dbvs01y1dxiet
|
||||||
@ -101,6 +101,29 @@ func _run() -> void:
|
|||||||
_check(ui.done, "repair never finished (stuck on step %d)" % ui.step_index)
|
_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")
|
_check(not Garage.faults("datto_120y").has("gearbox"), "DIY fix didn't clear the fault")
|
||||||
ui.free()
|
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
|
Garage._reset_for_test() # later stages must run stock
|
||||||
|
|
||||||
# cruise: throttle 3s, car must move
|
# cruise: throttle 3s, car must move
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user