diff --git a/core/GameState.gd b/core/GameState.gd index baa4d24..da2531a 100644 --- a/core/GameState.gd +++ b/core/GameState.gd @@ -1683,6 +1683,27 @@ func vessel_is_fleet_eligible(vessel: String) -> bool: return definition != null and definition.fleet_eligible +# What a ship crafter can actually be asked for right now. #216 made support +# craft orderable at all; this is the narrower question of whether THIS one +# can be ordered today. +# +# A support vessel is only worth building once there is somewhere to put it: +# a transporter with no hub and a repair drone with no workshop have nowhere +# to be assigned, so they would sit in the hangar as spent resources. The +# structure that uses one is therefore part of its availability, the same way +# research is for a fleet hull. +func vessel_can_be_crafted(vessel: String) -> bool: + var definition: Resource = vessel_definition(vessel) + if definition == null or not vessel_is_unlocked(vessel): + return false + if not (definition.fleet_eligible or definition.support_craftable): + return false + match vessel: + VESSEL_TRANSPORTER: return hub_count() > 0 + REPAIR_DRONE: return has_workshop() + return true + + func vessel_is_unlocked(vessel: String) -> bool: var definition: Resource = vessel_definition(vessel) if definition == null: @@ -1981,11 +2002,7 @@ func queue_order(id: int, product: String = PRODUCT_SHIP) -> bool: var b: Dictionary = find_building(id) if b.is_empty() or not can_produce(b.type): return false - var definition: Resource = vessel_definition(product) - # #216: support craft are orderable here too. They never join the fleet, - # so "can the crafter make it" is its own question now. - if definition == null or not vessel_is_unlocked(product) \ - or not (definition.fleet_eligible or definition.support_craftable): + if not vessel_can_be_crafted(product): return false (b.orders as Array).append({"product": product, "elapsed": 0.0, "started": false}) return true diff --git a/tests/_station_menu_preview.gd b/tests/_station_menu_preview.gd new file mode 100644 index 0000000..2745fdb --- /dev/null +++ b/tests/_station_menu_preview.gd @@ -0,0 +1,29 @@ +extends Node2D + +# Preview, not a test - #216's crew menu on a transport hub: how many are +# assigned, how many stand there, and what each of them has left. +# +# bash tests/screenshot.sh res://tests/_station_menu_preview.tscn shots/station_menu.png 3 720x1280 + + +func _ready() -> void: + GameState.buildings.clear() + GameState.extractors.clear() + GameState.transporters.clear() + GameState.reset_progression() + GameState.intro_active = false + GameState.unlocked = ["shipyards", "logistics", "freight", "freight_yards"] + GameState.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO) + var hub_id: int = GameState.add_building(GameState.BUILDING_TRANSPORT_HUB, + Vector2(700.0, 0.0)) + load("res://tests/_staff.gd").staff(GameState, hub_id, GameState.hub_capacity()) + # Worn to different degrees, so the bars have something to say. + var wear: Array = [GameState.TRANSPORTER_TRIPS, 9, 2] + for i in range(GameState.transporters.size()): + GameState.transporters[i].trips_left = int(wear[i % wear.size()]) + + var main: Node = load("res://world/Main.tscn").instantiate() + add_child(main) + main.get_node("AsteroidField").set_process(false) + main.get_node("EnemySpawner").set_process(false) + main.get_node("UI/StationMenu").open_for(hub_id) diff --git a/tests/_station_menu_preview.tscn b/tests/_station_menu_preview.tscn new file mode 100644 index 0000000..5bf17d3 --- /dev/null +++ b/tests/_station_menu_preview.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/_station_menu_preview.gd" id="1"] + +[node name="StationMenuPreview" type="Node2D"] +script = ExtResource("1") diff --git a/tests/test_carrier_vessel.gd b/tests/test_carrier_vessel.gd index 72892b5..5ef0959 100644 --- a/tests/test_carrier_vessel.gd +++ b/tests/test_carrier_vessel.gd @@ -107,10 +107,13 @@ func _process(delta: float) -> bool: print("unresearched carrier is unbuildable, as expected") # --- D. locked state in the crafter UI ------------------------- + # An unresearched hull is now ABSENT from the crafter rather than + # drawn greyed: a row nobody can press says nothing its absence + # does not. What must hold is that it cannot be ordered from here. controller.craft_building_id = crafter_id controller._refresh_craft_status() - if controller.craft_menu.vessel_buttons[FLEET_UI_INDEX].enabled: - return _fail("a locked carrier shows enabled in the crafter menu") + if controller.craft_menu.shown_types.has(GameState.VESSEL_CARRIER): + return _fail("an unresearched carrier is still offered in the crafter menu") stage = 1 elapsed = 0.0 diff --git a/tests/test_crafter_availability.gd b/tests/test_crafter_availability.gd new file mode 100644 index 0000000..60c90f2 --- /dev/null +++ b/tests/test_crafter_availability.gd @@ -0,0 +1,92 @@ +extends SceneTree + +# A ship crafter offers what can actually be ordered there, and nothing else. +# +# Two rules: +# - a hull whose research is not done is not on offer (it never was +# orderable; it was drawn greyed, which is a row that says nothing the +# absence does not) +# - a support vessel needs the structure that USES it. #216 made +# transporters and repair drones ordinary craft, and one with no hub or +# no workshop has nowhere to be assigned - it would sit in the hangar as +# spent resources. +# +# Covers: +# A. with no hub and no workshop, neither support craft can be ordered +# B. building a hub offers the transporter, and only the transporter +# C. building a workshop offers the drone +# D. the model refuses the order too, not just the menu +# E. an unresearched fleet hull is not on offer either + +var state: Node +var crafter_id: int + + +func _initialize() -> void: + state = root.get_node("GameState") + state.buildings.clear() + state.extractors.clear() + state.transporters.clear() + state.repair_drones.clear() + state.reset_progression() + state.rocks = 900 + state.unlocked = ["shipyards", "logistics", "freight"] + crafter_id = state.add_building(state.BUILDING_SHIP_CRAFTER, Vector2(400.0, 0.0)) + + +func _process(_delta: float) -> bool: + # --- A. nothing to assign them to, so nothing on offer ------------ + if state.vessel_can_be_crafted(GameState.VESSEL_TRANSPORTER): + return _fail("a transporter is orderable with no hub to put it in") + if state.vessel_can_be_crafted(GameState.REPAIR_DRONE): + return _fail("a repair drone is orderable with no workshop to put it in") + # --- D. and the model refuses, not merely the menu ---------------- + if state.queue_order(crafter_id, GameState.VESSEL_TRANSPORTER): + return _fail("the crafter queued a transporter with nowhere to send it") + print("no hub, no workshop: neither support craft is on offer") + + # --- B. a hub offers the transporter ------------------------------ + var hub_id: int = state.add_building(state.BUILDING_TRANSPORT_HUB, Vector2(900.0, 0.0)) + if not state.vessel_can_be_crafted(GameState.VESSEL_TRANSPORTER): + return _fail("a hub is standing and the transporter is still not orderable") + if state.vessel_can_be_crafted(GameState.REPAIR_DRONE): + return _fail("a transport hub made the repair drone orderable") + if not state.queue_order(crafter_id, GameState.VESSEL_TRANSPORTER): + return _fail("the crafter refused a transporter with a hub standing") + state.find_building(crafter_id).orders.clear() + print("a hub offers the transporter, and only that") + + # --- C. and a workshop the drone ---------------------------------- + state.add_building(GameState.BUILDING_WORKSHOP, Vector2(-900.0, 0.0)) + if not state.vessel_can_be_crafted(GameState.REPAIR_DRONE): + return _fail("a workshop is standing and the drone is still not orderable") + print("a workshop offers the repair drone") + + # Taking the hub away takes the offer with it - there is nowhere to + # assign one again. + state.remove_buildings_of_type(state.BUILDING_TRANSPORT_HUB) + if state.vessel_can_be_crafted(GameState.VESSEL_TRANSPORTER): + return _fail("the last hub came down and the transporter is still on offer") + print("the last hub down: the transporter is off the menu again") + + # --- E. an unresearched hull is not on offer ---------------------- + var locked: String = "" + for definition in load("res://vessels/VesselDefinitions.gd").fleet(): + if not state.vessel_is_unlocked(String(definition.definition_id)): + locked = String(definition.definition_id) + break + if locked == "": + return _fail("every fleet hull is already unlocked, so this proves nothing") + if state.vessel_can_be_crafted(locked): + return _fail("'%s' is unresearched and still on offer" % locked) + print("an unresearched hull ('%s') is not on offer" % locked) + + print("TEST_CRAFTER_AVAILABILITY: PASS") + quit(0) + return true + + +func _fail(message: String) -> bool: + print("TEST_CRAFTER_AVAILABILITY: FAIL (%s)" % message) + quit(1) + return true diff --git a/tests/test_menu_polish.gd b/tests/test_menu_polish.gd index 8df93b9..2b90853 100644 --- a/tests/test_menu_polish.gd +++ b/tests/test_menu_polish.gd @@ -44,6 +44,17 @@ func _initialize() -> void: # Office. This test flies more than one vessel, so it needs the offices # standing before the fleet spawns into them. load("res://tests/_berths.gd").grant(root.get_node("GameState"), 6) + # The crafter shows only what can be ordered there, so the vessels this + # test inspects have to be orderable: every hull researched, and a hub and + # a workshop standing for the two support craft. Section E in particular + # needs a TWO-resource price on offer - it is about a price that used to + # run off the end of its strip - and every ore-only hull would prove + # nothing. + for definition in load("res://vessels/VesselDefinitions.gd").craftable(): + if String(definition.unlock_id) != "": + game_state.vessel_levels[String(definition.unlock_id)] = 1 + game_state.add_building(GameState.BUILDING_TRANSPORT_HUB, Vector2(4000.0, 4000.0)) + game_state.add_building(GameState.BUILDING_WORKSHOP, Vector2(4600.0, 4000.0)) scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene @@ -72,10 +83,18 @@ func _process(delta: float) -> bool: # exact match rather than by hunting for resource words in it: a # Gas Bomber is named after a resource and always will be, and # what #111 removed was the PRICE spelled out underneath. - var fleet_defs: Array = VesselDefinitions.craftable() - for i in range(mini(controller.craft_menu.vessel_labels.size(), fleet_defs.size())): + # Read against what is ON OFFER, not the whole catalogue: a hull + # that is unresearched, or a support craft with nowhere to be + # assigned, is hidden rather than greyed, so slot i holds the ith + # OFFERED vessel and the hidden slots keep whatever text the + # scene was authored with. + var offered: PackedStringArray = controller.craft_menu.shown_types + if offered.is_empty(): + return _fail("the crafter offers nothing at all, so this proves nothing") + for i in range(offered.size()): var caption: String = String(controller.craft_menu.vessel_labels[i].text) - var expected: String = String(fleet_defs[i].display_name) + var expected: String = String(VesselDefinitions.get_definition( + String(offered[i])).display_name) if caption != expected: return _fail("a craft caption reads \"%s\", expected just \"%s\"" \ % [caption, expected]) @@ -97,10 +116,10 @@ func _process(delta: float) -> bool: controller.craft_menu.vessel_buttons.size()]) var priced: int = 0 var stacked: int = 0 - for i in range(controller.craft_menu.price_tags.size()): - # #216 added the two support craft to the crafter, so the rows - # are craftable() now - fleet hulls first, then those. - var definition: Resource = VesselDefinitions.craftable()[i] + # Over the OFFERED vessels, for the same reason as the captions + # above: a hidden strip belongs to no vessel. + for i in range(mini(offered.size(), controller.craft_menu.price_tags.size())): + var definition: Resource = VesselDefinitions.get_definition(String(offered[i])) var costs: Dictionary = game_state.product_costs(definition.definition_id) var strip: Control = controller.craft_menu.price_tags[i] if costs.is_empty(): diff --git a/tests/test_station_condition_readout.gd b/tests/test_station_condition_readout.gd new file mode 100644 index 0000000..7642bc8 --- /dev/null +++ b/tests/test_station_condition_readout.gd @@ -0,0 +1,95 @@ +extends SceneTree + +# The hub and workshop menus name what is left in each vessel they hold. +# +# #216 gave them crews that wear out, and the whole point of that is a +# decision - "build another before this one is spent" - which the player can +# only make if they can read the figure. A bar alone said "getting low"; the +# number says how low. +# +# Covers: +# A. a hub lists every transporter it holds, worst first, with runs left +# B. a workshop says REPAIRS rather than RUNS, in its own capacity +# C. an empty structure says nothing rather than "0 / 0" + +var state: Node +var scene: Node +var menu: Control +var hub_id: int +var workshop_id: int +var stage: int = 0 +var elapsed: float = 0.0 + + +func _initialize() -> void: + state = root.get_node("GameState") + state.buildings.clear() + state.extractors.clear() + state.transporters.clear() + state.repair_drones.clear() + state.reset_progression() + state.reset_new_game() + state.intro_active = false + state.reset_quests() + state.unlocked = ["shipyards", "logistics", "freight"] + hub_id = state.add_building(state.BUILDING_TRANSPORT_HUB, Vector2(900.0, 0.0)) + workshop_id = state.add_building(GameState.BUILDING_WORKSHOP, Vector2(-900.0, 0.0)) + scene = load("res://world/Main.tscn").instantiate() + root.add_child(scene) + current_scene = scene + menu = scene.get_node("UI/StationMenu") + + +func _process(delta: float) -> bool: + elapsed += delta + if elapsed < 0.3: + return false + match stage: + 0: + # --- C. nothing held, nothing claimed -------------------- + menu.open_for(hub_id) + if menu.condition_list.text.strip_edges() != "": + return _fail("an empty hub lists a crew: '%s'" % menu.condition_list.text) + print("an empty hub lists nothing") + + # --- A. worst first, with the figure --------------------- + var staff: Script = load("res://tests/_staff.gd") + staff.staff(state, hub_id, 2) + state.transporters[0].trips_left = 4 + state.transporters[1].trips_left = GameState.TRANSPORTER_TRIPS + menu.refresh() + var listed: String = menu.condition_list.text + if not listed.contains("4 / %d RUNS" % GameState.TRANSPORTER_TRIPS): + return _fail("a hub holding a worn transporter does not say so: '%s'" % listed) + if not listed.contains("%d / %d RUNS" % [GameState.TRANSPORTER_TRIPS, + GameState.TRANSPORTER_TRIPS]): + return _fail("the fresh transporter is missing from '%s'" % listed) + # Worst first: the nearly-spent one is the decision. + if listed.find("4 / ") > listed.find("%d / " % GameState.TRANSPORTER_TRIPS): + return _fail("the worn transporter is listed after the fresh one") + print("hub lists: %s" % listed.replace("\n", " | ")) + elapsed = 0.0 + stage = 1 + return false + 1: + # --- B. a workshop counts repairs, in its own capacity ---- + load("res://tests/_staff.gd").staff(state, workshop_id, 1) + state.repair_drones[0].repairs_left = 3 + menu.open_for(workshop_id) + var drone_text: String = menu.condition_list.text + if not drone_text.contains("3 / %d REPAIRS" % GameState.DRONE_REPAIRS): + return _fail("a workshop does not report repairs left: '%s'" % drone_text) + if drone_text.contains("RUNS"): + return _fail("a workshop reports its drones in RUNS: '%s'" % drone_text) + print("workshop lists: %s" % drone_text.replace("\n", " | ")) + + print("TEST_STATION_CONDITION_READOUT: PASS") + quit(0) + return true + return false + + +func _fail(message: String) -> bool: + print("TEST_STATION_CONDITION_READOUT: FAIL (%s)" % message) + quit(1) + return true diff --git a/ui/menus/CraftMenu.gd b/ui/menus/CraftMenu.gd index c0bff46..0dd0d3c 100644 --- a/ui/menus/CraftMenu.gd +++ b/ui/menus/CraftMenu.gd @@ -33,6 +33,8 @@ const PRICE_OFFSET := 154.0 @onready var queue_slots: Array = [$QueueSlot0, $QueueSlot1, $QueueSlot2, $QueueSlot3] var building_id: int = 0 +# Which vessel each visible slot is offering, in order. Empty slots are hidden. +var shown_types: PackedStringArray = PackedStringArray() var message: String = "" var message_until: int = 0 var price_tags: Array = [] @@ -41,10 +43,11 @@ var price_tags: Array = [] func _ready() -> void: super._ready() _build_price_tags() - for i in range(mini(vessel_buttons.size(), VESSEL_DEFINITIONS.craftable().size())): - var definition: Resource = VESSEL_DEFINITIONS.craftable()[i] - vessel_buttons[i].pressed.connect( - func(): craft_requested.emit(String(definition.definition_id))) + # Bound to the SLOT, not to a definition: which vessel sits in slot i + # changes as things become available, so a handler that captured one at + # startup would order the wrong hull the moment the list shortened. + for i in range(vessel_buttons.size()): + vessel_buttons[i].pressed.connect(func(): _on_vessel_pressed(i)) for i in range(queue_slots.size()): queue_slots[i].pressed.connect(func(): queue_cancel_requested.emit(i)) _layout_entries() @@ -70,17 +73,34 @@ func say(next_message: String) -> void: func refresh() -> void: status.text = "" - var definitions: Array = VESSEL_DEFINITIONS.craftable() - for i in range(mini(definitions.size(), vessel_buttons.size())): - var definition: Resource = definitions[i] + # Only what can actually be ordered here today: a hull whose research is + # done, and - for the support craft - one with somewhere to be assigned. + # Hidden rather than greyed, the way the build menu treats a structure + # that has not been researched; a row nobody can press is clutter that + # says nothing the absence does not. + shown_types.clear() + for definition in VESSEL_DEFINITIONS.craftable(): + if GameState.vessel_can_be_crafted(String(definition.definition_id)): + shown_types.append(String(definition.definition_id)) + + for i in range(vessel_buttons.size()): + var on_offer: bool = i < shown_types.size() + vessel_buttons[i].visible = on_offer + vessel_labels[i].visible = on_offer + if i < price_tags.size(): + price_tags[i].visible = on_offer + if not on_offer: + continue + var definition: Resource = VESSEL_DEFINITIONS.get_definition(String(shown_types[i])) var button: Control = vessel_buttons[i] vessel_labels[i].text = String(definition.display_name) if i < price_tags.size(): price_tags[i].set_costs(GameState.product_costs(definition.definition_id)) button.icon = definition.icon button.glow_color = definition.glow_color - button.enabled = GameState.vessel_is_unlocked(definition.definition_id) + button.enabled = true button.queue_redraw() + _layout_entries() refresh_queue() if message != "": if Time.get_ticks_msec() < message_until: @@ -133,8 +153,17 @@ func row_height() -> float: return PRICE_OFFSET + PRICE_HEIGHT +func _on_vessel_pressed(slot: int) -> void: + if slot < 0 or slot >= shown_types.size(): + return + craft_requested.emit(String(shown_types[slot])) + + func _layout_entries() -> void: - var rows: int = maxi(int(ceil(float(vessel_buttons.size()) / float(COLUMNS))), 1) + # Laid out over what is ON OFFER, so a short list is centred and packed + # rather than leaving holes where a hidden hull used to be. + var offered: int = maxi(shown_types.size(), 1) + var rows: int = maxi(int(ceil(float(offered) / float(COLUMNS))), 1) var band: Vector2 = MENU_LAYOUT.grid_band(status.offset_bottom, size.y) for i in range(vessel_buttons.size()): var row: int = i / COLUMNS diff --git a/ui/menus/StationMenu.gd b/ui/menus/StationMenu.gd index c639bbf..f02dcbf 100644 --- a/ui/menus/StationMenu.gd +++ b/ui/menus/StationMenu.gd @@ -12,10 +12,13 @@ extends "res://ui/menus/ModalMenu.gd" # it does, so the bar row below shows what each of them has left, worst first, # which is the number that decides whether another needs building. -const BAR_WIDTH: float = 54.0 -const BAR_HEIGHT: float = 18.0 -const BAR_GAP: float = 10.0 -const BAR_TOP: float = 470.0 +# One bar, wide, for the vessel closest to being spent - the one that decides +# whether another needs building. The per-vessel figures are listed as TEXT +# below it: a row of small bars was unreadable at the size a phone draws this +# menu, and "cycles left" is a number the player wants to actually read. +const BAR_WIDTH: float = 420.0 +const BAR_HEIGHT: float = 40.0 +const BAR_TOP: float = 494.0 const WORN_COLOR: Color = Color(1.0, 0.35, 0.25) const FRESH_COLOR: Color = Color(0.5, 1.0, 0.55) const PLATE_COLOR: Color = Color(0.02, 0.02, 0.06, 0.9) @@ -25,6 +28,7 @@ const FRAME_COLOR: Color = Color(0.85, 0.96, 1.0, 0.85) @onready var assigned_label: Label = $AssignedLabel @onready var status: Label = $StatusLabel @onready var condition_label: Label = $ConditionLabel +@onready var condition_list: Label = $ConditionList @onready var less_button: IconButton = $LessButton @onready var more_button: IconButton = $MoreButton @@ -83,7 +87,20 @@ func refresh() -> void: status.text = "%d on order - sent from the mothership hangar as they are built." % (wanted - here) var left: Array = GameState.station_condition(building_id) - condition_label.text = "CONDITION" if not left.is_empty() else "" + var full: int = maxi(GameState.station_work_capacity(building_id), 1) + var unit: String = "RUNS" if vessel_type == GameState.VESSEL_TRANSPORTER else "REPAIRS" + if left.is_empty(): + condition_label.text = "" + condition_list.text = "" + else: + condition_label.text = "CONDITION - %s LEFT" % unit + # Worst first, which is the order station_condition() answers in: the + # one about to be spent is the one being decided about. + var lines: PackedStringArray = PackedStringArray() + for i in range(left.size()): + lines.append("%s %d %d / %d %s" % [ + name.to_upper(), i + 1, int(left[i]), full, unit]) + condition_list.text = "\n".join(lines) queue_redraw() @@ -98,13 +115,13 @@ func _draw() -> void: if left.is_empty(): return var full: int = maxi(GameState.station_work_capacity(building_id), 1) - var total: float = float(left.size()) * BAR_WIDTH + float(left.size() - 1) * BAR_GAP - var x: float = (size.x - total) * 0.5 - for remaining in left: - var fraction: float = clampf(float(remaining) / float(full), 0.0, 1.0) - draw_rect(Rect2(x - 2.0, BAR_TOP - 2.0, BAR_WIDTH + 4.0, BAR_HEIGHT + 4.0), - PLATE_COLOR) - draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH * fraction, BAR_HEIGHT), - WORN_COLOR.lerp(FRESH_COLOR, fraction)) - draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH, BAR_HEIGHT), FRAME_COLOR, false, 1.5) - x += BAR_WIDTH + BAR_GAP + # The worst of them. station_condition() answers worst first. + var fraction: float = clampf(float(left[0]) / float(full), 0.0, 1.0) + var x: float = (size.x - BAR_WIDTH) * 0.5 + draw_rect(Rect2(x - 3.0, BAR_TOP - 3.0, BAR_WIDTH + 6.0, BAR_HEIGHT + 6.0), + PLATE_COLOR) + draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH, BAR_HEIGHT), + Color(FRESH_COLOR.r, FRESH_COLOR.g, FRESH_COLOR.b, 0.16)) + draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH * fraction, BAR_HEIGHT), + WORN_COLOR.lerp(FRESH_COLOR, fraction)) + draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH, BAR_HEIGHT), FRAME_COLOR, false, 2.0) diff --git a/ui/menus/StationMenu.tscn b/ui/menus/StationMenu.tscn index 965bfbe..ce5145c 100644 --- a/ui/menus/StationMenu.tscn +++ b/ui/menus/StationMenu.tscn @@ -75,14 +75,25 @@ glow_color = Color(0.95, 0.55, 0.85, 1) mouse_filter = 2 anchor_right = 1.0 offset_left = 36.0 -offset_top = 420.0 +offset_top = 430.0 offset_right = -36.0 -offset_bottom = 462.0 -theme_override_font_sizes/font_size = 22 +offset_bottom = 480.0 +theme_override_font_sizes/font_size = 28 theme_override_colors/font_color = Color(0.55, 0.78, 0.86, 1) text = "" horizontal_alignment = 1 +[node name="ConditionList" type="Label" parent="."] +mouse_filter = 2 +anchor_right = 1.0 +offset_left = 36.0 +offset_top = 560.0 +offset_right = -36.0 +offset_bottom = 940.0 +theme_override_font_sizes/font_size = 30 +text = "" +horizontal_alignment = 1 + [node name="StatusLabel" type="Label" parent="."] mouse_filter = 2 anchor_right = 1.0