Compose buildings from definitions and capabilities
This commit is contained in:
parent
1721edd123
commit
0504b24d8d
46 changed files with 1091 additions and 878 deletions
|
|
@ -1,5 +1,7 @@
|
|||
extends Node2D
|
||||
|
||||
const BUILDING_DEFINITIONS = preload("res://BuildingDefinitions.gd")
|
||||
|
||||
# Space-side building UI. Owns:
|
||||
# - the build menu (place a mothership, ship crafter or command centre)
|
||||
# - the shared ACTION button, which adapts to whatever the fleet is
|
||||
|
|
@ -22,14 +24,8 @@ extends Node2D
|
|||
@onready var building_action_button: IconButton = get_node("../UI/BuildingActionButton")
|
||||
|
||||
@onready var build_menu: Control = get_node("../UI/BuildMenu")
|
||||
@onready var build_mothership_button: IconButton = get_node("../UI/BuildMenu/MothershipButton")
|
||||
@onready var build_crafter_button: IconButton = get_node("../UI/BuildMenu/CrafterButton")
|
||||
@onready var build_command_button: IconButton = get_node("../UI/BuildMenu/CommandButton")
|
||||
@onready var build_close_button: IconButton = get_node("../UI/BuildMenu/CloseButton")
|
||||
@onready var build_status: Label = get_node("../UI/BuildMenu/StatusLabel")
|
||||
@onready var build_extractor_button: IconButton = get_node("../UI/BuildMenu/ExtractorButton")
|
||||
@onready var build_hub_button: IconButton = get_node("../UI/BuildMenu/TransportHubButton")
|
||||
@onready var build_lab_button: IconButton = get_node("../UI/BuildMenu/ShipLabButton")
|
||||
@onready var lab_menu: Control = get_node("../UI/LabMenu")
|
||||
@onready var lab_vessel_buttons: Array = [
|
||||
get_node("../UI/LabMenu/VesselA"), get_node("../UI/LabMenu/VesselB"),
|
||||
|
|
@ -78,20 +74,7 @@ extends Node2D
|
|||
get_node("../UI/HangarMenu/FleetShieldLabel"),
|
||||
get_node("../UI/HangarMenu/FleetTransporterLabel"),
|
||||
]
|
||||
@onready var build_menu_entries: Array = [
|
||||
[GameState.BUILDING_MOTHERSHIP, build_mothership_button,
|
||||
get_node("../UI/BuildMenu/MothershipLabel"), get_node("../UI/BuildMenu/MothershipCost")],
|
||||
[GameState.BUILDING_SHIP_CRAFTER, build_crafter_button,
|
||||
get_node("../UI/BuildMenu/CrafterLabel"), get_node("../UI/BuildMenu/CrafterCost")],
|
||||
[GameState.BUILDING_COMMAND_CENTER, build_command_button,
|
||||
get_node("../UI/BuildMenu/CommandLabel"), get_node("../UI/BuildMenu/CommandCost")],
|
||||
[GameState.BUILDING_EXTRACTOR, build_extractor_button,
|
||||
get_node("../UI/BuildMenu/ExtractorLabel"), get_node("../UI/BuildMenu/ExtractorCost")],
|
||||
[GameState.BUILDING_TRANSPORT_HUB, build_hub_button,
|
||||
get_node("../UI/BuildMenu/TransportHubLabel"), get_node("../UI/BuildMenu/TransportHubCost")],
|
||||
[GameState.BUILDING_SHIP_LAB, build_lab_button,
|
||||
get_node("../UI/BuildMenu/ShipLabLabel"), get_node("../UI/BuildMenu/ShipLabCost")],
|
||||
]
|
||||
var build_menu_entries: Array = []
|
||||
|
||||
# Which vessel the lab screen is showing, and which visible upgrade card is
|
||||
# selected. Each vessel gets its own small set of direct cards; the detail and
|
||||
|
|
@ -148,41 +131,6 @@ var lab_building_id: int = 0
|
|||
@onready var formation_close_button: IconButton = get_node("../UI/FormationMenu/CloseButton")
|
||||
@onready var formation_reset_button: IconButton = get_node("../UI/FormationMenu/ResetButton")
|
||||
|
||||
# What each option is and does, shown before anything is spent. Picking a
|
||||
# structure is a real commitment - it costs ore, and a mothership placement
|
||||
# can destroy the one you already have - so no option builds straight from
|
||||
# the menu; each one explains itself first and waits for a confirmation.
|
||||
#
|
||||
# The bodies are kept short on purpose: at the UI's font size the popup holds
|
||||
# roughly eight lines, and a placement condition or a price warning is
|
||||
# appended below them and needs some of those lines.
|
||||
const BUILDING_INFO: Dictionary = {
|
||||
GameState.BUILDING_MOTHERSHIP: {
|
||||
"title": "Mothership",
|
||||
"body": "Fleet home port. A wiped-out fleet rebuilds here, and extractor haulers deliver ore here.",
|
||||
},
|
||||
GameState.BUILDING_SHIP_CRAFTER: {
|
||||
"title": "Ship Crafter",
|
||||
"body": "Fly the fleet onto it, then use the action button to build extra vessels.",
|
||||
},
|
||||
GameState.BUILDING_COMMAND_CENTER: {
|
||||
"title": "Command Center",
|
||||
"body": "Fly the fleet onto it to drag your fleet into a new formation. Rebuilt fleets keep it.",
|
||||
},
|
||||
GameState.BUILDING_SHIP_LAB: {
|
||||
"title": "Ship Lab",
|
||||
"body": "Fly the fleet onto it to upgrade each vessel type, and to unlock the shield vessel.",
|
||||
},
|
||||
GameState.BUILDING_TRANSPORT_HUB: {
|
||||
"title": "Transport Hub",
|
||||
"body": "Stations transport vessels. They fetch ore from your mothership and deliver it to whichever building is waiting on it.",
|
||||
},
|
||||
GameState.BUILDING_EXTRACTOR: {
|
||||
"title": "Extractor",
|
||||
"body": "Mines a planet and runs a hauler to your mothership. It keeps earning while you are away.",
|
||||
},
|
||||
}
|
||||
|
||||
# Which structure the popup is currently offering to build, or "" when it is
|
||||
# only explaining something that cannot be built right now.
|
||||
var pending_build_type: String = ""
|
||||
|
|
@ -210,12 +158,7 @@ var context_actions_expanded: bool = false
|
|||
func _ready() -> void:
|
||||
build_button.pressed.connect(_on_build_pressed)
|
||||
build_close_button.pressed.connect(_close_build_menu)
|
||||
build_mothership_button.pressed.connect(_on_place_mothership)
|
||||
build_crafter_button.pressed.connect(_on_place_crafter)
|
||||
build_command_button.pressed.connect(_on_place_command_center)
|
||||
build_extractor_button.pressed.connect(_on_place_extractor)
|
||||
build_hub_button.pressed.connect(_on_place_transport_hub)
|
||||
build_lab_button.pressed.connect(_on_place_ship_lab)
|
||||
_configure_build_menu_entries()
|
||||
craft_shield_button.pressed.connect(_on_craft_shield)
|
||||
lab_close_button.pressed.connect(func(): lab_menu.visible = false)
|
||||
lab_buy_button.pressed.connect(_on_lab_buy)
|
||||
|
|
@ -267,6 +210,21 @@ func _ready() -> void:
|
|||
_layout_context_buttons()
|
||||
|
||||
|
||||
func _configure_build_menu_entries() -> void:
|
||||
build_menu_entries.clear()
|
||||
for definition in BUILDING_DEFINITIONS.all():
|
||||
if definition.menu_button_name == "":
|
||||
continue
|
||||
var button: IconButton = get_node("../UI/BuildMenu/%s" % definition.menu_button_name)
|
||||
var label: Label = get_node("../UI/BuildMenu/%s" % definition.menu_label_name)
|
||||
var cost_number: NeonNumber = get_node("../UI/BuildMenu/%s" % definition.menu_cost_name)
|
||||
button.icon = definition.menu_icon
|
||||
button.pressed.connect(_show_building_info.bind(String(definition.definition_id)))
|
||||
label.text = definition.display_name
|
||||
cost_number.amount = definition.cost
|
||||
build_menu_entries.append([definition.definition_id, button, label, cost_number, definition])
|
||||
|
||||
|
||||
# True while a building prompt or panel owns the shared action slot, so the
|
||||
# enter-atmosphere prompt knows to stay out of the way.
|
||||
func is_showing_building_prompt() -> bool:
|
||||
|
|
@ -318,9 +276,7 @@ func _process(delta: float) -> void:
|
|||
if build_menu.visible:
|
||||
# Keep the greyed state live while the menu is open, so flying onto
|
||||
# a planet lights the option up without reopening it.
|
||||
build_extractor_button.enabled = _planet_under_fleet() != null \
|
||||
and GameState.is_building_unlocked(GameState.BUILDING_EXTRACTOR)
|
||||
build_extractor_button.queue_redraw()
|
||||
_refresh_build_option_states()
|
||||
|
||||
if craft_menu.visible:
|
||||
# Live, so a delivery arriving while the menu is open visibly works
|
||||
|
|
@ -359,10 +315,7 @@ func _process(delta: float) -> void:
|
|||
|
||||
current_building = pending_building
|
||||
context_actions_expanded = false
|
||||
# The hangar is where finished vessels are delivered (#23), so it belongs
|
||||
# at the mothership and nowhere else.
|
||||
hangar_button.visible = current_building != null \
|
||||
and current_building.building_type == GameState.BUILDING_MOTHERSHIP
|
||||
hangar_button.visible = false
|
||||
# Every building can hold ore, so the transfer button rides alongside the
|
||||
# action button rather than being one of the things the action button
|
||||
# turns into - the two are independent choices at the same structure.
|
||||
|
|
@ -371,7 +324,7 @@ func _process(delta: float) -> void:
|
|||
action_button.visible = current_building != null
|
||||
if current_building != null:
|
||||
var actions: Array = _context_action_buttons()
|
||||
building_action_button.icon = _action_icon_for(current_building.building_type)
|
||||
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()
|
||||
|
|
@ -403,13 +356,20 @@ func _layout_context_buttons() -> void:
|
|||
func _context_action_buttons() -> Array:
|
||||
if current_building == null:
|
||||
return []
|
||||
var actions: Array = [transfer_button]
|
||||
if current_building.building_type in [GameState.BUILDING_MOTHERSHIP, GameState.BUILDING_SHIP_CRAFTER,
|
||||
GameState.BUILDING_SHIP_LAB, GameState.BUILDING_COMMAND_CENTER]:
|
||||
actions.push_front(building_action_button)
|
||||
if current_building.building_type == GameState.BUILDING_MOTHERSHIP:
|
||||
actions.append(hangar_button)
|
||||
return actions
|
||||
var buttons: Array = []
|
||||
for action in current_building.available_actions():
|
||||
var button: IconButton = _button_for_building_action(action)
|
||||
if button != null and button not in buttons:
|
||||
buttons.append(button)
|
||||
return buttons
|
||||
|
||||
|
||||
func _button_for_building_action(action: String) -> IconButton:
|
||||
if action == "transfer":
|
||||
return transfer_button
|
||||
if action == "hangar":
|
||||
return hangar_button
|
||||
return building_action_button
|
||||
|
||||
|
||||
func _on_context_action_pressed() -> void:
|
||||
|
|
@ -433,20 +393,6 @@ func _collapse_context_actions() -> void:
|
|||
_layout_context_buttons()
|
||||
|
||||
|
||||
func _action_icon_for(type: String) -> int:
|
||||
match type:
|
||||
GameState.BUILDING_SHIP_CRAFTER:
|
||||
return IconButton.Icon.SHIP
|
||||
GameState.BUILDING_TRANSPORT_HUB:
|
||||
return IconButton.Icon.TRANSPORT_HUB
|
||||
GameState.BUILDING_SHIP_LAB:
|
||||
return IconButton.Icon.SHIP_LAB
|
||||
GameState.BUILDING_MOTHERSHIP:
|
||||
return IconButton.Icon.MOTHERSHIP
|
||||
_:
|
||||
return IconButton.Icon.COMMAND_CENTER
|
||||
|
||||
|
||||
func _check_fleet_wipe() -> void:
|
||||
if not fleet.ships.is_empty():
|
||||
return
|
||||
|
|
@ -465,11 +411,12 @@ func _on_action_pressed() -> void:
|
|||
for bar in resource_bars:
|
||||
bar.building_id = current_building.building_id
|
||||
|
||||
if current_building.building_type == GameState.BUILDING_MOTHERSHIP:
|
||||
var action: String = current_building.primary_action()
|
||||
if action == "research":
|
||||
skill_menu.visible = true
|
||||
skill_tree.reset_scroll()
|
||||
_refresh_skill_menu()
|
||||
elif current_building.building_type == GameState.BUILDING_SHIP_CRAFTER:
|
||||
elif action == "craft":
|
||||
# Remembered explicitly: _process() clears current_building while any
|
||||
# menu is open, so by the next frame the panel would no longer know
|
||||
# which crafter it belongs to. Losing this line makes every button in
|
||||
|
|
@ -478,11 +425,11 @@ func _on_action_pressed() -> void:
|
|||
craft_building_id = current_building.building_id
|
||||
craft_menu.visible = true
|
||||
_refresh_craft_status()
|
||||
elif current_building.building_type == GameState.BUILDING_SHIP_LAB:
|
||||
elif action == "lab":
|
||||
lab_building_id = current_building.building_id
|
||||
lab_menu.visible = true
|
||||
_refresh_lab()
|
||||
elif current_building.building_type == GameState.BUILDING_COMMAND_CENTER:
|
||||
elif action == "formation":
|
||||
formation_menu.visible = true
|
||||
formation_editor.queue_redraw()
|
||||
|
||||
|
|
@ -551,8 +498,7 @@ func _refresh_build_status() -> void:
|
|||
# Researched but not usable right here is still shown, greyed: an
|
||||
# extractor needs a planet under the fleet, which is a "not now" rather
|
||||
# than a "not yet".
|
||||
build_extractor_button.enabled = _planet_under_fleet() != null \
|
||||
and GameState.is_building_unlocked(GameState.BUILDING_EXTRACTOR)
|
||||
_refresh_build_option_states()
|
||||
|
||||
# Prices are shown under each option now, so this only needs to say
|
||||
# whether the fleet is somewhere an extractor could go.
|
||||
|
|
@ -562,16 +508,14 @@ func _refresh_build_status() -> void:
|
|||
build_status.text = ""
|
||||
|
||||
|
||||
func _on_place_mothership() -> void:
|
||||
_show_building_info(GameState.BUILDING_MOTHERSHIP)
|
||||
|
||||
|
||||
func _on_place_crafter() -> void:
|
||||
_show_building_info(GameState.BUILDING_SHIP_CRAFTER)
|
||||
|
||||
|
||||
func _on_place_command_center() -> void:
|
||||
_show_building_info(GameState.BUILDING_COMMAND_CENTER)
|
||||
func _refresh_build_option_states() -> void:
|
||||
for entry in build_menu_entries:
|
||||
var definition: Resource = entry[4]
|
||||
var enabled: bool = GameState.is_building_unlocked(definition.definition_id)
|
||||
if definition.placement_mode == "planet":
|
||||
enabled = enabled and _planet_under_fleet() != null
|
||||
entry[1].enabled = enabled
|
||||
entry[1].queue_redraw()
|
||||
|
||||
|
||||
func _close_build_menu() -> void:
|
||||
|
|
@ -582,40 +526,17 @@ func _close_build_menu() -> void:
|
|||
build_menu.visible = false
|
||||
|
||||
|
||||
func _on_place_ship_lab() -> void:
|
||||
_show_building_info(GameState.BUILDING_SHIP_LAB)
|
||||
|
||||
|
||||
func _on_place_transport_hub() -> void:
|
||||
_show_building_info(GameState.BUILDING_TRANSPORT_HUB)
|
||||
|
||||
|
||||
func _on_place_extractor() -> void:
|
||||
# The option stays pressable while greyed, so this is also how a
|
||||
# not-over-a-planet extractor explains itself.
|
||||
_show_building_info(GameState.BUILDING_EXTRACTOR)
|
||||
|
||||
|
||||
func cost_of(type: String) -> int:
|
||||
match type:
|
||||
GameState.BUILDING_TRANSPORT_HUB:
|
||||
return GameState.TRANSPORT_HUB_COST
|
||||
GameState.BUILDING_SHIP_LAB:
|
||||
return GameState.SHIP_LAB_COST
|
||||
GameState.BUILDING_MOTHERSHIP:
|
||||
return GameState.MOTHERSHIP_COST
|
||||
GameState.BUILDING_SHIP_CRAFTER:
|
||||
return GameState.SHIP_CRAFTER_COST
|
||||
GameState.BUILDING_COMMAND_CENTER:
|
||||
return GameState.COMMAND_CENTER_COST
|
||||
_:
|
||||
return GameState.EXTRACTOR_COST
|
||||
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
||||
return int(definition.cost) if definition != null else 0
|
||||
|
||||
|
||||
func _show_building_info(type: String) -> void:
|
||||
var info: Dictionary = BUILDING_INFO[type]
|
||||
var cost: int = cost_of(type)
|
||||
var lines: Array = [info.body]
|
||||
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
||||
if definition == null:
|
||||
return
|
||||
var cost: int = int(definition.cost)
|
||||
var lines: Array = [definition.description]
|
||||
var can_build: bool = true
|
||||
|
||||
# Locked structures still list themselves, greyed, so the player can see
|
||||
|
|
@ -626,7 +547,7 @@ func _show_building_info(type: String) -> void:
|
|||
|
||||
# Anything that would stop this particular placement is spelled out in
|
||||
# the same popup, rather than silently failing after the press.
|
||||
if can_build and type == GameState.BUILDING_EXTRACTOR:
|
||||
if can_build and definition.placement_mode == "planet":
|
||||
var planet: Node2D = _planet_under_fleet()
|
||||
if planet == null:
|
||||
lines.append("No planet in reach - fly over one.")
|
||||
|
|
@ -635,15 +556,15 @@ func _show_building_info(type: String) -> void:
|
|||
lines.append("%s already has an extractor." % planet.planet_name)
|
||||
can_build = false
|
||||
else:
|
||||
lines.append("Will mine %s from %s." % ["gas" if planet.resource_type == "gas" else "ore", planet.planet_name])
|
||||
elif can_build and type == GameState.BUILDING_MOTHERSHIP and GameState.has_mothership():
|
||||
lines.append("Will mine %s from %s." % [GameState.resource_name(planet.resource_type), planet.planet_name])
|
||||
elif can_build and definition.unique and GameState.has_building_of_type(type):
|
||||
lines.append("WARNING: only one may exist - this destroys your current one.")
|
||||
|
||||
if can_build and not GameState.can_afford(cost):
|
||||
lines.append("Not enough rocks - needs %d, you have %d." % [cost, GameState.rocks])
|
||||
can_build = false
|
||||
|
||||
info_title.text = info.title
|
||||
info_title.text = definition.display_name
|
||||
info_message.text = "\n\n".join(lines)
|
||||
info_cost.amount = cost
|
||||
info_build_button.visible = can_build
|
||||
|
|
@ -668,28 +589,31 @@ func _confirm_build() -> void:
|
|||
# planet an extractor was going to mine.
|
||||
if not GameState.is_building_unlocked(type):
|
||||
return
|
||||
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
||||
if definition == null:
|
||||
return
|
||||
|
||||
if type == GameState.BUILDING_EXTRACTOR:
|
||||
if definition.placement_mode == "planet":
|
||||
var planet: Node2D = _planet_under_fleet()
|
||||
if planet == null or GameState.has_extractor_at(planet.planet_name):
|
||||
build_status.text = "No planet in reach any more"
|
||||
return
|
||||
if not GameState.spend(GameState.EXTRACTOR_COST):
|
||||
build_status.text = "Not enough rocks (need %d, have %d)" % [GameState.EXTRACTOR_COST, GameState.rocks]
|
||||
if not GameState.spend(definition.cost):
|
||||
build_status.text = "Not enough rocks (need %d, have %d)" % [definition.cost, GameState.rocks]
|
||||
return
|
||||
GameState.add_extractor(planet.planet_name, planet.position, planet.resource_type)
|
||||
build_menu.visible = false
|
||||
return
|
||||
|
||||
if type == GameState.BUILDING_MOTHERSHIP and GameState.has_mothership():
|
||||
if not GameState.can_afford(GameState.MOTHERSHIP_COST):
|
||||
if definition.unique and GameState.has_building_of_type(type):
|
||||
if not GameState.can_afford(definition.cost):
|
||||
build_status.text = "Not enough rocks (need %d, have %d)" % [
|
||||
GameState.MOTHERSHIP_COST, GameState.rocks
|
||||
definition.cost, GameState.rocks
|
||||
]
|
||||
return
|
||||
# Only now is the old one destroyed - never before the new one is
|
||||
# known to be affordable, or the player would be left with none.
|
||||
GameState.remove_buildings_of_type(GameState.BUILDING_MOTHERSHIP)
|
||||
GameState.remove_buildings_of_type(type)
|
||||
building_field.rebuild_all()
|
||||
|
||||
_try_place(type, cost_of(type))
|
||||
|
|
|
|||
Loading…
Reference in a new issue