The Shed: workshop tools unlock tune-ups (second progression path)
Six tools, bought once, global: SUPERCHEAP WHEEL RAMPS, CASHIES CODE READER (one previous owner, screen cracked), ALDI SOCKET SET, BUNNINGS AIR COMPRESSOR, CASHIES CHAIN HOIST (on wheels, definitely not stolen), SERVO ADDITIVE CRATE. Each unlocks a per-car tune-up at labour prices: dodgy alignment (+0.4 grip), check engine delete (+6% power, the light WAS the problem), 60 PSI in every tyre, head skim & lumpy cam (+12%), octane booster habit (+0.25 boost), and RETORQUE EVERYTHING -- the one effect parts can't buy: it raises the car's panel-shed and wreck thresholds, which are now per-instance vars instead of consts. Parts buy bling; the shed buys labour. Garage screen grows three sections (parts / shed / tune-ups) with locked states showing which tool a tune needs. Save schema extends compatibly. Smoke covers the tool gate, double-sell refusal and panel hardening on a live car. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fd1218621b
commit
117d950675
21
README.md
21
README.md
@ -93,11 +93,22 @@ real stat bump AND bolts a procedural eyesore onto the car -- progression you
|
|||||||
can see from across the carpark. Fully kitted is roughly +35% power, +1.1 grip
|
can see from across the carpark. Fully kitted is roughly +35% power, +1.1 grip
|
||||||
and +0.55 boost.
|
and +0.55 boost.
|
||||||
|
|
||||||
Purchases are per-car and persist in `user://garage.save`. Upgrades apply to a
|
**The Shed** is the second path: workshop tools bought once, globally --
|
||||||
duplicate of the CarStats resource -- the .tres is shared with traffic and
|
SUPERCHEAP WHEEL RAMPS, the CASHIES CODE READER (one previous owner, screen
|
||||||
parked spawns of the same model, and tuning your Lazza must not secretly tune
|
cracked), an ALDI SOCKET SET, a BUNNINGS AIR COMPRESSOR, the CASHIES CHAIN
|
||||||
every Lazza in the city. Bolt-ons attach after the deformer snapshots the
|
HOIST (on wheels, definitely not stolen) and a SERVO ADDITIVE CRATE. Each tool
|
||||||
mesh, so the wing survives every crash, as is right and proper.
|
unlocks a per-car **tune-up** at labour prices: dodgy wheel alignment, check
|
||||||
|
engine delete ("the light WAS the problem"), 60 PSI in every tyre, a head skim
|
||||||
|
and lumpy cam, an octane booster habit -- and RETORQUE EVERYTHING, which is the
|
||||||
|
one thing parts can't buy: panels that hang on through bigger hits (it raises
|
||||||
|
the car's shed-a-panel and wreck thresholds). Parts buy bling; the shed buys
|
||||||
|
labour.
|
||||||
|
|
||||||
|
Purchases are per-car (tools are global) and persist in `user://garage.save`.
|
||||||
|
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
|
||||||
|
snapshots the mesh, so the wing survives every crash, as is right and proper.
|
||||||
|
|
||||||
## Crash deformation and damage
|
## Crash deformation and damage
|
||||||
|
|
||||||
|
|||||||
40
shot_garage_f.png.import
Normal file
40
shot_garage_f.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dr7aglv20utx3"
|
||||||
|
path="res://.godot/imported/shot_garage_f.png-aa2f56aea17c124e5ede0f7b9bc1c349.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://shot_garage_f.png"
|
||||||
|
dest_files=["res://.godot/imported/shot_garage_f.png-aa2f56aea17c124e5ede0f7b9bc1c349.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
|
||||||
40
shot_garage_r.png.import
Normal file
40
shot_garage_r.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bmlq6d63wenbp"
|
||||||
|
path="res://.godot/imported/shot_garage_r.png-524c5e48bfb09463cc90db7e4ba2f22b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://shot_garage_r.png"
|
||||||
|
dest_files=["res://.godot/imported/shot_garage_r.png-524c5e48bfb09463cc90db7e4ba2f22b.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
|
||||||
11
src/car.gd
11
src/car.gd
@ -24,6 +24,11 @@ const BOOST_CAP := 4.0 # takedowns grow the meter up to 4x (Burnout 3 rul
|
|||||||
const PART_DV := 4.0 # velocity jolt (m/s in one tick) that sheds a panel
|
const PART_DV := 4.0 # velocity jolt (m/s in one tick) that sheds a panel
|
||||||
const WRECK_DV := 9.0 # jolt that wrecks you
|
const WRECK_DV := 9.0 # jolt that wrecks you
|
||||||
|
|
||||||
|
# per-instance copies: the Garage's RETORQUE EVERYTHING tune raises them, so a
|
||||||
|
# properly spannered shitbox holds its panels on through bigger hits
|
||||||
|
var part_dv := PART_DV
|
||||||
|
var wreck_dv := WRECK_DV
|
||||||
|
|
||||||
var throttle_in := 0.0
|
var throttle_in := 0.0
|
||||||
var steer_in := 0.0
|
var steer_in := 0.0
|
||||||
var drift_in := false
|
var drift_in := false
|
||||||
@ -178,7 +183,7 @@ func _physics_process(delta: float) -> void:
|
|||||||
var jolt := linear_velocity - _prev_lv
|
var jolt := linear_velocity - _prev_lv
|
||||||
_prev_lv = linear_velocity
|
_prev_lv = linear_velocity
|
||||||
_impact_cd = maxf(_impact_cd - delta, 0.0)
|
_impact_cd = maxf(_impact_cd - delta, 0.0)
|
||||||
if jolt.length() > PART_DV and _impact_cd == 0.0:
|
if jolt.length() > part_dv and _impact_cd == 0.0:
|
||||||
_impact_cd = 0.4 # one collision = one crash event, not one per tick
|
_impact_cd = 0.4 # one collision = one crash event, not one per tick
|
||||||
_impact(jolt)
|
_impact(jolt)
|
||||||
|
|
||||||
@ -190,7 +195,7 @@ func _impact(jolt: Vector3) -> void:
|
|||||||
crashed.emit(dv)
|
crashed.emit(dv)
|
||||||
Sfx.shot(self, "crash_big" if dv > 8.0 else "crash_small",
|
Sfx.shot(self, "crash_big" if dv > 8.0 else "crash_small",
|
||||||
-2.0, randf_range(0.9, 1.1))
|
-2.0, randf_range(0.9, 1.1))
|
||||||
if dv > WRECK_DV:
|
if dv > wreck_dv:
|
||||||
Sfx.shot(self, "glass", -4.0)
|
Sfx.shot(self, "glass", -4.0)
|
||||||
Fx.sparks(self, clampi(int(dv * 3.0), 10, 44))
|
Fx.sparks(self, clampi(int(dv * 3.0), 10, 44))
|
||||||
var side := (global_basis.inverse() * -jolt).normalized()
|
var side := (global_basis.inverse() * -jolt).normalized()
|
||||||
@ -202,7 +207,7 @@ func _impact(jolt: Vector3) -> void:
|
|||||||
_deform.impact(reach, -side, minf(dv, 26.0) * 0.048)
|
_deform.impact(reach, -side, minf(dv, 26.0) * 0.048)
|
||||||
for i in 1 + int(dv > 8.0) + int(dv > 12.0):
|
for i in 1 + int(dv > 8.0) + int(dv > 12.0):
|
||||||
_shed_panel(side)
|
_shed_panel(side)
|
||||||
if dv > WRECK_DV and not wrecked:
|
if dv > wreck_dv and not wrecked:
|
||||||
wrecked = true
|
wrecked = true
|
||||||
wreck_started.emit()
|
wreck_started.emit()
|
||||||
get_tree().create_timer(2.2, true, false, true).timeout.connect(_recover)
|
get_tree().create_timer(2.2, true, false, true).timeout.connect(_recover)
|
||||||
|
|||||||
101
src/garage.gd
101
src/garage.gd
@ -57,6 +57,39 @@ const CATALOGUE := {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## THE SHED: workshop tools are global, bought once, and each unlocks a
|
||||||
|
## per-car TUNE-UP. Parts buy bling; the shed buys labour. Provenance of the
|
||||||
|
## hoist is not to be discussed.
|
||||||
|
const TOOLS := {
|
||||||
|
"ramps": {"name": "SUPERCHEAP WHEEL RAMPS", "price": 800,
|
||||||
|
"desc": "Plastic. Rated to \"most cars, probably\"."},
|
||||||
|
"reader": {"name": "CASHIES CODE READER", "price": 1200,
|
||||||
|
"desc": "One previous owner. Screen cracked. Works if you squint."},
|
||||||
|
"sockets": {"name": "ALDI SOCKET SET", "price": 600,
|
||||||
|
"desc": "Sunday special. Metric-ish."},
|
||||||
|
"compressor": {"name": "BUNNINGS AIR COMPRESSOR", "price": 1500,
|
||||||
|
"desc": "Came with three fittings, none of them the right one."},
|
||||||
|
"hoist": {"name": "CASHIES CHAIN HOIST (ON WHEELS)", "price": 2500,
|
||||||
|
"desc": "Definitely not stolen. Rolls beautifully."},
|
||||||
|
"additives": {"name": "SERVO ADDITIVE CRATE", "price": 400,
|
||||||
|
"desc": "A crate of bottles that all claim +30 horsepower."},
|
||||||
|
}
|
||||||
|
## tune id -> {name, desc, price (labour), tool (required)}
|
||||||
|
const TUNES := {
|
||||||
|
"align": {"name": "DODGY WHEEL ALIGNMENT", "price": 300, "tool": "ramps",
|
||||||
|
"desc": "Eyeballed against the fence. Straight-ish. +0.4 grip"},
|
||||||
|
"cel": {"name": "CHECK ENGINE DELETE", "price": 300, "tool": "reader",
|
||||||
|
"desc": "The light WAS the problem. +6% power"},
|
||||||
|
"retorque": {"name": "RETORQUE EVERYTHING", "price": 300, "tool": "sockets",
|
||||||
|
"desc": "Every bolt. Three left over, but panels HANG ON now. +panel grit"},
|
||||||
|
"psi": {"name": "60 PSI IN EVERY TYRE", "price": 300, "tool": "compressor",
|
||||||
|
"desc": "More PSI is more grip, that's just science. +0.3 grip, +2% power"},
|
||||||
|
"headskim": {"name": "HEAD SKIM & LUMPY CAM", "price": 500, "tool": "hoist",
|
||||||
|
"desc": "Engine out on the driveway. Neighbours notified by sound. +12% power"},
|
||||||
|
"octane": {"name": "OCTANE BOOSTER HABIT", "price": 250, "tool": "additives",
|
||||||
|
"desc": "Two bottles per tank, minimum. +0.25 boost"},
|
||||||
|
}
|
||||||
|
|
||||||
static var _data := {}
|
static var _data := {}
|
||||||
static var _loaded := false
|
static var _loaded := false
|
||||||
|
|
||||||
@ -64,7 +97,7 @@ static func _load() -> void:
|
|||||||
if _loaded:
|
if _loaded:
|
||||||
return
|
return
|
||||||
_loaded = true
|
_loaded = true
|
||||||
_data = {"bucks": 0, "cars": {}}
|
_data = {"bucks": 0, "cars": {}, "tools": [], "tunes": {}}
|
||||||
if FileAccess.file_exists(save_path):
|
if FileAccess.file_exists(save_path):
|
||||||
var parsed = JSON.parse_string(FileAccess.get_file_as_string(save_path))
|
var parsed = JSON.parse_string(FileAccess.get_file_as_string(save_path))
|
||||||
if parsed is Dictionary and parsed.has("bucks"):
|
if parsed is Dictionary and parsed.has("bucks"):
|
||||||
@ -106,6 +139,53 @@ static func buy(car_id: String, upgrade: String) -> bool:
|
|||||||
_flush()
|
_flush()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
static func tools() -> Array:
|
||||||
|
_load()
|
||||||
|
return _data.get("tools", [])
|
||||||
|
|
||||||
|
static func has_tool(tool: String) -> bool:
|
||||||
|
return tool in tools()
|
||||||
|
|
||||||
|
static func buy_tool(tool: String) -> bool:
|
||||||
|
_load()
|
||||||
|
if not TOOLS.has(tool) or has_tool(tool):
|
||||||
|
return false
|
||||||
|
var price: int = TOOLS[tool]["price"]
|
||||||
|
if int(_data["bucks"]) < price:
|
||||||
|
return false
|
||||||
|
_data["bucks"] = int(_data["bucks"]) - price
|
||||||
|
var lst: Array = _data.get("tools", [])
|
||||||
|
lst.append(tool)
|
||||||
|
_data["tools"] = lst
|
||||||
|
_flush()
|
||||||
|
return true
|
||||||
|
|
||||||
|
static func tunes(car_id: String) -> Array:
|
||||||
|
_load()
|
||||||
|
return _data.get("tunes", {}).get(car_id, [])
|
||||||
|
|
||||||
|
static func has_tune(car_id: String, tune: String) -> bool:
|
||||||
|
return tune in tunes(car_id)
|
||||||
|
|
||||||
|
static func buy_tune(car_id: String, tune: String) -> bool:
|
||||||
|
## The skill-tree gate: no tool, no tune. The shed IS the prerequisite.
|
||||||
|
_load()
|
||||||
|
if not TUNES.has(tune) or has_tune(car_id, tune):
|
||||||
|
return false
|
||||||
|
if not has_tool(TUNES[tune]["tool"]):
|
||||||
|
return false
|
||||||
|
var price: int = TUNES[tune]["price"]
|
||||||
|
if int(_data["bucks"]) < price:
|
||||||
|
return false
|
||||||
|
_data["bucks"] = int(_data["bucks"]) - price
|
||||||
|
var t: Dictionary = _data.get("tunes", {})
|
||||||
|
var lst: Array = t.get(car_id, [])
|
||||||
|
lst.append(tune)
|
||||||
|
t[car_id] = lst
|
||||||
|
_data["tunes"] = t
|
||||||
|
_flush()
|
||||||
|
return true
|
||||||
|
|
||||||
static func car_id_of(stats_path: String) -> String:
|
static func car_id_of(stats_path: String) -> String:
|
||||||
# res://cars/<id>/stats.tres -> <id>
|
# res://cars/<id>/stats.tres -> <id>
|
||||||
return stats_path.get_base_dir().get_file()
|
return stats_path.get_base_dir().get_file()
|
||||||
@ -116,8 +196,10 @@ static func apply(car: Node3D, stats_path: String, is_player: bool) -> void:
|
|||||||
## Player only: traffic and parked cars stay stock -- your mods are YOURS.
|
## Player only: traffic and parked cars stay stock -- your mods are YOURS.
|
||||||
if not is_player:
|
if not is_player:
|
||||||
return
|
return
|
||||||
var ups := owned(car_id_of(stats_path))
|
var cid := car_id_of(stats_path)
|
||||||
if ups.is_empty():
|
var ups := owned(cid)
|
||||||
|
var tns := tunes(cid)
|
||||||
|
if ups.is_empty() and tns.is_empty():
|
||||||
return
|
return
|
||||||
var st = car.get("stats")
|
var st = car.get("stats")
|
||||||
if st != null:
|
if st != null:
|
||||||
@ -134,6 +216,17 @@ static func apply(car: Node3D, stats_path: String, is_player: bool) -> void:
|
|||||||
"spoiler": ga += 0.6
|
"spoiler": ga += 0.6
|
||||||
"exhaust": ba += 0.35
|
"exhaust": ba += 0.35
|
||||||
"neons": ba += 0.2
|
"neons": ba += 0.2
|
||||||
|
for t in tns:
|
||||||
|
match t:
|
||||||
|
"align": ga += 0.4
|
||||||
|
"cel": pm += 0.06
|
||||||
|
"psi": ga += 0.3; pm += 0.02
|
||||||
|
"headskim": pm += 0.12
|
||||||
|
"octane": ba += 0.25
|
||||||
|
"retorque":
|
||||||
|
# panels cling on: thresholds live on the car, not the stats
|
||||||
|
car.set("part_dv", float(car.get("part_dv")) + 2.0)
|
||||||
|
car.set("wreck_dv", float(car.get("wreck_dv")) + 1.5)
|
||||||
st.engine_power *= pm
|
st.engine_power *= pm
|
||||||
st.grip += ga
|
st.grip += ga
|
||||||
car.set("stats", st)
|
car.set("stats", st)
|
||||||
@ -266,6 +359,6 @@ static func _bolt_visuals(car: Node3D, ups: Array) -> void:
|
|||||||
|
|
||||||
## test hook: wipe state (used by the smoke suite, never the game)
|
## test hook: wipe state (used by the smoke suite, never the game)
|
||||||
static func _reset_for_test() -> void:
|
static func _reset_for_test() -> void:
|
||||||
_data = {"bucks": 0, "cars": {}}
|
_data = {"bucks": 0, "cars": {}, "tools": [], "tunes": {}}
|
||||||
_loaded = true
|
_loaded = true
|
||||||
_flush()
|
_flush()
|
||||||
|
|||||||
@ -8,7 +8,7 @@ var list: ItemList
|
|||||||
var bucks_label: Label
|
var bucks_label: Label
|
||||||
var desc_label: Label
|
var desc_label: Label
|
||||||
var buy_btn: Button
|
var buy_btn: Button
|
||||||
var ids: Array = []
|
var entries: Array = [] # row -> {"kind": "part"|"tool"|"tune"|"header", "id": ...}
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
car_id = Garage.car_id_of(Garage.ui_stats_path)
|
car_id = Garage.car_id_of(Garage.ui_stats_path)
|
||||||
@ -70,37 +70,98 @@ func _ready() -> void:
|
|||||||
row.add_child(back)
|
row.add_child(back)
|
||||||
|
|
||||||
_refresh()
|
_refresh()
|
||||||
if list.item_count > 0:
|
for i in list.item_count: # row 0 is a section header now
|
||||||
list.select(0)
|
if list.is_item_selectable(i):
|
||||||
_on_select(0)
|
list.select(i)
|
||||||
|
_on_select(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
func _header(text: String) -> void:
|
||||||
|
var i := list.add_item(text)
|
||||||
|
list.set_item_selectable(i, false)
|
||||||
|
list.set_item_custom_fg_color(i, Color(1, 0.82, 0.35))
|
||||||
|
entries.append({"kind": "header"})
|
||||||
|
|
||||||
func _refresh() -> void:
|
func _refresh() -> void:
|
||||||
var keep := list.get_selected_items()
|
var keep := list.get_selected_items()
|
||||||
list.clear()
|
list.clear()
|
||||||
ids.clear()
|
entries.clear()
|
||||||
bucks_label.text = "BUCKS: $%d" % Garage.bucks()
|
bucks_label.text = "BUCKS: $%d" % Garage.bucks()
|
||||||
|
_header("--- BOLT-ON PARTS (this car) ---")
|
||||||
for id in Garage.CATALOGUE:
|
for id in Garage.CATALOGUE:
|
||||||
var c: Dictionary = Garage.CATALOGUE[id]
|
var c: Dictionary = Garage.CATALOGUE[id]
|
||||||
var tag := "[FITTED]" if Garage.has(car_id, id) else "$%d" % c["price"]
|
var tag := "[FITTED]" if Garage.has(car_id, id) else "$%d" % c["price"]
|
||||||
list.add_item("%-26s %s" % [c["name"], tag])
|
list.add_item("%-28s %s" % [c["name"], tag])
|
||||||
ids.append(id)
|
entries.append({"kind": "part", "id": id})
|
||||||
if not keep.is_empty() and keep[0] < list.item_count:
|
_header("--- THE SHED (tools, buy once, unlock tune-ups) ---")
|
||||||
|
for id in Garage.TOOLS:
|
||||||
|
var c: Dictionary = Garage.TOOLS[id]
|
||||||
|
var tag := "[OWNED]" if Garage.has_tool(id) else "$%d" % c["price"]
|
||||||
|
list.add_item("%-28s %s" % [c["name"], tag])
|
||||||
|
entries.append({"kind": "tool", "id": id})
|
||||||
|
_header("--- TUNE-UPS (this car, needs the tool) ---")
|
||||||
|
for id in Garage.TUNES:
|
||||||
|
var c: Dictionary = Garage.TUNES[id]
|
||||||
|
var tag: String
|
||||||
|
if Garage.has_tune(car_id, id):
|
||||||
|
tag = "[DONE]"
|
||||||
|
elif not Garage.has_tool(c["tool"]):
|
||||||
|
tag = "[NEEDS %s]" % Garage.TOOLS[c["tool"]]["name"]
|
||||||
|
else:
|
||||||
|
tag = "$%d" % c["price"]
|
||||||
|
list.add_item("%-28s %s" % [c["name"], tag])
|
||||||
|
entries.append({"kind": "tune", "id": id})
|
||||||
|
if not keep.is_empty() and keep[0] < list.item_count and list.is_item_selectable(keep[0]):
|
||||||
list.select(keep[0])
|
list.select(keep[0])
|
||||||
|
|
||||||
|
func _entry_info(i: int) -> Dictionary:
|
||||||
|
var e: Dictionary = entries[i]
|
||||||
|
match e.get("kind"):
|
||||||
|
"part":
|
||||||
|
var c: Dictionary = Garage.CATALOGUE[e["id"]]
|
||||||
|
return {"desc": c["desc"], "price": c["price"],
|
||||||
|
"done": Garage.has(car_id, e["id"]), "locked": false}
|
||||||
|
"tool":
|
||||||
|
var c: Dictionary = Garage.TOOLS[e["id"]]
|
||||||
|
return {"desc": c["desc"], "price": c["price"],
|
||||||
|
"done": Garage.has_tool(e["id"]), "locked": false}
|
||||||
|
"tune":
|
||||||
|
var c: Dictionary = Garage.TUNES[e["id"]]
|
||||||
|
return {"desc": c["desc"], "price": c["price"],
|
||||||
|
"done": Garage.has_tune(car_id, e["id"]),
|
||||||
|
"locked": not Garage.has_tool(c["tool"])}
|
||||||
|
return {}
|
||||||
|
|
||||||
func _on_select(i: int) -> void:
|
func _on_select(i: int) -> void:
|
||||||
var c: Dictionary = Garage.CATALOGUE[ids[i]]
|
var info := _entry_info(i)
|
||||||
desc_label.text = c["desc"]
|
if info.is_empty():
|
||||||
buy_btn.disabled = Garage.has(car_id, ids[i]) or Garage.bucks() < int(c["price"])
|
return
|
||||||
buy_btn.text = "FITTED" if Garage.has(car_id, ids[i]) else "BUY ($%d)" % c["price"]
|
desc_label.text = info["desc"]
|
||||||
|
if info["done"]:
|
||||||
|
buy_btn.text = "DONE"
|
||||||
|
buy_btn.disabled = true
|
||||||
|
elif info["locked"]:
|
||||||
|
buy_btn.text = "LOCKED (buy the tool)"
|
||||||
|
buy_btn.disabled = true
|
||||||
|
else:
|
||||||
|
buy_btn.text = "BUY ($%d)" % int(info["price"])
|
||||||
|
buy_btn.disabled = Garage.bucks() < int(info["price"])
|
||||||
|
|
||||||
func _on_buy() -> void:
|
func _on_buy() -> void:
|
||||||
var sel := list.get_selected_items()
|
var sel := list.get_selected_items()
|
||||||
if sel.is_empty():
|
if sel.is_empty():
|
||||||
return
|
return
|
||||||
if Garage.buy(car_id, ids[sel[0]]):
|
var e: Dictionary = entries[sel[0]]
|
||||||
|
var ok := false
|
||||||
|
match e.get("kind"):
|
||||||
|
"part": ok = Garage.buy(car_id, e["id"])
|
||||||
|
"tool": ok = Garage.buy_tool(e["id"])
|
||||||
|
"tune": ok = Garage.buy_tune(car_id, e["id"])
|
||||||
|
if ok:
|
||||||
Sfx.shot_2d("clunk")
|
Sfx.shot_2d("clunk")
|
||||||
_refresh()
|
_refresh()
|
||||||
_on_select(sel[0])
|
if sel[0] < list.item_count:
|
||||||
|
_on_select(sel[0])
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if event.is_action_pressed("ui_cancel"):
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
|||||||
@ -42,6 +42,15 @@ func _run() -> void:
|
|||||||
"wing added no grip")
|
"wing added no grip")
|
||||||
_check(gg.car.stats.resource_path == "", "upgrades mutated the SHARED stats resource")
|
_check(gg.car.stats.resource_path == "", "upgrades mutated the SHARED stats resource")
|
||||||
gg.free()
|
gg.free()
|
||||||
|
# the shed: tunes are gated on tools, and retorque hardens the panels
|
||||||
|
Garage.earn(2000)
|
||||||
|
_check(not Garage.buy_tune("datto_120y", "retorque"), "tuned without owning the tool")
|
||||||
|
_check(Garage.buy_tool("sockets"), "couldn't buy the socket set")
|
||||||
|
_check(Garage.buy_tune("datto_120y", "retorque"), "tool owned but tune refused")
|
||||||
|
_check(not Garage.buy_tune("datto_120y", "retorque"), "same tune sold twice")
|
||||||
|
gg = await _boot("cruise")
|
||||||
|
_check(gg.car.part_dv > Car.PART_DV + 1.0, "retorque didn't harden panels (%.1f)" % gg.car.part_dv)
|
||||||
|
gg.free()
|
||||||
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