Compose vessels from definitions and capabilities
This commit is contained in:
parent
0504b24d8d
commit
14081ad230
39 changed files with 705 additions and 386 deletions
228
GameState.gd
228
GameState.gd
|
|
@ -3,6 +3,7 @@ extends Node
|
|||
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")
|
||||
|
||||
|
||||
# Autoload singleton - the one thing that survives change_scene_to_file()
|
||||
|
|
@ -404,21 +405,10 @@ const VESSEL_INTERCEPTOR: String = "interceptor"
|
|||
const VESSEL_SHIELD: String = "shield"
|
||||
const VESSEL_TRANSPORTER: String = "transporter"
|
||||
|
||||
const VESSEL_NAMES: Dictionary = {
|
||||
VESSEL_INTERCEPTOR: "Interceptor",
|
||||
VESSEL_SHIELD: "Shield Vessel",
|
||||
VESSEL_TRANSPORTER: "Transporter",
|
||||
}
|
||||
|
||||
# Compatibility constants for callers and old tests. Runtime metadata is
|
||||
# resolved from VesselDefinition below.
|
||||
const SHIELD_COST: int = 14
|
||||
|
||||
# How long one vessel takes to build, once its ore is actually in the
|
||||
# building. A queue you can watch and cancel needs time to exist in.
|
||||
const BUILD_TIMES: Dictionary = {
|
||||
VESSEL_INTERCEPTOR: 10.0,
|
||||
VESSEL_SHIELD: 16.0,
|
||||
VESSEL_TRANSPORTER: 8.0,
|
||||
}
|
||||
const TRANSPORTER_COST: int = 10
|
||||
const LAB_RESEARCH_TIME: float = 8.0
|
||||
|
||||
|
||||
|
|
@ -429,57 +419,14 @@ var build_time_scale: float = 1.0
|
|||
|
||||
|
||||
func build_time(product: String) -> float:
|
||||
return float(BUILD_TIMES.get(product, 10.0)) * build_time_scale
|
||||
var definition: Resource = VESSEL_DEFINITIONS.get_definition(product)
|
||||
return (definition.build_time if definition != null else 10.0) * build_time_scale
|
||||
|
||||
|
||||
func lab_research_time() -> float:
|
||||
return LAB_RESEARCH_TIME * build_time_scale
|
||||
|
||||
# Researched at the ship lab, per vessel type. Separate from SKILL_TREE on
|
||||
# purpose: that gates what the player may build at all and is a strategic
|
||||
# spine, while these are repeatable tuning on a vessel you already have.
|
||||
# `cost` is the FIRST level; each level after costs cost * (level + 1), so
|
||||
# the third point in something is not the same price as the first.
|
||||
const VESSEL_UPGRADES: Array = [
|
||||
{
|
||||
"id": "int_damage", "vessel": VESSEL_INTERCEPTOR, "name": "Heavy Slugs",
|
||||
"cost": 30, "max_level": 3, "desc": "+1 damage per shot.",
|
||||
},
|
||||
{
|
||||
"id": "int_rate", "vessel": VESSEL_INTERCEPTOR, "name": "Autoloader",
|
||||
"cost": 25, "max_level": 3, "desc": "Fires 15% faster per level.",
|
||||
},
|
||||
{
|
||||
"id": "int_range", "vessel": VESSEL_INTERCEPTOR, "name": "Targeting Array",
|
||||
"cost": 20, "max_level": 3, "desc": "+60 firing range per level.",
|
||||
},
|
||||
{
|
||||
"id": "int_speed", "vessel": VESSEL_INTERCEPTOR, "name": "Thrusters",
|
||||
"cost": 25, "max_level": 3, "desc": "+40 top speed, +60 acceleration per level.",
|
||||
},
|
||||
{
|
||||
"id": "shd_hits", "vessel": VESSEL_SHIELD, "name": "Layered Emitters",
|
||||
"cost": 60, "max_level": 2, "desc": "+1 hit absorbed before the shield drops.",
|
||||
},
|
||||
{
|
||||
"id": "shd_recharge", "vessel": VESSEL_SHIELD, "name": "Fast Cycling",
|
||||
"cost": 35, "max_level": 3, "desc": "Shield returns 1.2s sooner per level.",
|
||||
},
|
||||
{
|
||||
"id": "shd_speed", "vessel": VESSEL_SHIELD, "name": "Thrusters",
|
||||
"cost": 25, "max_level": 3, "desc": "+40 top speed, +60 acceleration per level.",
|
||||
},
|
||||
{
|
||||
"id": "trn_capacity", "vessel": VESSEL_TRANSPORTER, "name": "Expanded Hold",
|
||||
"cost": 30, "max_level": 3, "desc": "Carries 8 more ore per supply run.",
|
||||
},
|
||||
{
|
||||
"id": "trn_speed", "vessel": VESSEL_TRANSPORTER, "name": "Freight Engines",
|
||||
"cost": 30, "max_level": 3, "desc": "+70 speed on supply runs.",
|
||||
},
|
||||
]
|
||||
|
||||
# Base figures every vessel starts from, before anything is researched.
|
||||
# Compatibility constants. VesselDefinition owns the live base figures.
|
||||
const SHIP_BASE_DAMAGE: int = 1
|
||||
const SHIP_BASE_COOLDOWN: float = 0.5
|
||||
const SHIP_BASE_RANGE: float = 320.0
|
||||
|
|
@ -503,9 +450,7 @@ func has_ship_lab() -> bool:
|
|||
|
||||
|
||||
func shield_vessel_unlocked() -> bool:
|
||||
# Unlocked AT the lab, by paying for it there - a vessel type rather than
|
||||
# a stat, so it is a one-off rather than a level.
|
||||
return int(vessel_levels.get(UNLOCK_SHIELD_VESSEL, 0)) > 0
|
||||
return vessel_is_unlocked(VESSEL_SHIELD)
|
||||
|
||||
|
||||
const UNLOCK_SHIELD_VESSEL: String = "unlock_shield"
|
||||
|
|
@ -513,27 +458,69 @@ const UNLOCK_SHIELD_COST: int = 45
|
|||
|
||||
|
||||
func unlock_shield_vessel() -> bool:
|
||||
if shield_vessel_unlocked() or not has_ship_lab():
|
||||
return unlock_vessel(VESSEL_SHIELD)
|
||||
|
||||
|
||||
func vessel_definition(vessel: String) -> Resource:
|
||||
return VESSEL_DEFINITIONS.get_definition(vessel)
|
||||
|
||||
|
||||
func vessel_name(vessel: String) -> String:
|
||||
var definition: Resource = vessel_definition(vessel)
|
||||
return definition.display_name if definition != null else vessel.capitalize()
|
||||
|
||||
|
||||
func vessel_has_capability(vessel: String, capability: String) -> bool:
|
||||
var definition: Resource = vessel_definition(vessel)
|
||||
return definition != null and definition.has_capability(capability)
|
||||
|
||||
|
||||
func vessel_is_unlocked(vessel: String) -> bool:
|
||||
var definition: Resource = vessel_definition(vessel)
|
||||
if definition == null:
|
||||
return false
|
||||
if not spend(UNLOCK_SHIELD_COST):
|
||||
return definition.unlock_id == "" or int(vessel_levels.get(definition.unlock_id, 0)) > 0
|
||||
|
||||
|
||||
func vessel_for_unlock(unlock_id: String) -> Resource:
|
||||
for definition in VESSEL_DEFINITIONS.all():
|
||||
if definition.unlock_id == unlock_id:
|
||||
return definition
|
||||
return null
|
||||
|
||||
|
||||
func unlock_vessel(vessel: String) -> bool:
|
||||
var definition: Resource = vessel_definition(vessel)
|
||||
if definition == null or definition.unlock_id == "" or vessel_is_unlocked(vessel) or not has_ship_lab():
|
||||
return false
|
||||
vessel_levels[UNLOCK_SHIELD_VESSEL] = 1
|
||||
vessel_upgraded.emit(UNLOCK_SHIELD_VESSEL)
|
||||
if not spend(definition.unlock_cost):
|
||||
return false
|
||||
vessel_levels[definition.unlock_id] = 1
|
||||
vessel_upgraded.emit(definition.unlock_id)
|
||||
return true
|
||||
|
||||
|
||||
func upgrade_def(id: String) -> Dictionary:
|
||||
for u in VESSEL_UPGRADES:
|
||||
if u.id == id:
|
||||
return u
|
||||
for definition in VESSEL_DEFINITIONS.all():
|
||||
for upgrade in definition.upgrades:
|
||||
if upgrade.upgrade_id == id:
|
||||
return {"id": upgrade.upgrade_id, "vessel": definition.definition_id,
|
||||
"name": upgrade.display_name, "desc": upgrade.description,
|
||||
"cost": upgrade.base_cost, "max_level": upgrade.max_level,
|
||||
"icon": upgrade.icon, "resource": upgrade}
|
||||
return {}
|
||||
|
||||
|
||||
func upgrades_for(vessel: String) -> Array:
|
||||
var out: Array = []
|
||||
for u in VESSEL_UPGRADES:
|
||||
if u.vessel == vessel:
|
||||
out.append(u)
|
||||
var definition: Resource = vessel_definition(vessel)
|
||||
if definition == null:
|
||||
return out
|
||||
for upgrade in definition.upgrades:
|
||||
out.append({"id": upgrade.upgrade_id, "vessel": vessel,
|
||||
"name": upgrade.display_name, "desc": upgrade.description,
|
||||
"cost": upgrade.base_cost, "max_level": upgrade.max_level,
|
||||
"icon": upgrade.icon, "resource": upgrade})
|
||||
return out
|
||||
|
||||
|
||||
|
|
@ -567,66 +554,51 @@ func buy_upgrade(id: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
# --- what those upgrades actually come to ---------------------------------
|
||||
# Read live rather than baked into a vessel when it is built, so researching
|
||||
# something improves the ships already flying instead of only the next ones.
|
||||
|
||||
func ship_damage(vessel: String) -> int:
|
||||
return SHIP_BASE_DAMAGE + (upgrade_level("int_damage") if vessel == VESSEL_INTERCEPTOR else 0)
|
||||
func vessel_stat(vessel: String, stat: String) -> float:
|
||||
var definition: Resource = vessel_definition(vessel)
|
||||
if definition == null:
|
||||
return 0.0
|
||||
var value: float = float(definition.base_stats.get(stat, 0.0))
|
||||
for upgrade in definition.upgrades:
|
||||
var level: int = upgrade_level(upgrade.upgrade_id)
|
||||
value += float(upgrade.additive_modifiers.get(stat, 0.0)) * level
|
||||
if upgrade.multiplicative_modifiers.has(stat):
|
||||
value *= pow(float(upgrade.multiplicative_modifiers[stat]), level)
|
||||
if definition.minimum_stats.has(stat):
|
||||
value = maxf(value, float(definition.minimum_stats[stat]))
|
||||
return value
|
||||
|
||||
|
||||
func ship_fire_cooldown(vessel: String) -> float:
|
||||
if vessel != VESSEL_INTERCEPTOR:
|
||||
return SHIP_BASE_COOLDOWN
|
||||
return SHIP_BASE_COOLDOWN * pow(0.85, float(upgrade_level("int_rate")))
|
||||
|
||||
|
||||
func ship_fire_range(vessel: String) -> float:
|
||||
return SHIP_BASE_RANGE + (60.0 * upgrade_level("int_range") if vessel == VESSEL_INTERCEPTOR else 0.0)
|
||||
|
||||
|
||||
func _speed_levels(vessel: String) -> int:
|
||||
if vessel == VESSEL_SHIELD:
|
||||
return upgrade_level("shd_speed")
|
||||
return upgrade_level("int_speed")
|
||||
|
||||
|
||||
func ship_max_speed(vessel: String) -> float:
|
||||
return SHIP_BASE_SPEED + 40.0 * _speed_levels(vessel)
|
||||
|
||||
|
||||
func ship_acceleration(vessel: String) -> float:
|
||||
return SHIP_BASE_ACCEL + 60.0 * _speed_levels(vessel)
|
||||
|
||||
func ship_damage(vessel: String) -> int: return int(vessel_stat(vessel, "damage"))
|
||||
func ship_fire_cooldown(vessel: String) -> float: return vessel_stat(vessel, "fire_cooldown")
|
||||
func ship_fire_range(vessel: String) -> float: return vessel_stat(vessel, "fire_range")
|
||||
func ship_max_speed(vessel: String) -> float: return vessel_stat(vessel, "max_speed")
|
||||
func ship_acceleration(vessel: String) -> float: return vessel_stat(vessel, "acceleration")
|
||||
|
||||
func shield_max_charges() -> int:
|
||||
return SHIELD_BASE_CHARGES + upgrade_level("shd_hits")
|
||||
|
||||
var definition: Resource = VESSEL_DEFINITIONS.with_capability("shield")
|
||||
return int(vessel_stat(definition.definition_id, "shield_charges"))
|
||||
|
||||
func shield_recharge_time() -> float:
|
||||
return maxf(2.0, SHIELD_BASE_RECHARGE - 1.2 * upgrade_level("shd_recharge"))
|
||||
|
||||
var definition: Resource = VESSEL_DEFINITIONS.with_capability("shield")
|
||||
return vessel_stat(definition.definition_id, "shield_recharge")
|
||||
|
||||
func transporter_load_limit() -> int:
|
||||
return TRANSPORTER_BASE_LOAD + 8 * upgrade_level("trn_capacity")
|
||||
|
||||
var definition: Resource = VESSEL_DEFINITIONS.with_capability("transport")
|
||||
return int(vessel_stat(definition.definition_id, "cargo_capacity"))
|
||||
|
||||
func transporter_speed() -> float:
|
||||
return TRANSPORTER_BASE_SPEED + 70.0 * upgrade_level("trn_speed")
|
||||
var definition: Resource = VESSEL_DEFINITIONS.with_capability("transport")
|
||||
return vessel_stat(definition.definition_id, "transport_speed")
|
||||
|
||||
|
||||
const PRODUCT_SHIP: String = "interceptor"
|
||||
const PRODUCT_TRANSPORTER: String = "transporter"
|
||||
|
||||
const TRANSPORTER_COST: int = 10
|
||||
|
||||
|
||||
func product_cost(product: String) -> int:
|
||||
if product == PRODUCT_TRANSPORTER:
|
||||
return TRANSPORTER_COST
|
||||
if product == VESSEL_SHIELD:
|
||||
return SHIELD_COST
|
||||
return SHIP_COST
|
||||
var definition: Resource = vessel_definition(product)
|
||||
return definition.build_cost if definition != null else 0
|
||||
|
||||
|
||||
func can_produce(type: String) -> bool:
|
||||
|
|
@ -713,13 +685,14 @@ 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
|
||||
if product == VESSEL_SHIELD and not shield_vessel_unlocked():
|
||||
var definition: Resource = vessel_definition(product)
|
||||
if definition == null or not vessel_is_unlocked(product):
|
||||
return false
|
||||
if product == PRODUCT_TRANSPORTER and not has_transport_hub():
|
||||
if definition.has_capability("transport") and not has_transport_hub():
|
||||
# Nowhere to station it. Refused rather than queued, so the player is
|
||||
# told now instead of watching an order sit forever.
|
||||
return false
|
||||
if product == PRODUCT_TRANSPORTER and not can_station_transporter():
|
||||
if definition.has_capability("transport") and not can_station_transporter():
|
||||
# Every berth is taken or spoken for (#20).
|
||||
return false
|
||||
(b.orders as Array).append({"product": product, "elapsed": 0.0, "started": false})
|
||||
|
|
@ -742,15 +715,16 @@ func _advance_production(delta: float) -> void:
|
|||
var product: String = String(order.product)
|
||||
var cost: int = product_cost(product)
|
||||
var inventory: RefCounted = _inventory_for_record(b)
|
||||
var definition: Resource = vessel_definition(product)
|
||||
|
||||
if not bool(order.started):
|
||||
if inventory.amount(RESOURCE_ORE) < cost:
|
||||
continue
|
||||
if product == PRODUCT_TRANSPORTER and transporters.size() >= transport_capacity():
|
||||
if definition.has_capability("transport") and transporters.size() >= transport_capacity():
|
||||
# A hub was demolished after this was ordered, so the berth it
|
||||
# was going to use is gone. Hold rather than overfill.
|
||||
continue
|
||||
if product == PRODUCT_TRANSPORTER and nearest_hub_to(b.position).is_empty():
|
||||
if definition.has_capability("transport") and nearest_hub_to(b.position).is_empty():
|
||||
continue
|
||||
inventory.withdraw(RESOURCE_ORE, cost)
|
||||
order.started = true
|
||||
|
|
@ -760,7 +734,7 @@ func _advance_production(delta: float) -> void:
|
|||
continue
|
||||
|
||||
orders.pop_front()
|
||||
if product == PRODUCT_TRANSPORTER:
|
||||
if definition.has_capability("transport"):
|
||||
# Transporters are unchanged: they take themselves to the nearest
|
||||
# hub and wait for work (#19).
|
||||
_station_transporter(b.position)
|
||||
|
|
@ -782,7 +756,8 @@ func queue_lab_research(lab_id: int, upgrade_id: String) -> bool:
|
|||
if not building_has_capability(lab, "vessel_research") or upgrade_id == "":
|
||||
return false
|
||||
var orders: Array = lab.get("research_orders", [])
|
||||
if not orders.is_empty() or (upgrade_id != UNLOCK_SHIELD_VESSEL and upgrade_maxed(upgrade_id)):
|
||||
var unlock_definition: Resource = vessel_for_unlock(upgrade_id)
|
||||
if not orders.is_empty() or (unlock_definition == null and upgrade_maxed(upgrade_id)):
|
||||
return false
|
||||
orders.append({"id": upgrade_id, "elapsed": 0.0, "started": false})
|
||||
lab.research_orders = orders
|
||||
|
|
@ -804,7 +779,8 @@ func _advance_lab_research(delta: float) -> void:
|
|||
continue
|
||||
var order: Dictionary = orders[0]
|
||||
var id: String = String(order.id)
|
||||
var cost: int = UNLOCK_SHIELD_COST if id == UNLOCK_SHIELD_VESSEL else upgrade_cost(id)
|
||||
var unlock_definition: Resource = vessel_for_unlock(id)
|
||||
var cost: int = unlock_definition.unlock_cost if unlock_definition != null else upgrade_cost(id)
|
||||
var inventory: RefCounted = _inventory_for_record(lab)
|
||||
if not bool(order.started):
|
||||
if inventory.amount(RESOURCE_ORE) < cost:
|
||||
|
|
@ -814,7 +790,7 @@ func _advance_lab_research(delta: float) -> void:
|
|||
order.elapsed = float(order.elapsed) + delta
|
||||
if float(order.elapsed) < lab_research_time():
|
||||
continue
|
||||
if id == UNLOCK_SHIELD_VESSEL:
|
||||
if unlock_definition != null:
|
||||
vessel_levels[id] = 1
|
||||
else:
|
||||
vessel_levels[id] = upgrade_level(id) + 1
|
||||
|
|
@ -1041,7 +1017,7 @@ func pending_transporter_orders() -> int:
|
|||
var n: int = 0
|
||||
for b in buildings:
|
||||
for o in b.get("orders", []):
|
||||
if String(o.product) == PRODUCT_TRANSPORTER:
|
||||
if vessel_has_capability(String(o.product), "transport"):
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue