Add research lab planets and research points (#63)
This commit is contained in:
parent
651f8b787e
commit
3f3a22227d
43 changed files with 459 additions and 30 deletions
97
GameState.gd
97
GameState.gd
|
|
@ -4,6 +4,7 @@ const INVENTORY = preload("res://Inventory.gd")
|
|||
const RESOURCE_DEFINITIONS = preload("res://ResourceDefinitions.gd")
|
||||
const BUILDING_DEFINITIONS = preload("res://BuildingDefinitions.gd")
|
||||
const VESSEL_DEFINITIONS = preload("res://VesselDefinitions.gd")
|
||||
const PLANET_DEFINITIONS = preload("res://PlanetDefinitions.gd")
|
||||
|
||||
|
||||
# Autoload singleton - the one thing that survives change_scene_to_file()
|
||||
|
|
@ -44,6 +45,13 @@ var boost_depleted_notice: bool = false
|
|||
func set_fleet_speed_multiplier(value: float) -> void:
|
||||
fleet_speed_multiplier = clampf(value, 1.0, MAX_FLEET_SPEED_MULTIPLIER)
|
||||
|
||||
|
||||
# #63: currency gated behind conquering a research_lab planet - required by
|
||||
# both progression paths (skill tree nodes, vessel/upgrade research) on top
|
||||
# of their existing ore/resource costs.
|
||||
signal research_points_changed(total: int)
|
||||
var research_points: int = 0
|
||||
|
||||
# Explored-chunk history for the world map's fog-of-war reveal. Lives
|
||||
# here (not on ExplorationTracker, which gets destroyed and recreated on
|
||||
# every scene swap) so exploration progress isn't lost every time the
|
||||
|
|
@ -272,6 +280,13 @@ var buildings: Array = []
|
|||
# tree always reads root-at-the-bottom and cannot drift out of step with the
|
||||
# dependencies when nodes are added. `requires` is a single prerequisite id
|
||||
# ("" for a root).
|
||||
# #63: research_points (default 0 where omitted) is a SEPARATE currency from
|
||||
# cost (ore), spent only from GameState.research_points - earned by
|
||||
# conquering a research_lab planet, never bought. Balance, decided
|
||||
# deliberately: shipyards and logistics stay at 0 so a fresh game can still
|
||||
# reach a ship crafter and an extractor with zero research points (see the
|
||||
# closing comment on #63 for the reasoning); every other node - none of them
|
||||
# required for that opening - costs at least 1.
|
||||
const SKILL_TREE: Array = [
|
||||
{
|
||||
"id": "shipyards", "name": "Shipyards", "cost": 15, "requires": "",
|
||||
|
|
@ -285,42 +300,42 @@ const SKILL_TREE: Array = [
|
|||
},
|
||||
{
|
||||
"id": "coordination", "name": "Coordination", "cost": 20, "requires": "shipyards",
|
||||
"x": 0.50, "unlocks": BUILDING_COMMAND_CENTER,
|
||||
"x": 0.50, "unlocks": BUILDING_COMMAND_CENTER, "research_points": 1,
|
||||
"desc": "Build command centers, where the fleet formation is arranged.",
|
||||
},
|
||||
{
|
||||
"id": "freight", "name": "Freight Handling", "cost": 40, "requires": "logistics",
|
||||
"x": 0.18, "unlocks": BUILDING_TRANSPORT_HUB,
|
||||
"x": 0.18, "unlocks": BUILDING_TRANSPORT_HUB, "research_points": 1,
|
||||
"desc": "Build transport hubs. Each hub includes its own automatic transporters.",
|
||||
},
|
||||
{
|
||||
"id": "vessel_science", "name": "Vessel Science", "cost": 35, "requires": "shipyards",
|
||||
"x": 0.82, "unlocks": BUILDING_SHIP_LAB,
|
||||
"x": 0.82, "unlocks": BUILDING_SHIP_LAB, "research_points": 1,
|
||||
"desc": "Build ship labs, where each vessel type is upgraded and new ones unlocked.",
|
||||
},
|
||||
{
|
||||
"id": "freight_yards", "name": "Freight Yards", "cost": 80, "requires": "freight",
|
||||
"x": 0.18, "hub_bonus": 1,
|
||||
"x": 0.18, "hub_bonus": 1, "research_points": 2,
|
||||
"desc": "Permanent: +1 transporter stationed at every hub, including ones already built.",
|
||||
},
|
||||
{
|
||||
"id": "power_systems", "name": "Power Systems", "cost": 45, "requires": "freight",
|
||||
"x": 0.50, "unlocks": BUILDING_GAS_BURNER,
|
||||
"x": 0.50, "unlocks": BUILDING_GAS_BURNER, "research_points": 2,
|
||||
"desc": "Build gas burners. Each converts gas into energy at its own site - hub transporters keep it stocked and collect what it makes.",
|
||||
},
|
||||
{
|
||||
"id": "wing", "name": "Wing Doctrine", "cost": 30, "requires": "coordination",
|
||||
"x": 0.82, "fleet_bonus": 1,
|
||||
"x": 0.82, "fleet_bonus": 1, "research_points": 1,
|
||||
"desc": "Permanent: +1 vessel in the fleet.",
|
||||
},
|
||||
{
|
||||
"id": "squadron", "name": "Squadron Doctrine", "cost": 70, "requires": "wing",
|
||||
"x": 0.50, "fleet_bonus": 1,
|
||||
"x": 0.50, "fleet_bonus": 1, "research_points": 2,
|
||||
"desc": "Permanent: +1 vessel in the fleet.",
|
||||
},
|
||||
{
|
||||
"id": "armada", "name": "Armada Doctrine", "cost": 150, "requires": "squadron",
|
||||
"x": 0.50, "fleet_bonus": 1,
|
||||
"x": 0.50, "fleet_bonus": 1, "research_points": 3,
|
||||
"desc": "Permanent: +1 vessel in the fleet.",
|
||||
},
|
||||
]
|
||||
|
|
@ -370,16 +385,23 @@ func prerequisite_met(id: String) -> bool:
|
|||
|
||||
|
||||
func can_unlock(id: String) -> bool:
|
||||
return not is_unlocked(id) and prerequisite_met(id) and can_afford(int(skill_node(id).cost))
|
||||
var node: Dictionary = skill_node(id)
|
||||
return not is_unlocked(id) and prerequisite_met(id) and can_afford(int(node.cost)) \
|
||||
and research_points >= int(node.get("research_points", 0))
|
||||
|
||||
|
||||
func unlock(id: String) -> bool:
|
||||
if not can_unlock(id):
|
||||
return false
|
||||
if not spend(int(skill_node(id).cost)):
|
||||
var node: Dictionary = skill_node(id)
|
||||
if not spend(int(node.cost)):
|
||||
return false
|
||||
var points_needed: int = int(node.get("research_points", 0))
|
||||
if points_needed > 0:
|
||||
research_points -= points_needed
|
||||
research_points_changed.emit(research_points)
|
||||
unlocked.append(id)
|
||||
if int(skill_node(id).get("hub_bonus", 0)) > 0:
|
||||
if int(node.get("hub_bonus", 0)) > 0:
|
||||
_sync_hub_transporters()
|
||||
return true
|
||||
|
||||
|
|
@ -424,6 +446,7 @@ func reset_new_game() -> void:
|
|||
planet_id = ""
|
||||
planet_definition_id = "rocky"
|
||||
fleet_speed_multiplier = 1.0
|
||||
research_points = 0
|
||||
explored_chunks.clear()
|
||||
bank_inventory = INVENTORY.new({RESOURCE_ORE: 500})
|
||||
fleet_inventory = INVENTORY.new()
|
||||
|
|
@ -600,10 +623,14 @@ func unlock_vessel(vessel: String) -> bool:
|
|||
if definition == null or definition.unlock_id == "" or vessel_is_unlocked(vessel) or not has_ship_lab():
|
||||
return false
|
||||
var costs: Dictionary = research_costs(definition.unlock_id)
|
||||
if not can_afford_resources(costs):
|
||||
var points_needed: int = int(definition.research_point_cost)
|
||||
if not can_afford_resources(costs) or research_points < points_needed:
|
||||
return false
|
||||
for resource_type in costs:
|
||||
bank_inventory.withdraw(String(resource_type), int(costs[resource_type]))
|
||||
if points_needed > 0:
|
||||
research_points -= points_needed
|
||||
research_points_changed.emit(research_points)
|
||||
vessel_levels[definition.unlock_id] = 1
|
||||
vessel_upgraded.emit(definition.unlock_id)
|
||||
return true
|
||||
|
|
@ -650,7 +677,8 @@ func upgrade_cost(id: String) -> int:
|
|||
|
||||
|
||||
func can_buy_upgrade(id: String) -> bool:
|
||||
return has_ship_lab() and not upgrade_maxed(id) and can_afford(upgrade_cost(id))
|
||||
return has_ship_lab() and not upgrade_maxed(id) and can_afford(upgrade_cost(id)) \
|
||||
and research_points >= research_point_cost(id)
|
||||
|
||||
|
||||
func buy_upgrade(id: String) -> bool:
|
||||
|
|
@ -658,6 +686,10 @@ func buy_upgrade(id: String) -> bool:
|
|||
return false
|
||||
if not spend(upgrade_cost(id)):
|
||||
return false
|
||||
var points_needed: int = research_point_cost(id)
|
||||
if points_needed > 0:
|
||||
research_points -= points_needed
|
||||
research_points_changed.emit(research_points)
|
||||
vessel_levels[id] = upgrade_level(id) + 1
|
||||
vessel_upgraded.emit(id)
|
||||
return true
|
||||
|
|
@ -920,6 +952,14 @@ func research_cost(id: String) -> int:
|
|||
return int(research_costs(id).get(RESOURCE_ORE, 0))
|
||||
|
||||
|
||||
func research_point_cost(id: String) -> int:
|
||||
var unlock_definition: Resource = vessel_for_unlock(id)
|
||||
if unlock_definition != null:
|
||||
return int(unlock_definition.research_point_cost)
|
||||
var d: Dictionary = upgrade_def(id)
|
||||
return int(d.resource.research_point_cost) if not d.is_empty() else 0
|
||||
|
||||
|
||||
func research_costs(id: String) -> Dictionary:
|
||||
var unlock_definition: Resource = vessel_for_unlock(id)
|
||||
if unlock_definition != null:
|
||||
|
|
@ -948,10 +988,20 @@ func _advance_lab_research(delta: float) -> void:
|
|||
if inventory.amount(String(resource_type)) < int(costs[resource_type]):
|
||||
affordable = false
|
||||
break
|
||||
# #63: research points are a fleet-wide currency, not delivered
|
||||
# to the lab like ore/gas - checked and spent here too so a
|
||||
# shortfall stalls the order the same way an undelivered
|
||||
# resource does, rather than letting it start regardless.
|
||||
var points_needed: int = research_point_cost(id)
|
||||
if points_needed > research_points:
|
||||
affordable = false
|
||||
if not affordable:
|
||||
continue
|
||||
for resource_type in costs:
|
||||
inventory.withdraw(String(resource_type), int(costs[resource_type]))
|
||||
if points_needed > 0:
|
||||
research_points -= points_needed
|
||||
research_points_changed.emit(research_points)
|
||||
order.started = true
|
||||
order.elapsed = float(order.elapsed) + delta
|
||||
if float(order.elapsed) < lab_research_time():
|
||||
|
|
@ -1212,7 +1262,7 @@ var planet_capture: Dictionary = {}
|
|||
signal planet_conquered(planet_id: String)
|
||||
|
||||
|
||||
func register_capture_points(planet_key: String, total: int) -> void:
|
||||
func register_capture_points(planet_key: String, total: int, definition_id: String = "") -> void:
|
||||
# Idempotent: landing again on a planet must not wipe points already
|
||||
# captured on a previous visit.
|
||||
if planet_capture.has(planet_key):
|
||||
|
|
@ -1223,8 +1273,13 @@ func register_capture_points(planet_key: String, total: int) -> void:
|
|||
# A planet demanding zero points is vacuously already held (#55: a
|
||||
# black hole's "void" profile has no authored PlanetSetup at all, so
|
||||
# this is the only registration it ever gets) - every planet WITH real
|
||||
# points still starts unconquered, exactly as before.
|
||||
planet_capture[planet_key] = {"captured": captured, "conquered": total <= 0}
|
||||
# points still starts unconquered, exactly as before. The planet definition
|
||||
# supplies an optional conquest reward, avoiding planet-id special cases in
|
||||
# the capture system.
|
||||
var definition: Resource = PLANET_DEFINITIONS.get_definition(definition_id)
|
||||
var point_reward: int = 0 if definition == null else int(definition.conquest_research_points)
|
||||
planet_capture[planet_key] = {"captured": captured, "conquered": total <= 0,
|
||||
"research_point_reward": point_reward, "research_awarded": false}
|
||||
|
||||
|
||||
func capture_state(planet_key: String) -> Dictionary:
|
||||
|
|
@ -1251,6 +1306,16 @@ func mark_point_captured(planet_key: String, index: int) -> void:
|
|||
if captured.all(func(c): return bool(c)):
|
||||
state.conquered = true
|
||||
planet_conquered.emit(planet_key)
|
||||
# #63: definition-authored research points, exactly once per planet.
|
||||
# state.conquered flipping above already makes a second call to this
|
||||
# whole function a no-op via the early return above it, but
|
||||
# research_awarded is its own explicit guard on the award itself
|
||||
# regardless of how conquest was reached.
|
||||
var reward: int = int(state.get("research_point_reward", 0))
|
||||
if reward > 0 and not bool(state.get("research_awarded", false)):
|
||||
state.research_awarded = true
|
||||
research_points += reward
|
||||
research_points_changed.emit(research_points)
|
||||
|
||||
|
||||
func is_planet_conquered(planet_key: String) -> bool:
|
||||
|
|
|
|||
Loading…
Reference in a new issue