#220: every berth past the first paid one costs 1.12x the one before it, on top of the flat per-vessel charge. A linear bill makes the tenth hull no more of a decision than the second; compounding weighs each addition against the whole fleet behind it. Shallow on purpose - the thirteenth berth costs about three and a half times the second, the twentieth about seven and a half. Fleet Offices gain a menu naming the whole bill per minute, a line per paying berth with its multiplier, and the arithmetic in words. It renders fleet_upkeep_breakdown(), which is the calculation the charging loop itself runs: a readout that summed independently could quote a bill the game does not take, which is the thing worth preventing here. The test pins that what the menu quotes for a berth is what that office is charged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
71 lines
2.7 KiB
GDScript
71 lines
2.7 KiB
GDScript
extends "res://ui/menus/ModalMenu.gd"
|
|
|
|
# Forgejo #220: what the fleet costs to keep, and WHY it costs that.
|
|
#
|
|
# A Fleet Office charges for the vessel in its berth every 25 seconds, and
|
|
# each berth costs more than the one before it. Neither of those is visible
|
|
# from the resource bar - the player watches a number fall and cannot tell
|
|
# which of their own decisions is draining it - so this states the bill and
|
|
# shows the arithmetic that produced it, line by line.
|
|
#
|
|
# Every number here comes from GameState.fleet_upkeep_breakdown(), which is
|
|
# the same calculation the charging loop runs. A readout that did its own sum
|
|
# could disagree with what is actually being taken, which is exactly the kind
|
|
# of "the game is lying to me" the ticket is about.
|
|
|
|
@onready var total_label: Label = $TotalLabel
|
|
@onready var detail_label: Label = $DetailLabel
|
|
@onready var formula_label: Label = $FormulaLabel
|
|
|
|
var building_id: int = 0
|
|
|
|
|
|
func open_for(next_building_id: int) -> void:
|
|
building_id = next_building_id
|
|
open_menu()
|
|
refresh()
|
|
|
|
|
|
func close_menu() -> void:
|
|
super.close_menu()
|
|
building_id = 0
|
|
|
|
|
|
func refresh() -> void:
|
|
if not visible:
|
|
return
|
|
var breakdown: Dictionary = GameState.fleet_upkeep_breakdown()
|
|
var berths: Array = breakdown.berths
|
|
var per_minute: Dictionary = breakdown.per_minute
|
|
|
|
if per_minute.is_empty():
|
|
total_label.text = "NOTHING TO KEEP"
|
|
else:
|
|
var parts: PackedStringArray = PackedStringArray()
|
|
for resource_type in per_minute:
|
|
parts.append("%.1f %s" % [float(per_minute[resource_type]),
|
|
GameState.resource_name(String(resource_type)).to_lower()])
|
|
total_label.text = "%s PER MINUTE" % " · ".join(parts)
|
|
|
|
# One line per paying berth: which hull, what it is charged, and the
|
|
# multiplier that got it there. An empty berth is charged nothing and is
|
|
# left out rather than listed at zero.
|
|
var lines: PackedStringArray = PackedStringArray()
|
|
for berth in berths:
|
|
var costs: PackedStringArray = PackedStringArray()
|
|
for resource_type in berth.costs:
|
|
costs.append("%d %s" % [int(berth.costs[resource_type]),
|
|
GameState.resource_name(String(resource_type)).to_lower()])
|
|
lines.append("BERTH %d %-16s %s (x%.2f)%s" % [
|
|
int(berth.slot), GameState.vessel_name(String(berth.vessel_type)),
|
|
" ".join(costs), float(berth.scale),
|
|
" — UNPAID" if bool(berth.starved) else "",
|
|
])
|
|
detail_label.text = "\n".join(lines) if not lines.is_empty() \
|
|
else "No berth is being charged. Build a Fleet Office and put a vessel in it."
|
|
|
|
formula_label.text = ("Each berth is charged every %ds: the vessel's build price "
|
|
+ "divided by %d, then multiplied by %.2f for every berth past the first "
|
|
+ "paid one. The first vessel is free.") % [
|
|
int(breakdown.charge_seconds), int(breakdown.divisor),
|
|
float(breakdown.growth)]
|