Store resources in generic inventories
This commit is contained in:
parent
02643c76c4
commit
1721edd123
24 changed files with 385 additions and 172 deletions
236
GameState.gd
236
GameState.gd
|
|
@ -1,5 +1,8 @@
|
|||
extends Node
|
||||
|
||||
const INVENTORY = preload("res://Inventory.gd")
|
||||
const RESOURCE_DEFINITIONS = preload("res://ResourceDefinitions.gd")
|
||||
|
||||
|
||||
# Autoload singleton - the one thing that survives change_scene_to_file()
|
||||
# scene swaps between space (Main.tscn) and a planet's surface
|
||||
|
|
@ -23,126 +26,101 @@ var explored_chunks: Dictionary = {}
|
|||
|
||||
# --- Resources -------------------------------------------------------------
|
||||
|
||||
# Ore that has been BANKED at the mothership. This is the only pool anything
|
||||
# can be bought with.
|
||||
# Starts stocked for play-testing so buildings can be tried without mining
|
||||
# first - drop this back to 0 for a real playthrough.
|
||||
var rocks: int = 500
|
||||
const RESOURCE_ORE: String = RESOURCE_DEFINITIONS.ORE_ID
|
||||
const RESOURCE_GAS: String = RESOURCE_DEFINITIONS.GAS_ID
|
||||
const RESOURCE_TYPES: PackedStringArray = RESOURCE_DEFINITIONS.ORDER
|
||||
|
||||
# Gas is a second, banked resource. It is deliberately separate from ore:
|
||||
# construction still spends ore, while future gas technologies can consume
|
||||
# gas without making the two materials silently interchangeable.
|
||||
var gas: int = 0
|
||||
# All resource-bearing locations use the same container. The old public
|
||||
# names below are computed compatibility accessors, not separate storage.
|
||||
var bank_inventory: RefCounted = INVENTORY.new({RESOURCE_ORE: 500})
|
||||
var fleet_inventory: RefCounted = INVENTORY.new()
|
||||
|
||||
# Ore the fleet is physically carrying and has not delivered yet (#14). Mining
|
||||
# fills this, not `rocks`: ore is cargo until the fleet brings it home, so a
|
||||
# long mining run is a real trip with something to lose rather than instant
|
||||
# income from anywhere on the map.
|
||||
#
|
||||
# Lives here, like everything else that has to survive Main.tscn being torn
|
||||
# down and rebuilt on every trip to a planet's surface - and cargo mined ON a
|
||||
# surface would otherwise be dropped the moment the player took off.
|
||||
var fleet_cargo: int = 0
|
||||
var fleet_gas_cargo: int = 0
|
||||
var rocks: int:
|
||||
get: return bank_inventory.amount(RESOURCE_ORE)
|
||||
set(value): bank_inventory.set_amount(RESOURCE_ORE, value)
|
||||
|
||||
# Resource-facing UI and logistics iterate this list instead of growing a
|
||||
# second set of controls and controller branches every time a material is
|
||||
# introduced. Keep presentation data here with the state it describes so a
|
||||
# resource has one name and colour everywhere it appears.
|
||||
const RESOURCE_ORE: String = "ore"
|
||||
const RESOURCE_GAS: String = "gas"
|
||||
const RESOURCE_TYPES: PackedStringArray = [RESOURCE_ORE, RESOURCE_GAS]
|
||||
const RESOURCE_NAMES: Dictionary = {
|
||||
RESOURCE_ORE: "Ore",
|
||||
RESOURCE_GAS: "Gas",
|
||||
}
|
||||
const RESOURCE_COLORS: Dictionary = {
|
||||
RESOURCE_ORE: Color(0.0, 0.72, 0.82),
|
||||
RESOURCE_GAS: Color(0.85, 0.52, 1.0),
|
||||
}
|
||||
var gas: int:
|
||||
get: return bank_inventory.amount(RESOURCE_GAS)
|
||||
set(value): bank_inventory.set_amount(RESOURCE_GAS, value)
|
||||
|
||||
var fleet_cargo: int:
|
||||
get: return fleet_inventory.amount(RESOURCE_ORE)
|
||||
set(value): fleet_inventory.set_amount(RESOURCE_ORE, value)
|
||||
|
||||
var fleet_gas_cargo: int:
|
||||
get: return fleet_inventory.amount(RESOURCE_GAS)
|
||||
set(value): fleet_inventory.set_amount(RESOURCE_GAS, value)
|
||||
|
||||
func collect_cargo(amount: int = 1) -> void:
|
||||
fleet_cargo += amount
|
||||
fleet_inventory.deposit(RESOURCE_ORE, amount)
|
||||
|
||||
func collect_gas_cargo(amount: int = 1) -> void:
|
||||
fleet_gas_cargo += amount
|
||||
fleet_inventory.deposit(RESOURCE_GAS, amount)
|
||||
|
||||
|
||||
func resource_types() -> PackedStringArray:
|
||||
return RESOURCE_DEFINITIONS.ids()
|
||||
|
||||
|
||||
func resource_definition(resource_type: String) -> Resource:
|
||||
return RESOURCE_DEFINITIONS.get_definition(resource_type)
|
||||
|
||||
|
||||
func resource_name(resource_type: String) -> String:
|
||||
return String(RESOURCE_NAMES.get(resource_type, resource_type.capitalize()))
|
||||
var definition: Resource = resource_definition(resource_type)
|
||||
return String(definition.display_name) if definition != null else resource_type.capitalize()
|
||||
|
||||
|
||||
func resource_color(resource_type: String) -> Color:
|
||||
return RESOURCE_COLORS.get(resource_type, Color.WHITE)
|
||||
var definition: Resource = resource_definition(resource_type)
|
||||
return definition.color if definition != null else Color.WHITE
|
||||
|
||||
|
||||
func banked_resource(resource_type: String) -> int:
|
||||
match resource_type:
|
||||
RESOURCE_ORE: return rocks
|
||||
RESOURCE_GAS: return gas
|
||||
_: return 0
|
||||
return bank_inventory.amount(resource_type)
|
||||
|
||||
|
||||
func fleet_resource(resource_type: String) -> int:
|
||||
match resource_type:
|
||||
RESOURCE_ORE: return fleet_cargo
|
||||
RESOURCE_GAS: return fleet_gas_cargo
|
||||
_: return 0
|
||||
return fleet_inventory.amount(resource_type)
|
||||
|
||||
|
||||
func _inventory_for_record(record: Dictionary) -> RefCounted:
|
||||
var inventory: RefCounted = record.get("inventory") as RefCounted
|
||||
if inventory == null:
|
||||
inventory = INVENTORY.new()
|
||||
record.inventory = inventory
|
||||
# Compatibility migration for state made before #33. Erase each legacy
|
||||
# field once absorbed so it cannot drift out of step with the inventory.
|
||||
if record.has("stock"):
|
||||
inventory.set_amount(RESOURCE_ORE, int(record.stock))
|
||||
record.erase("stock")
|
||||
if record.has("gas_stock"):
|
||||
inventory.set_amount(RESOURCE_GAS, int(record.gas_stock))
|
||||
record.erase("gas_stock")
|
||||
return inventory
|
||||
|
||||
|
||||
func building_inventory(id: int) -> RefCounted:
|
||||
var building: Dictionary = find_building(id)
|
||||
if building.is_empty():
|
||||
return null
|
||||
if building.type == BUILDING_MOTHERSHIP:
|
||||
return bank_inventory
|
||||
return _inventory_for_record(building)
|
||||
|
||||
|
||||
func building_resource(id: int, resource_type: String) -> int:
|
||||
var building: Dictionary = find_building(id)
|
||||
if building.is_empty():
|
||||
return 0
|
||||
if building.type == BUILDING_MOTHERSHIP:
|
||||
return banked_resource(resource_type)
|
||||
match resource_type:
|
||||
RESOURCE_ORE: return int(building.get("stock", 0))
|
||||
RESOURCE_GAS: return int(building.get("gas_stock", 0))
|
||||
_: return 0
|
||||
var inventory: RefCounted = building_inventory(id)
|
||||
return 0 if inventory == null else inventory.amount(resource_type)
|
||||
|
||||
|
||||
func transfer_resource_to_building(id: int, resource_type: String, amount: int) -> int:
|
||||
var building: Dictionary = find_building(id)
|
||||
if building.is_empty():
|
||||
return 0
|
||||
var moved: int = mini(maxi(amount, 0), fleet_resource(resource_type))
|
||||
if moved <= 0:
|
||||
return 0
|
||||
match resource_type:
|
||||
RESOURCE_ORE: fleet_cargo -= moved
|
||||
RESOURCE_GAS: fleet_gas_cargo -= moved
|
||||
_: return 0
|
||||
if building.type == BUILDING_MOTHERSHIP:
|
||||
match resource_type:
|
||||
RESOURCE_ORE: rocks += moved
|
||||
RESOURCE_GAS: gas += moved
|
||||
else:
|
||||
match resource_type:
|
||||
RESOURCE_ORE: building.stock = int(building.get("stock", 0)) + moved
|
||||
RESOURCE_GAS: building.gas_stock = int(building.get("gas_stock", 0)) + moved
|
||||
return moved
|
||||
return fleet_inventory.transfer_to(building_inventory(id), resource_type, amount)
|
||||
|
||||
|
||||
func transfer_resource_to_fleet(id: int, resource_type: String, amount: int) -> int:
|
||||
var building: Dictionary = find_building(id)
|
||||
if building.is_empty():
|
||||
return 0
|
||||
var moved: int = mini(maxi(amount, 0), building_resource(id, resource_type))
|
||||
if moved <= 0:
|
||||
return 0
|
||||
if building.type == BUILDING_MOTHERSHIP:
|
||||
match resource_type:
|
||||
RESOURCE_ORE: rocks -= moved
|
||||
RESOURCE_GAS: gas -= moved
|
||||
else:
|
||||
match resource_type:
|
||||
RESOURCE_ORE: building.stock = int(building.get("stock", 0)) - moved
|
||||
RESOURCE_GAS: building.gas_stock = int(building.get("gas_stock", 0)) - moved
|
||||
match resource_type:
|
||||
RESOURCE_ORE: fleet_cargo += moved
|
||||
RESOURCE_GAS: fleet_gas_cargo += moved
|
||||
return moved
|
||||
var source: RefCounted = building_inventory(id)
|
||||
return 0 if source == null else source.transfer_to(fleet_inventory, resource_type, amount)
|
||||
|
||||
|
||||
# The batch size the transfer menu offers, next to "All". A batch rather
|
||||
|
|
@ -401,8 +379,8 @@ func add_building(type: String, at_position: Vector2) -> int:
|
|||
var id: int = next_building_id
|
||||
next_building_id += 1
|
||||
buildings.append({
|
||||
"id": id, "type": type, "position": at_position, "stock": 0,
|
||||
"gas_stock": 0, "orders": [],
|
||||
"id": id, "type": type, "position": at_position,
|
||||
"inventory": INVENTORY.new(), "orders": [],
|
||||
})
|
||||
return id
|
||||
|
||||
|
|
@ -655,8 +633,7 @@ func can_produce(type: String) -> bool:
|
|||
|
||||
|
||||
func building_stock(id: int) -> int:
|
||||
var b: Dictionary = find_building(id)
|
||||
return 0 if b.is_empty() else int(b.get("stock", 0))
|
||||
return building_resource(id, RESOURCE_ORE)
|
||||
|
||||
|
||||
func pending_orders(id: int) -> int:
|
||||
|
|
@ -703,7 +680,7 @@ func cancel_order(id: int, index: int) -> bool:
|
|||
return false
|
||||
var order: Dictionary = orders[index]
|
||||
if bool(order.started):
|
||||
b.stock = int(b.get("stock", 0)) + product_cost(String(order.product))
|
||||
_inventory_for_record(b).deposit(RESOURCE_ORE, product_cost(String(order.product)))
|
||||
orders.remove_at(index)
|
||||
return true
|
||||
|
||||
|
|
@ -718,7 +695,7 @@ func outstanding_cost(id: int) -> int:
|
|||
# A started build has already paid, so it is not still owed.
|
||||
if not bool(o.started):
|
||||
need += product_cost(String(o.product))
|
||||
return maxi(need - int(b.get("stock", 0)), 0)
|
||||
return maxi(need - _inventory_for_record(b).amount(RESOURCE_ORE), 0)
|
||||
|
||||
|
||||
func deposit_to_building(id: int, amount: int) -> int:
|
||||
|
|
@ -727,8 +704,7 @@ func deposit_to_building(id: int, amount: int) -> int:
|
|||
var b: Dictionary = find_building(id)
|
||||
if b.is_empty():
|
||||
return 0
|
||||
b.stock = int(b.get("stock", 0)) + amount
|
||||
return amount
|
||||
return _inventory_for_record(b).deposit(RESOURCE_ORE, amount)
|
||||
|
||||
|
||||
func queue_order(id: int, product: String = PRODUCT_SHIP) -> bool:
|
||||
|
|
@ -763,9 +739,10 @@ func _advance_production(delta: float) -> void:
|
|||
var order: Dictionary = orders[0]
|
||||
var product: String = String(order.product)
|
||||
var cost: int = product_cost(product)
|
||||
var inventory: RefCounted = _inventory_for_record(b)
|
||||
|
||||
if not bool(order.started):
|
||||
if int(b.get("stock", 0)) < cost:
|
||||
if inventory.amount(RESOURCE_ORE) < cost:
|
||||
continue
|
||||
if product == PRODUCT_TRANSPORTER and transporters.size() >= transport_capacity():
|
||||
# A hub was demolished after this was ordered, so the berth it
|
||||
|
|
@ -773,7 +750,7 @@ func _advance_production(delta: float) -> void:
|
|||
continue
|
||||
if product == PRODUCT_TRANSPORTER and nearest_hub_to(b.position).is_empty():
|
||||
continue
|
||||
b.stock = int(b.stock) - cost
|
||||
inventory.withdraw(RESOURCE_ORE, cost)
|
||||
order.started = true
|
||||
|
||||
order.elapsed = float(order.elapsed) + delta
|
||||
|
|
@ -826,10 +803,11 @@ func _advance_lab_research(delta: float) -> void:
|
|||
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 inventory: RefCounted = _inventory_for_record(lab)
|
||||
if not bool(order.started):
|
||||
if int(lab.get("stock", 0)) < cost:
|
||||
if inventory.amount(RESOURCE_ORE) < cost:
|
||||
continue
|
||||
lab.stock = int(lab.stock) - cost
|
||||
inventory.withdraw(RESOURCE_ORE, cost)
|
||||
order.started = true
|
||||
order.elapsed = float(order.elapsed) + delta
|
||||
if float(order.elapsed) < lab_research_time():
|
||||
|
|
@ -918,7 +896,7 @@ func rotate_formation(radians: float) -> void:
|
|||
# Extractors mine into their OWN local stock. They do not own a magical
|
||||
# hauler: ore becomes spendable only when a hub-stationed transporter picks
|
||||
# up a full load and physically delivers it to the mothership. Entries are
|
||||
# {"position", "planet_name", "resource_type", "stock", "mining_progress"}.
|
||||
# {"position", "planet_name", "resource_type", "inventory", "mining_progress"}.
|
||||
const EXTRACTOR_RATE: float = 2.0 # ore per second
|
||||
|
||||
var extractors: Array = []
|
||||
|
|
@ -939,7 +917,7 @@ func add_extractor(planet_name: String, at_position: Vector2, resource_type: Str
|
|||
"position": at_position,
|
||||
"planet_name": planet_name,
|
||||
"resource_type": resource_type,
|
||||
"stock": 0,
|
||||
"inventory": INVENTORY.new(),
|
||||
"mining_progress": 0.0,
|
||||
})
|
||||
# An extractor is built from SPACE, with the fleet parked on the planet -
|
||||
|
|
@ -952,7 +930,21 @@ func add_extractor(planet_name: String, at_position: Vector2, resource_type: Str
|
|||
func extractor_stock(index: int) -> int:
|
||||
if index < 0 or index >= extractors.size():
|
||||
return 0
|
||||
return int(extractors[index].get("stock", 0))
|
||||
var extractor: Dictionary = extractors[index]
|
||||
return _extractor_inventory(extractor).amount(String(extractor.get("resource_type", RESOURCE_ORE)))
|
||||
|
||||
|
||||
func _extractor_inventory(extractor: Dictionary) -> RefCounted:
|
||||
var inventory: RefCounted = extractor.get("inventory") as RefCounted
|
||||
if inventory == null:
|
||||
inventory = INVENTORY.new()
|
||||
extractor.inventory = inventory
|
||||
# Old extractor state had one stock field whose material was described by
|
||||
# resource_type. Absorb it into that key on first access.
|
||||
if extractor.has("stock"):
|
||||
inventory.set_amount(String(extractor.get("resource_type", RESOURCE_ORE)), int(extractor.stock))
|
||||
extractor.erase("stock")
|
||||
return inventory
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
|
@ -965,7 +957,7 @@ func _process(delta: float) -> void:
|
|||
e.mining_progress = float(e.get("mining_progress", 0.0)) + delta * EXTRACTOR_RATE
|
||||
var whole: int = int(floor(float(e.mining_progress)))
|
||||
if whole > 0:
|
||||
e.stock = int(e.get("stock", 0)) + whole
|
||||
_extractor_inventory(e).deposit(String(e.get("resource_type", RESOURCE_ORE)), whole)
|
||||
e.mining_progress = float(e.mining_progress) - whole
|
||||
_advance_transporters(delta)
|
||||
|
||||
|
|
@ -1127,7 +1119,8 @@ func _dispatch_transporters() -> void:
|
|||
var load: int = transporter_load_limit()
|
||||
for i in range(extractors.size()):
|
||||
var extractor: Dictionary = extractors[i]
|
||||
if int(extractor.get("stock", 0)) < load or _transporter_inbound_to_extractor(i):
|
||||
var resource_id: String = String(extractor.get("resource_type", RESOURCE_ORE))
|
||||
if _extractor_inventory(extractor).amount(resource_id) < load or _transporter_inbound_to_extractor(i):
|
||||
continue
|
||||
var chosen_extractor: Dictionary = {}
|
||||
var best_extractor_distance: float = INF
|
||||
|
|
@ -1141,7 +1134,7 @@ func _dispatch_transporters() -> void:
|
|||
if chosen_extractor.is_empty():
|
||||
return
|
||||
chosen_extractor.extractor_index = i
|
||||
chosen_extractor.resource_type = String(extractor.get("resource_type", "ore"))
|
||||
chosen_extractor.resource_type = resource_id
|
||||
chosen_extractor.target_id = 0
|
||||
_begin_leg(chosen_extractor, transporter_position(chosen_extractor), extractor.position,
|
||||
TRANSPORTER_TO_EXTRACTOR)
|
||||
|
|
@ -1192,13 +1185,13 @@ func _arrive(t: Dictionary) -> void:
|
|||
TRANSPORTER_TO_MOTHERSHIP:
|
||||
var target: Dictionary = find_building(int(t.target_id))
|
||||
var want: int = outstanding_cost(int(t.target_id))
|
||||
var take: int = mini(mini(want, rocks), transporter_load_limit())
|
||||
var take: int = mini(mini(want, bank_inventory.amount(RESOURCE_ORE)), transporter_load_limit())
|
||||
if take <= 0 or target.is_empty():
|
||||
# Nothing to carry, or the building is gone. Go home empty.
|
||||
t.target_id = 0
|
||||
_begin_leg(t, here, hub_pos if hub_pos != null else here, TRANSPORTER_RETURNING)
|
||||
return
|
||||
rocks -= take
|
||||
bank_inventory.withdraw(RESOURCE_ORE, take)
|
||||
t.carrying = take
|
||||
_begin_leg(t, here, target.position, TRANSPORTER_TO_BUILDING)
|
||||
TRANSPORTER_TO_BUILDING:
|
||||
|
|
@ -1206,7 +1199,7 @@ func _arrive(t: Dictionary) -> void:
|
|||
if deposit_to_building(int(t.target_id), int(t.carrying)) <= 0:
|
||||
# Building demolished in transit - put the ore back rather
|
||||
# than quietly destroying it.
|
||||
rocks += int(t.carrying)
|
||||
bank_inventory.deposit(RESOURCE_ORE, int(t.carrying))
|
||||
t.carrying = 0
|
||||
t.target_id = 0
|
||||
_begin_leg(t, here, hub_pos if hub_pos != null else here, TRANSPORTER_RETURNING)
|
||||
|
|
@ -1217,20 +1210,19 @@ func _arrive(t: Dictionary) -> void:
|
|||
return
|
||||
var extractor: Dictionary = extractors[extractor_index]
|
||||
var load: int = transporter_load_limit()
|
||||
if int(extractor.get("stock", 0)) < load:
|
||||
var resource_id: String = String(extractor.get("resource_type", RESOURCE_ORE))
|
||||
var extractor_inventory: RefCounted = _extractor_inventory(extractor)
|
||||
if extractor_inventory.amount(resource_id) < load:
|
||||
t.extractor_index = -1
|
||||
_begin_leg(t, here, hub_pos if hub_pos != null else here, TRANSPORTER_RETURNING)
|
||||
return
|
||||
extractor.stock = int(extractor.stock) - load
|
||||
extractor_inventory.withdraw(resource_id, load)
|
||||
t.carrying = load
|
||||
t.resource_type = String(extractor.get("resource_type", "ore"))
|
||||
t.resource_type = resource_id
|
||||
_begin_leg(t, here, get_mothership_position(), TRANSPORTER_TO_MOTHERSHIP_FROM_EXTRACTOR)
|
||||
TRANSPORTER_TO_MOTHERSHIP_FROM_EXTRACTOR:
|
||||
# Bank only on arrival; cargo retains the extractor's resource type.
|
||||
if String(t.get("resource_type", "ore")) == "gas":
|
||||
gas += int(t.carrying)
|
||||
else:
|
||||
rocks += int(t.carrying)
|
||||
bank_inventory.deposit(String(t.get("resource_type", RESOURCE_ORE)), int(t.carrying))
|
||||
t.carrying = 0
|
||||
t.extractor_index = -1
|
||||
_begin_leg(t, here, hub_pos if hub_pos != null else here, TRANSPORTER_RETURNING)
|
||||
|
|
@ -1240,11 +1232,11 @@ func _arrive(t: Dictionary) -> void:
|
|||
|
||||
|
||||
func can_afford(cost: int) -> bool:
|
||||
return rocks >= cost
|
||||
return bank_inventory.amount(RESOURCE_ORE) >= cost
|
||||
|
||||
|
||||
func spend(cost: int) -> bool:
|
||||
if not can_afford(cost):
|
||||
return false
|
||||
rocks -= cost
|
||||
bank_inventory.withdraw(RESOURCE_ORE, cost)
|
||||
return true
|
||||
|
|
|
|||
11
GasResourceDefinition.tres
Normal file
11
GasResourceDefinition.tres
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[gd_resource type="Resource" script_class="ResourceDefinition" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ResourceDefinition.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://GasResourceGlyph.tscn" id="2"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
resource_id = "gas"
|
||||
display_name = "Gas"
|
||||
color = Color(0.85, 0.52, 1, 1)
|
||||
glyph_scene = ExtResource("2")
|
||||
19
GasResourceGlyph.gd
Normal file
19
GasResourceGlyph.gd
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
extends Control
|
||||
|
||||
@export var colour: Color = Color(0.85, 0.52, 1.0)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var centre: Vector2 = size * 0.5
|
||||
var radius: float = minf(size.x, size.y) * 0.34
|
||||
var bubbles: Array = [
|
||||
[centre + Vector2(-radius * 0.38, radius * 0.12), radius * 0.46],
|
||||
[centre + Vector2(radius * 0.34, radius * 0.26), radius * 0.58],
|
||||
[centre + Vector2(radius * 0.38, -radius * 0.48), radius * 0.32],
|
||||
]
|
||||
for bubble in bubbles:
|
||||
var ring := PackedVector2Array()
|
||||
for i in range(12):
|
||||
var angle: float = TAU * float(i) / 12.0
|
||||
ring.append(bubble[0] + Vector2(cos(angle), sin(angle)) * float(bubble[1]))
|
||||
NeonDraw.draw_glowing_polygon(self, ring, colour, 1.0, colour.darkened(0.78), maxf(0.5, radius * 0.035))
|
||||
1
GasResourceGlyph.gd.uid
Normal file
1
GasResourceGlyph.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d1ko6yhtjjavx
|
||||
13
GasResourceGlyph.tscn
Normal file
13
GasResourceGlyph.tscn
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://GasResourceGlyph.gd" id="1"]
|
||||
|
||||
[node name="GlyphVisual" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1")
|
||||
79
Inventory.gd
Normal file
79
Inventory.gd
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
class_name Inventory
|
||||
extends RefCounted
|
||||
|
||||
var total_capacity: int = -1
|
||||
var resource_capacities: Dictionary = {}
|
||||
var _amounts: Dictionary = {}
|
||||
|
||||
|
||||
func _init(initial: Dictionary = {}, capacity: int = -1) -> void:
|
||||
total_capacity = capacity
|
||||
for resource_id in initial:
|
||||
set_amount(String(resource_id), int(initial[resource_id]))
|
||||
|
||||
|
||||
func amount(resource_id: String) -> int:
|
||||
return int(_amounts.get(resource_id, 0))
|
||||
|
||||
|
||||
func total() -> int:
|
||||
var result: int = 0
|
||||
for value in _amounts.values():
|
||||
result += int(value)
|
||||
return result
|
||||
|
||||
|
||||
func capacity_for(resource_id: String) -> int:
|
||||
return int(resource_capacities.get(resource_id, -1))
|
||||
|
||||
|
||||
func space_for(resource_id: String) -> int:
|
||||
var space: int = 1 << 30
|
||||
if total_capacity >= 0:
|
||||
space = maxi(total_capacity - total(), 0)
|
||||
var resource_capacity: int = capacity_for(resource_id)
|
||||
if resource_capacity >= 0:
|
||||
space = mini(space, maxi(resource_capacity - amount(resource_id), 0))
|
||||
return space
|
||||
|
||||
|
||||
func set_amount(resource_id: String, value: int) -> int:
|
||||
_amounts.erase(resource_id)
|
||||
var accepted: int = mini(maxi(value, 0), space_for(resource_id))
|
||||
if accepted > 0:
|
||||
_amounts[resource_id] = accepted
|
||||
return amount(resource_id)
|
||||
|
||||
|
||||
func deposit(resource_id: String, requested: int) -> int:
|
||||
var moved: int = mini(maxi(requested, 0), space_for(resource_id))
|
||||
if moved > 0:
|
||||
_amounts[resource_id] = amount(resource_id) + moved
|
||||
return moved
|
||||
|
||||
|
||||
func withdraw(resource_id: String, requested: int) -> int:
|
||||
var moved: int = mini(maxi(requested, 0), amount(resource_id))
|
||||
if moved <= 0:
|
||||
return 0
|
||||
var remaining: int = amount(resource_id) - moved
|
||||
if remaining > 0:
|
||||
_amounts[resource_id] = remaining
|
||||
else:
|
||||
_amounts.erase(resource_id)
|
||||
return moved
|
||||
|
||||
|
||||
func transfer_to(destination: Inventory, resource_id: String, requested: int) -> int:
|
||||
if destination == null:
|
||||
return 0
|
||||
var moved: int = mini(mini(maxi(requested, 0), amount(resource_id)), destination.space_for(resource_id))
|
||||
if moved <= 0:
|
||||
return 0
|
||||
withdraw(resource_id, moved)
|
||||
destination.deposit(resource_id, moved)
|
||||
return moved
|
||||
|
||||
|
||||
func snapshot() -> Dictionary:
|
||||
return _amounts.duplicate()
|
||||
1
Inventory.gd.uid
Normal file
1
Inventory.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://celx1yj6cp2rf
|
||||
11
OreResourceDefinition.tres
Normal file
11
OreResourceDefinition.tres
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[gd_resource type="Resource" script_class="ResourceDefinition" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ResourceDefinition.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://OreResourceGlyph.tscn" id="2"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
resource_id = "ore"
|
||||
display_name = "Ore"
|
||||
color = Color(0, 0.72, 0.82, 1)
|
||||
glyph_scene = ExtResource("2")
|
||||
14
OreResourceGlyph.gd
Normal file
14
OreResourceGlyph.gd
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
extends Control
|
||||
|
||||
@export var colour: Color = Color(0.0, 0.72, 0.82)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var centre: Vector2 = size * 0.5
|
||||
var radius: float = minf(size.x, size.y) * 0.34
|
||||
var points := PackedVector2Array()
|
||||
for i in range(6):
|
||||
var angle: float = TAU * float(i) / 6.0 - PI * 0.5
|
||||
points.append(centre + Vector2(cos(angle), sin(angle)) * radius)
|
||||
NeonDraw.draw_glowing_polygon(self, points, colour, 1.0, colour.darkened(0.78), maxf(0.5, radius * 0.035))
|
||||
NeonDraw.draw_glowing_line(self, points[5], points[2], colour, 0.55, maxf(0.4, radius * 0.025), colour.darkened(0.78))
|
||||
1
OreResourceGlyph.gd.uid
Normal file
1
OreResourceGlyph.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cwgkhqo4ev5ao
|
||||
13
OreResourceGlyph.tscn
Normal file
13
OreResourceGlyph.tscn
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://OreResourceGlyph.gd" id="1"]
|
||||
|
||||
[node name="GlyphVisual" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1")
|
||||
|
|
@ -3,7 +3,7 @@ extends Control
|
|||
|
||||
# Compact resource table shared by crafting, research and the ship lab.
|
||||
# Rows are resources; columns are locations. Adding another material to
|
||||
# GameState.RESOURCE_TYPES automatically gives it a row here.
|
||||
# The resource-definition catalog automatically gives each material a row.
|
||||
|
||||
var building_id: int = 0
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ func _ready() -> void:
|
|||
add_child(label)
|
||||
_header_labels.append(label)
|
||||
|
||||
for resource_type in GameState.RESOURCE_TYPES:
|
||||
for resource_type in GameState.resource_types():
|
||||
var glyph := ResourceGlyph.new()
|
||||
glyph.resource_type = resource_type
|
||||
add_child(glyph)
|
||||
|
|
@ -78,7 +78,7 @@ func _layout() -> void:
|
|||
|
||||
var row_height: float = (size.y - HEADER_HEIGHT - 4.0) / maxf(1.0, float(_rows.size()))
|
||||
var row_index: int = 0
|
||||
for resource_type in GameState.RESOURCE_TYPES:
|
||||
for resource_type in GameState.resource_types():
|
||||
var row: Dictionary = _rows[resource_type]
|
||||
var y: float = HEADER_HEIGHT + float(row_index) * row_height
|
||||
row.glyph.position = Vector2(2.0, y + 2.0)
|
||||
|
|
@ -94,7 +94,7 @@ func _layout() -> void:
|
|||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
for resource_type in GameState.RESOURCE_TYPES:
|
||||
for resource_type in GameState.resource_types():
|
||||
var row: Dictionary = _rows.get(resource_type, {})
|
||||
if row.is_empty():
|
||||
continue
|
||||
|
|
|
|||
7
ResourceDefinition.gd
Normal file
7
ResourceDefinition.gd
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class_name ResourceDefinition
|
||||
extends Resource
|
||||
|
||||
@export var resource_id: String = "ore"
|
||||
@export var display_name: String = "Ore"
|
||||
@export var color: Color = Color.WHITE
|
||||
@export var glyph_scene: PackedScene
|
||||
1
ResourceDefinition.gd.uid
Normal file
1
ResourceDefinition.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://kvy02iu7pbyu
|
||||
18
ResourceDefinitions.gd
Normal file
18
ResourceDefinitions.gd
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
class_name ResourceDefinitions
|
||||
extends RefCounted
|
||||
|
||||
const ORE_ID: String = "ore"
|
||||
const GAS_ID: String = "gas"
|
||||
const ORDER: PackedStringArray = [ORE_ID, GAS_ID]
|
||||
const DEFINITIONS: Dictionary = {
|
||||
ORE_ID: preload("res://OreResourceDefinition.tres"),
|
||||
GAS_ID: preload("res://GasResourceDefinition.tres"),
|
||||
}
|
||||
|
||||
|
||||
static func get_definition(resource_id: String) -> Resource:
|
||||
return DEFINITIONS.get(resource_id)
|
||||
|
||||
|
||||
static func ids() -> PackedStringArray:
|
||||
return ORDER
|
||||
1
ResourceDefinitions.gd.uid
Normal file
1
ResourceDefinitions.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://ci7yyyxyd3o67
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
class_name ResourceGlyph
|
||||
extends Control
|
||||
|
||||
const RESOURCE_DEFINITIONS = preload("res://ResourceDefinitions.gd")
|
||||
|
||||
# A small, non-interactive resource mark shared by tables and transfer rows.
|
||||
# Resource buttons used to borrow IconButton, which made decorative icons
|
||||
# look tappable and encouraged each screen to invent its own version.
|
||||
|
|
@ -12,43 +14,21 @@ func set_resource_type(value: String) -> void:
|
|||
if resource_type == value:
|
||||
return
|
||||
resource_type = value
|
||||
queue_redraw()
|
||||
if is_node_ready():
|
||||
_rebuild_visual()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
queue_redraw()
|
||||
_rebuild_visual()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var colour: Color = GameState.resource_color(resource_type)
|
||||
var centre: Vector2 = size * 0.5
|
||||
var radius: float = minf(size.x, size.y) * 0.34
|
||||
match resource_type:
|
||||
GameState.RESOURCE_GAS:
|
||||
_draw_gas(centre, radius, colour)
|
||||
_:
|
||||
_draw_ore(centre, radius, colour)
|
||||
|
||||
|
||||
func _draw_ore(centre: Vector2, radius: float, colour: Color) -> void:
|
||||
var points := PackedVector2Array()
|
||||
for i in range(6):
|
||||
var angle: float = TAU * float(i) / 6.0 - PI * 0.5
|
||||
points.append(centre + Vector2(cos(angle), sin(angle)) * radius)
|
||||
NeonDraw.draw_glowing_polygon(self, points, colour, 1.0, colour.darkened(0.78), maxf(0.5, radius * 0.035))
|
||||
NeonDraw.draw_glowing_line(self, points[5], points[2], colour, 0.55, maxf(0.4, radius * 0.025), colour.darkened(0.78))
|
||||
|
||||
|
||||
func _draw_gas(centre: Vector2, radius: float, colour: Color) -> void:
|
||||
var bubbles: Array = [
|
||||
[centre + Vector2(-radius * 0.38, radius * 0.12), radius * 0.46],
|
||||
[centre + Vector2(radius * 0.34, radius * 0.26), radius * 0.58],
|
||||
[centre + Vector2(radius * 0.38, -radius * 0.48), radius * 0.32],
|
||||
]
|
||||
for bubble in bubbles:
|
||||
var ring := PackedVector2Array()
|
||||
for i in range(12):
|
||||
var angle: float = TAU * float(i) / 12.0
|
||||
ring.append(bubble[0] + Vector2(cos(angle), sin(angle)) * float(bubble[1]))
|
||||
NeonDraw.draw_glowing_polygon(self, ring, colour, 1.0, colour.darkened(0.78), maxf(0.5, radius * 0.035))
|
||||
func _rebuild_visual() -> void:
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
var definition: Resource = RESOURCE_DEFINITIONS.get_definition(resource_type)
|
||||
if definition == null or definition.glyph_scene == null:
|
||||
return
|
||||
var visual: Control = definition.glyph_scene.instantiate()
|
||||
visual.colour = definition.color
|
||||
add_child(visual)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func _ready() -> void:
|
|||
_destination_heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
add_child(_destination_heading)
|
||||
|
||||
for resource_type in GameState.RESOURCE_TYPES:
|
||||
for resource_type in GameState.resource_types():
|
||||
var colour: Color = GameState.resource_color(resource_type)
|
||||
var glyph := ResourceGlyph.new()
|
||||
glyph.resource_type = resource_type
|
||||
|
|
@ -140,7 +140,7 @@ func set_destination(id: int, heading: String) -> void:
|
|||
|
||||
|
||||
func refresh() -> void:
|
||||
for resource_type in GameState.RESOURCE_TYPES:
|
||||
for resource_type in GameState.resource_types():
|
||||
var row: Dictionary = _rows.get(resource_type, {})
|
||||
if row.is_empty():
|
||||
continue
|
||||
|
|
@ -169,7 +169,7 @@ func _layout() -> void:
|
|||
|
||||
var row_height: float = (size.y - HEADER_HEIGHT) / maxf(1.0, float(_rows.size()))
|
||||
var row_index: int = 0
|
||||
for resource_type in GameState.RESOURCE_TYPES:
|
||||
for resource_type in GameState.resource_types():
|
||||
var row: Dictionary = _rows[resource_type]
|
||||
var y: float = HEADER_HEIGHT + float(row_index) * row_height
|
||||
var glyph_size: float = minf(66.0, row_height * 0.23)
|
||||
|
|
|
|||
48
tests/test_generic_inventory.gd
Normal file
48
tests/test_generic_inventory.gd
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
extends SceneTree
|
||||
|
||||
const INVENTORY = preload("res://Inventory.gd")
|
||||
const TEST_RESOURCE: String = "crystal"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var game_state: Node = root.get_node("GameState")
|
||||
game_state.buildings.clear()
|
||||
game_state.bank_inventory.set_amount(TEST_RESOURCE, 40)
|
||||
game_state.fleet_inventory.set_amount(TEST_RESOURCE, 0)
|
||||
|
||||
var mothership_id: int = game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
|
||||
var crafter_id: int = game_state.add_building(GameState.BUILDING_SHIP_CRAFTER, Vector2(100, 0))
|
||||
if game_state.transfer_resource_to_fleet(mothership_id, TEST_RESOURCE, 15) != 15:
|
||||
return _fail("third resource could not move from bank to fleet")
|
||||
if game_state.transfer_resource_to_building(crafter_id, TEST_RESOURCE, 9) != 9:
|
||||
return _fail("third resource could not move from fleet to building")
|
||||
if game_state.transfer_resource_to_fleet(crafter_id, TEST_RESOURCE, 4) != 4:
|
||||
return _fail("third resource could not move back out of building")
|
||||
if game_state.banked_resource(TEST_RESOURCE) != 25 \
|
||||
or game_state.fleet_resource(TEST_RESOURCE) != 10 \
|
||||
or game_state.building_resource(crafter_id, TEST_RESOURCE) != 5:
|
||||
return _fail("third-resource amounts were not conserved")
|
||||
|
||||
var limited: RefCounted = INVENTORY.new({}, 10)
|
||||
limited.resource_capacities[TEST_RESOURCE] = 6
|
||||
if limited.deposit(TEST_RESOURCE, 99) != 6 or limited.deposit("water", 99) != 4:
|
||||
return _fail("total or per-resource capacity was not enforced")
|
||||
|
||||
var legacy_id: int = game_state.next_building_id
|
||||
game_state.next_building_id += 1
|
||||
game_state.buildings.append({"id": legacy_id, "type": GameState.BUILDING_SHIP_CRAFTER,
|
||||
"position": Vector2.ZERO, "stock": 7, "gas_stock": 3, "orders": []})
|
||||
if game_state.building_resource(legacy_id, GameState.RESOURCE_ORE) != 7 \
|
||||
or game_state.building_resource(legacy_id, GameState.RESOURCE_GAS) != 3:
|
||||
return _fail("legacy building stock was not migrated")
|
||||
var migrated: Dictionary = game_state.find_building(legacy_id)
|
||||
if migrated.has("stock") or migrated.has("gas_stock") or not migrated.has("inventory"):
|
||||
return _fail("legacy fields remained after migration")
|
||||
|
||||
print("TEST_GENERIC_INVENTORY: PASS")
|
||||
quit(0)
|
||||
|
||||
|
||||
func _fail(message: String) -> void:
|
||||
print("TEST_GENERIC_INVENTORY: FAIL (%s)" % message)
|
||||
quit(1)
|
||||
1
tests/test_generic_inventory.gd.uid
Normal file
1
tests/test_generic_inventory.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://53pexhmp3ns0
|
||||
|
|
@ -42,7 +42,8 @@ const CRAFTER_AT: Vector2 = Vector2(3000.0, 0.0)
|
|||
func _total() -> int:
|
||||
var sum: int = game_state.rocks + game_state.fleet_cargo
|
||||
for b in game_state.buildings:
|
||||
sum += int(b.get("stock", 0))
|
||||
if b.type != GameState.BUILDING_MOTHERSHIP:
|
||||
sum += game_state.building_resource(int(b.id), GameState.RESOURCE_ORE)
|
||||
return sum
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func _total() -> int:
|
|||
var sum: int = game_state.rocks + game_state.fleet_cargo
|
||||
for b in game_state.buildings:
|
||||
if b.type != GameState.BUILDING_MOTHERSHIP:
|
||||
sum += int(b.get("stock", 0))
|
||||
sum += game_state.building_resource(int(b.id), GameState.RESOURCE_ORE)
|
||||
return sum
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func _process(delta: float) -> bool:
|
|||
match stage:
|
||||
0:
|
||||
# --- gather a rock -------------------------------------------
|
||||
scene.get_node("RockField").set_process(false)
|
||||
scene.get_node("SurfaceFeatures/ResourceProvider").set_process(false)
|
||||
rock = load("res://Rock.tscn").instantiate()
|
||||
rock.build_shape(1)
|
||||
# Placed OUTSIDE the pickup shape but inside attract_radius, so
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ const CRAFTER_AT: Vector2 = Vector2(900.0, -300.0)
|
|||
func _total() -> int:
|
||||
var sum: int = game_state.rocks + game_state.fleet_cargo
|
||||
for b in game_state.buildings:
|
||||
sum += int(b.get("stock", 0))
|
||||
if b.type != GameState.BUILDING_MOTHERSHIP:
|
||||
sum += game_state.building_resource(int(b.id), GameState.RESOURCE_ORE)
|
||||
for t in game_state.transporters:
|
||||
sum += int(t.get("carrying", 0))
|
||||
# Ore inside a build that has STARTED but not finished (#23). It has left
|
||||
|
|
|
|||
Loading…
Reference in a new issue