Visit The Shed and one of the locals is waiting on the driveway with a
broken shitbox and cash: their actual car model parked there under a
floating Label3D job sign -- who they are, what they reckon is wrong
("it makes a noise at church speed"), the honest fault name, and the
offer. Pull up beside it and hold still to take the job; the wrench
minigame opens on THEIR car.
The roster is the extended universe: Nanna, P-Plate Pepper, Mullet
Merv, Torque'n Tezza, the Self-Described Bathurst Legend, Your Mate
Dazza (don't tell Shazza), Karen from Number 12, and the Widowmaker,
who says nothing and pays cash. Roster and fault rotate
deterministically off jobs_done.
Settlement: full pay at 60+, a $50 tip at 85+, half below 60 -- and
SHED REP moves with the workmanship (+3 clean, -6 botched, cap 40),
scaling the next offer from 70% of shop rate at rep 0 to 100% at cap.
Rep shows on the menu and garage header. Customer jobs bank NO quality
buffs: the customer's car id might be a car the player owns, and
fixing Merv's Kingswood must not secretly tune yours -- the wrench
consumes Garage.ui_customer and routes _finish to customer_done()
instead of clear_fault/set_quality.
The shed guards the spawn (parent is Game and mode == cruise) so the
wrench scene's backdrop copy of the level never summons a customer
mid-job. The spare parked shitbox moves to the kerb; the driveway
belongs to commerce now.
Smoke: customer waits after a shed visit, holding beside the spot takes
the job (flag-guarded for headless), the driven wrench job pays, rep
rises, the driveway clears, and the customer's own-car quality stays
untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
206 lines
6.9 KiB
GDScript
206 lines
6.9 KiB
GDScript
extends Control
|
|
## The shop. Pick a part, read its lie, hand over the bucks.
|
|
## Which car you're outfitting comes in via Garage.ui_stats_path (set by the menu).
|
|
|
|
var car_id := ""
|
|
var car_name := ""
|
|
var list: ItemList
|
|
var bucks_label: Label
|
|
var desc_label: Label
|
|
var buy_btn: Button
|
|
var entries: Array = [] # row -> {"kind": "part"|"tool"|"tune"|"header", "id": ...}
|
|
|
|
func _ready() -> void:
|
|
car_id = Garage.car_id_of(Garage.ui_stats_path)
|
|
var st: CarStats = load(Garage.ui_stats_path)
|
|
car_name = st.display_name if st else car_id
|
|
|
|
var bg := ColorRect.new()
|
|
bg.color = Color(0.07, 0.06, 0.09)
|
|
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
add_child(bg)
|
|
|
|
var box := VBoxContainer.new()
|
|
box.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
box.offset_left = 60
|
|
box.offset_top = 40
|
|
box.offset_right = -60
|
|
box.offset_bottom = -40
|
|
box.add_theme_constant_override("separation", 14)
|
|
add_child(box)
|
|
|
|
var title := Label.new()
|
|
title.text = "THE GARAGE -- %s" % car_name
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
title.add_theme_font_size_override("font_size", 40)
|
|
title.add_theme_color_override("font_color", Color(1, 0.82, 0.35))
|
|
box.add_child(title)
|
|
|
|
bucks_label = Label.new()
|
|
bucks_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
bucks_label.add_theme_font_size_override("font_size", 26)
|
|
box.add_child(bucks_label)
|
|
|
|
list = ItemList.new()
|
|
list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
list.add_theme_font_size_override("font_size", 22)
|
|
list.item_selected.connect(_on_select)
|
|
box.add_child(list)
|
|
|
|
desc_label = Label.new()
|
|
desc_label.add_theme_font_size_override("font_size", 20)
|
|
desc_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
box.add_child(desc_label)
|
|
|
|
var row := HBoxContainer.new()
|
|
row.add_theme_constant_override("separation", 20)
|
|
box.add_child(row)
|
|
buy_btn = Button.new()
|
|
buy_btn.text = "BUY"
|
|
buy_btn.add_theme_font_size_override("font_size", 28)
|
|
buy_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
buy_btn.pressed.connect(_on_buy)
|
|
row.add_child(buy_btn)
|
|
var back := Button.new()
|
|
back.text = "BACK"
|
|
back.add_theme_font_size_override("font_size", 28)
|
|
back.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
back.pressed.connect(func() -> void:
|
|
get_tree().change_scene_to_file("res://src/main.tscn"))
|
|
row.add_child(back)
|
|
|
|
_refresh()
|
|
for i in list.item_count: # row 0 is a section header now
|
|
if list.is_item_selectable(i):
|
|
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:
|
|
var keep := list.get_selected_items()
|
|
list.clear()
|
|
entries.clear()
|
|
bucks_label.text = "BUCKS: $%d | SHED REP %d" % [Garage.bucks(), Garage.rep()]
|
|
var flt := Garage.faults(car_id)
|
|
if not flt.is_empty():
|
|
_header("--- IT'S BROKEN (fix it or pay up) ---")
|
|
for id in flt:
|
|
var c: Dictionary = Garage.FAULTS[id]
|
|
list.add_item("%-28s %s" % ["FIX: " + c["name"], "DIY IN THE SHED"])
|
|
entries.append({"kind": "fault", "id": id})
|
|
list.add_item("%-28s $%d" % [" ...or pay someone", c["shop"]])
|
|
entries.append({"kind": "shopfix", "id": id})
|
|
_header("--- BOLT-ON PARTS (this car) ---")
|
|
for id in Garage.CATALOGUE:
|
|
var c: Dictionary = Garage.CATALOGUE[id]
|
|
var tag := "[FITTED]" if Garage.has(car_id, id) else "$%d" % c["price"]
|
|
list.add_item("%-28s %s" % [c["name"], tag])
|
|
entries.append({"kind": "part", "id": id})
|
|
_header("--- THE SHED (tools; buy again to upgrade the tier) ---")
|
|
for id in Garage.TOOLS:
|
|
var c: Dictionary = Garage.TOOLS[id]
|
|
var tier := Garage.tool_tier(id)
|
|
var shown: String = c["tiers"][maxi(tier - 1, 0)]["name"]
|
|
var tag: String
|
|
if tier == 0:
|
|
tag = "$%d" % int(c["price"])
|
|
elif tier < 3:
|
|
tag = "[T%d] UP $%d" % [tier, Garage.tool_upgrade_price(id)]
|
|
else:
|
|
tag = "[T3 MAXED]"
|
|
list.add_item("%-28s %s" % [shown, 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])
|
|
|
|
func _entry_info(i: int) -> Dictionary:
|
|
var e: Dictionary = entries[i]
|
|
match e.get("kind"):
|
|
"fault":
|
|
var c: Dictionary = Garage.FAULTS[e["id"]]
|
|
return {"desc": c["desc"] + " (free, uses your tools and your dignity)",
|
|
"price": 0, "done": false, "locked": false}
|
|
"shopfix":
|
|
var c: Dictionary = Garage.FAULTS[e["id"]]
|
|
return {"desc": "Someone else's problem for $%d. No questions, no bolts left over." % c["shop"],
|
|
"price": c["shop"], "done": false, "locked": false}
|
|
"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"]]
|
|
var tier := Garage.tool_tier(e["id"])
|
|
var d: String = c["tiers"][mini(tier, 2)]["desc"]
|
|
if tier > 0 and tier < 3:
|
|
d = "NEXT: " + String(c["tiers"][tier]["name"]) + " -- " + d
|
|
return {"desc": d, "price": Garage.tool_upgrade_price(e["id"]),
|
|
"done": tier >= 3, "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:
|
|
var info := _entry_info(i)
|
|
if info.is_empty():
|
|
return
|
|
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"]) if int(info["price"]) > 0 \
|
|
else "GET UNDER IT (free)"
|
|
buy_btn.disabled = Garage.bucks() < int(info["price"])
|
|
|
|
func _on_buy() -> void:
|
|
var sel := list.get_selected_items()
|
|
if sel.is_empty():
|
|
return
|
|
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"])
|
|
"shopfix": ok = Garage.shop_fix(car_id, e["id"])
|
|
"fault":
|
|
# DIY: off to the wrench minigame; it clears the fault on completion
|
|
Garage.ui_fault = e["id"]
|
|
get_tree().change_scene_to_file("res://src/%s.tscn"
|
|
% String(Garage.FAULTS[e["id"]].get("scene", "wrench")))
|
|
return
|
|
if ok:
|
|
Sfx.shot_2d("clunk")
|
|
_refresh()
|
|
if sel[0] < list.item_count:
|
|
_on_select(sel[0])
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if event.is_action_pressed("ui_cancel"):
|
|
get_tree().change_scene_to_file("res://src/main.tscn")
|