diff --git a/BuildController.gd b/BuildController.gd index ae3c914..18e81cd 100644 --- a/BuildController.gd +++ b/BuildController.gd @@ -159,9 +159,12 @@ var craft_message_until: int = 0 # current_building while any menu is open, so by the next frame the panel # would not know which building it belongs to. var transfer_building_id: int = 0 +var transfer_extractor_index: int = -1 var current_building: Node2D = null var pending_building: Node2D = null +var current_extractor_index: int = -1 +var pending_extractor_index: int = -1 var pending_time: float = 0.0 var context_actions_expanded: bool = false @@ -304,6 +307,8 @@ func _process(delta: float) -> void: or transfer_menu.visible or hangar_menu.visible: current_building = null pending_building = null + current_extractor_index = -1 + pending_extractor_index = -1 action_button.visible = false building_action_button.visible = false transfer_button.visible = false @@ -314,17 +319,25 @@ func _process(delta: float) -> void: # Debounced exactly like the enter-atmosphere prompt, so the action # button can't flicker while the fleet drifts on the interact boundary. var detected: Node2D = building_field.get_building_fleet_is_on() + var detected_extractor: int = -1 + if detected == null: + var planet: Node2D = _planet_under_fleet() + if planet != null: + detected_extractor = GameState.extractor_index_at(planet.planet_id) - if detected != pending_building: + if detected != pending_building or detected_extractor != pending_extractor_index: pending_building = detected + pending_extractor_index = detected_extractor pending_time = 0.0 else: pending_time += delta - if pending_time < prompt_debounce_time or pending_building == current_building: + if pending_time < prompt_debounce_time or (pending_building == current_building \ + and pending_extractor_index == current_extractor_index): return current_building = pending_building + current_extractor_index = pending_extractor_index context_actions_expanded = false hangar_button.visible = false # Every building can hold ore, so the transfer button rides alongside the @@ -332,13 +345,16 @@ func _process(delta: float) -> void: # turns into - the two are independent choices at the same structure. transfer_button.visible = false building_action_button.visible = false - action_button.visible = current_building != null + action_button.visible = current_building != null or current_extractor_index >= 0 if current_building != null: var actions: Array = _context_action_buttons() building_action_button.icon = int(current_building.definition.action_icon) building_action_button.queue_redraw() action_button.icon = building_action_button.icon if actions.size() == 1 else IconButton.Icon.ACTION_MENU action_button.queue_redraw() + elif current_extractor_index >= 0: + action_button.icon = IconButton.Icon.TRANSFER + action_button.queue_redraw() _layout_context_buttons() @@ -365,6 +381,8 @@ func _layout_context_buttons() -> void: func _context_action_buttons() -> Array: + if current_extractor_index >= 0: + return [transfer_button] if current_building == null: return [] var buttons: Array = [] @@ -384,7 +402,7 @@ func _button_for_building_action(action: String) -> IconButton: func _on_context_action_pressed() -> void: - if current_building == null: + if current_building == null and current_extractor_index < 0: return var actions: Array = _context_action_buttons() if actions.size() == 1: @@ -877,9 +895,10 @@ const ALL: int = 1 << 30 func _on_transfer_pressed() -> void: - if current_building == null: + if current_building == null and current_extractor_index < 0: return - transfer_building_id = current_building.building_id + transfer_building_id = current_building.building_id if current_building != null else 0 + transfer_extractor_index = current_extractor_index for bar in resource_bars: bar.building_id = transfer_building_id transfer_menu.visible = true @@ -892,15 +911,17 @@ func _on_transfer(to_building: bool, amount: int) -> void: func _on_resource_transfer(resource_type: String, to_building: bool, amount: int = ALL) -> void: - if transfer_building_id == 0: - transfer_status.text = "No building selected - reopen this panel from the building" + if transfer_building_id == 0 and transfer_extractor_index < 0: + transfer_status.text = "No resource store selected - reopen this panel from its action" return - var moved: int = GameState.transfer_resource_to_building( - transfer_building_id, resource_type, amount - ) if to_building else GameState.transfer_resource_to_fleet( - transfer_building_id, resource_type, amount - ) + var moved: int + if transfer_extractor_index >= 0: + moved = GameState.transfer_resource_to_extractor(transfer_extractor_index, resource_type, amount) \ + if to_building else GameState.transfer_resource_from_extractor(transfer_extractor_index, resource_type, amount) + else: + moved = GameState.transfer_resource_to_building(transfer_building_id, resource_type, amount) \ + if to_building else GameState.transfer_resource_to_fleet(transfer_building_id, resource_type, amount) var resource_name: String = GameState.resource_name(resource_type).to_lower() # Nothing moving is the common case at an empty end, and a silent no-op @@ -922,6 +943,8 @@ func _on_gas_transfer(to_building: bool) -> void: func _transfer_place_name() -> String: + if transfer_extractor_index >= 0: + return "the extractor" var b: Dictionary = GameState.find_building(transfer_building_id) if b.is_empty(): return "this building" @@ -933,6 +956,9 @@ func _transfer_place_name() -> String: func _refresh_transfer() -> void: + if transfer_extractor_index >= 0: + transfer_list.set_extractor_destination(transfer_extractor_index, "Planet extractor") + return var building: Dictionary = GameState.find_building(transfer_building_id) var heading: String = "This building" if not building.is_empty() and building.type == GameState.BUILDING_MOTHERSHIP: diff --git a/GameState.gd b/GameState.gd index b3d700b..c6687ee 100644 --- a/GameState.gd +++ b/GameState.gd @@ -896,6 +896,14 @@ func has_extractor_at(planet_key: String) -> bool: return false +func extractor_index_at(planet_key: String) -> int: + for i in range(extractors.size()): + var extractor: Dictionary = extractors[i] + if String(extractor.get("planet_id", extractor.get("planet_name", ""))) == planet_key: + return i + return -1 + + signal extractor_added(planet_name: String) @@ -924,6 +932,24 @@ func extractor_stock(index: int) -> int: return _extractor_inventory(extractor).amount(String(extractor.get("resource_type", RESOURCE_ORE))) +func extractor_resource(index: int, resource_type: String) -> int: + if index < 0 or index >= extractors.size(): + return 0 + return _extractor_inventory(extractors[index]).amount(resource_type) + + +func transfer_resource_to_extractor(index: int, resource_type: String, amount: int) -> int: + if index < 0 or index >= extractors.size(): + return 0 + return fleet_inventory.transfer_to(_extractor_inventory(extractors[index]), resource_type, amount) + + +func transfer_resource_from_extractor(index: int, resource_type: String, amount: int) -> int: + if index < 0 or index >= extractors.size(): + return 0 + return _extractor_inventory(extractors[index]).transfer_to(fleet_inventory, resource_type, amount) + + func _extractor_inventory(extractor: Dictionary) -> RefCounted: var inventory: RefCounted = extractor.get("inventory") as RefCounted if inventory == null: diff --git a/ResourceTransferList.gd b/ResourceTransferList.gd index 859f0f1..3424191 100644 --- a/ResourceTransferList.gd +++ b/ResourceTransferList.gd @@ -15,6 +15,7 @@ const ALL_AMOUNT: int = 1 << 30 const AMOUNT_OPTIONS: Array[int] = [1, 5, 25, ALL_AMOUNT] var building_id: int = 0 +var extractor_index: int = -1 var _destination_heading: Label var _rows: Dictionary = {} @@ -134,6 +135,15 @@ func _refresh_amount_controls(row: Dictionary) -> void: func set_destination(id: int, heading: String) -> void: building_id = id + extractor_index = -1 + if _destination_heading != null: + _destination_heading.text = heading.to_upper() + refresh() + + +func set_extractor_destination(index: int, heading: String) -> void: + building_id = 0 + extractor_index = index if _destination_heading != null: _destination_heading.text = heading.to_upper() refresh() @@ -145,7 +155,8 @@ func refresh() -> void: if row.is_empty(): continue var fleet_amount: int = GameState.fleet_resource(resource_type) - var local_amount: int = GameState.building_resource(building_id, resource_type) + var local_amount: int = GameState.extractor_resource(extractor_index, resource_type) \ + if extractor_index >= 0 else GameState.building_resource(building_id, resource_type) row.fleet_amount.text = str(fleet_amount) row.local_amount.text = str(local_amount) row.unload.enabled = fleet_amount > 0 diff --git a/tests/_extractor_transfer_preview.gd b/tests/_extractor_transfer_preview.gd new file mode 100644 index 0000000..6ccbe3e --- /dev/null +++ b/tests/_extractor_transfer_preview.gd @@ -0,0 +1,24 @@ +extends Node2D + + +func _ready() -> void: + GameState.extractors.clear() + GameState.transporters.clear() + GameState.fleet_cargo = 9 + GameState.fleet_gas_cargo = 4 + var main: Node = load("res://Main.tscn").instantiate() + add_child(main) + var planet: Node2D + for planets in main.get_node("PlanetField").populated_chunks.values(): + if not planets.is_empty() and is_instance_valid(planets[0]): + planet = planets[0] + break + if planet == null: + return + GameState.add_extractor(planet.planet_name, planet.position, planet.resource_type, planet.planet_id) + var index: int = GameState.extractors.size() - 1 + GameState.extractors[index].inventory.deposit(planet.resource_type, 38) + var controller: Node = main.get_node("BuildController") + controller.transfer_extractor_index = index + main.get_node("UI/TransferMenu").visible = true + controller._refresh_transfer() diff --git a/tests/_extractor_transfer_preview.tscn b/tests/_extractor_transfer_preview.tscn new file mode 100644 index 0000000..b269dab --- /dev/null +++ b/tests/_extractor_transfer_preview.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/_extractor_transfer_preview.gd" id="1"] +[node name="ExtractorTransferPreview" type="Node2D"] +script = ExtResource("1") diff --git a/tests/test_extractor_transfer.gd b/tests/test_extractor_transfer.gd new file mode 100644 index 0000000..97151e4 --- /dev/null +++ b/tests/test_extractor_transfer.gd @@ -0,0 +1,74 @@ +extends SceneTree + +var state: Node +var scene: Node +var fleet: Node +var controller: Node +var planet: Node2D +var extractor_index: int = -1 +var resource_type: String +var stage: int = 0 +var elapsed: float = 0.0 + + +func _initialize() -> void: + state = root.get_node("GameState") + state.extractors.clear() + state.transporters.clear() + state.fleet_cargo = 0 + state.fleet_gas_cargo = 0 + scene = load("res://Main.tscn").instantiate() + root.add_child(scene) + fleet = scene.get_node("Fleet") + controller = scene.get_node("BuildController") + + +func _process(delta: float) -> bool: + elapsed += delta + match stage: + 0: + scene.get_node("AsteroidField").set_process(false) + scene.get_node("EnemySpawner").set_process(false) + for planets in scene.get_node("PlanetField").populated_chunks.values(): + if not planets.is_empty() and is_instance_valid(planets[0]): + planet = planets[0] + break + if planet == null: + return _fail("no nearby generated planet available") + resource_type = planet.resource_type + state.add_extractor(planet.planet_name, planet.position, resource_type, planet.planet_id) + extractor_index = state.extractors.size() - 1 + state.extractors[extractor_index].inventory.deposit(resource_type, 12) + for ship in fleet.ships: + ship.position = planet.position + fleet.move_to(planet.position) + elapsed = 0.0 + stage = 1 + return false + 1: + if elapsed < 1.2: + return false + if controller.current_extractor_index != extractor_index or not controller.action_button.visible: + return _fail("extractor did not expose a contextual action") + if not scene.get_node("UI/EnterAtmosphereButton").visible: + return _fail("extractor action hid the separate landing action") + controller._on_context_action_pressed() + if not controller.transfer_menu.visible or controller.transfer_list.extractor_index != extractor_index: + return _fail("transfer panel did not bind to the extractor") + var before: int = state.fleet_resource(resource_type) + controller._on_resource_transfer(resource_type, false, 5) + if state.fleet_resource(resource_type) != before + 5: + return _fail("fleet did not fetch five resources from extractor") + if state.extractor_resource(extractor_index, resource_type) < 7: + return _fail("extractor transfer removed too much stock") + print("fleet fetched %s from extractor while landing remained available" % resource_type) + print("TEST_EXTRACTOR_TRANSFER: PASS") + quit(0) + return true + return false + + +func _fail(message: String) -> bool: + print("TEST_EXTRACTOR_TRANSFER: FAIL (%s)" % message) + quit(1) + return true diff --git a/tests/test_planet_unique_identity.gd.uid b/tests/test_planet_unique_identity.gd.uid new file mode 100644 index 0000000..ebfc3bc --- /dev/null +++ b/tests/test_planet_unique_identity.gd.uid @@ -0,0 +1 @@ +uid://0ghcdnlq2vd2