Charge more for each berth, and show the bill
#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
This commit is contained in:
parent
cc71811d89
commit
2bb46cba94
9 changed files with 369 additions and 5 deletions
71
ui/menus/FleetUpkeepMenu.gd
Normal file
71
ui/menus/FleetUpkeepMenu.gd
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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)]
|
||||
77
ui/menus/FleetUpkeepMenu.tscn
Normal file
77
ui/menus/FleetUpkeepMenu.tscn
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/IconButton.gd" id="icon"]
|
||||
[ext_resource type="Script" path="res://ui/menus/FleetUpkeepMenu.gd" id="menu"]
|
||||
|
||||
[node name="FleetUpkeepMenu" type="Control" groups=["modal_menus"]]
|
||||
visible = false
|
||||
mouse_filter = 2
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("menu")
|
||||
|
||||
[node name="Backdrop" type="ColorRect" parent="."]
|
||||
mouse_filter = 2
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.03, 0.03, 0.08, 0.96)
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="."]
|
||||
mouse_filter = 2
|
||||
anchor_right = 1.0
|
||||
offset_left = 24.0
|
||||
offset_top = 24.0
|
||||
offset_right = -180.0
|
||||
offset_bottom = 100.0
|
||||
theme_override_font_sizes/font_size = 40
|
||||
text = "FLEET UPKEEP"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CloseButton" type="Control" parent="."]
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -140.0
|
||||
offset_top = 24.0
|
||||
offset_right = -24.0
|
||||
offset_bottom = 140.0
|
||||
script = ExtResource("icon")
|
||||
icon = 1
|
||||
glow_color = Color(0.55, 0.6, 1, 1)
|
||||
|
||||
[node name="TotalLabel" type="Label" parent="."]
|
||||
mouse_filter = 2
|
||||
anchor_right = 1.0
|
||||
offset_left = 32.0
|
||||
offset_top = 150.0
|
||||
offset_right = -32.0
|
||||
offset_bottom = 220.0
|
||||
theme_override_font_sizes/font_size = 32
|
||||
theme_override_colors/font_color = Color(1, 0.85, 0.35, 1)
|
||||
text = ""
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="DetailLabel" type="Label" parent="."]
|
||||
mouse_filter = 2
|
||||
anchor_right = 1.0
|
||||
offset_left = 32.0
|
||||
offset_top = 250.0
|
||||
offset_right = -32.0
|
||||
offset_bottom = 860.0
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_colors/font_color = Color(0.72, 0.88, 0.96, 1)
|
||||
text = ""
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="FormulaLabel" type="Label" parent="."]
|
||||
mouse_filter = 2
|
||||
anchor_right = 1.0
|
||||
offset_left = 40.0
|
||||
offset_top = 880.0
|
||||
offset_right = -40.0
|
||||
offset_bottom = 1060.0
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_colors/font_color = Color(0.55, 0.78, 0.86, 1)
|
||||
text = ""
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
Loading…
Reference in a new issue