Add supply auto-purchase and preserve save recovery state

This commit is contained in:
Takeshi
2026-10-02 07:35:03 +09:00
parent 285528b7a3
commit 39f6068461
44 changed files with 2625 additions and 61 deletions
+40 -7
View File
@@ -19,7 +19,10 @@ const CONSTRUCTION_EXPENSE_ACCOUNT := &"CONSTRUCTION_EXPENSE"
const OPERATING_EXPENSE_ACCOUNT := &"OPERATING_EXPENSE"
const AUTHORITY_FINE_ACCOUNT := &"AUTHORITY_FINE_EXPENSE"
const WAGE_EXPENSE_ACCOUNT := &"WAGE_EXPENSE"
const RUNTIME_SNAPSHOT_VERSION := 13
const RUNTIME_SNAPSHOT_VERSION := 14
const AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION := 14
const EVENT_THREAT_RUNTIME_SNAPSHOT_VERSION := 13
const AutoPurchasePlansType := preload("res://scripts/supply/supply_auto_purchase_plans.gd")
const CLEANING_RUNTIME_SNAPSHOT_VERSION := 12
const WEATHER_RUNTIME_SNAPSHOT_VERSION := 11
const FacilityCleanliness = preload("res://scripts/environment/facility_cleanliness_service.gd")
@@ -110,6 +113,7 @@ var logistics_dispatcher := LogisticsDispatcher.new()
var logistics_requests: Dictionary = {}
var kitchen_production := KitchenProductionService.new()
var supply_management := SupplyManagementService.new()
var auto_purchase_plans := AutoPurchasePlansType.new()
var supply_logistics := SupplyLogisticsBridge.new()
var elevators := ElevatorService.new()
var legacy_stairs := LegacyStairService.new()
@@ -300,6 +304,7 @@ func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_S
register_command_executor(&"complete_progression_quest", Callable(self, "_execute_complete_progression_quest"))
register_command_executor(&"acknowledge_operations_pause", Callable(self, "_execute_acknowledge_operations_pause"))
register_command_executor(&"purchase_supplies", Callable(self, "_execute_purchase_supplies"))
register_command_executor(&"set_supply_auto_purchase", Callable(self, "_execute_set_supply_auto_purchase"))
register_command_executor(&"set_kitchen_production", Callable(self, "_execute_set_kitchen_production"))
register_command_executor(&"add_inventory_stack", Callable(self, "_execute_add_inventory_stack"))
register_command_executor(&"create_logistics_request", Callable(self, "_execute_create_logistics_request"))
@@ -502,9 +507,7 @@ func get_rng_stream(stream_id: StringName) -> DeterministicRng:
func create_snapshot() -> Dictionary:
var rng_data := {}
var stream_ids: Array = _rng_streams.keys()
stream_ids.sort()
for stream_id: Variant in stream_ids:
for stream_id: StringName in _sorted_name_keys(_rng_streams):
var stream: DeterministicRng = _rng_streams[stream_id]
rng_data[String(stream_id)] = stream.snapshot()
var owned_systems := {
@@ -531,6 +534,7 @@ func create_snapshot() -> Dictionary:
&"upkeep_settlement": upkeep_settlement.create_snapshot(),
&"inventory": inventory_service.create_snapshot(),
&"kitchen_production": kitchen_production.create_snapshot(),
&"auto_purchase_plans": auto_purchase_plans.create_snapshot(),
&"elevators": elevators.create_snapshot(),
&"legacy_stairs": legacy_stairs.create_snapshot(),
&"logistics_dispatcher": logistics_dispatcher.create_snapshot(),
@@ -610,6 +614,7 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
UPKEEP_RUNTIME_SNAPSHOT_VERSION,
WEATHER_RUNTIME_SNAPSHOT_VERSION,
CLEANING_RUNTIME_SNAPSHOT_VERSION,
EVENT_THREAT_RUNTIME_SNAPSHOT_VERSION,
RUNTIME_SNAPSHOT_VERSION,
]:
return false
@@ -649,6 +654,8 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
runtime_current_keys.append(&"weather")
if runtime_schema_version >= CLEANING_RUNTIME_SNAPSHOT_VERSION:
runtime_current_keys.append(&"facility_cleanliness")
if runtime_schema_version >= AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION:
runtime_current_keys.append(&"auto_purchase_plans")
if not _dictionary_has_exact_canonical_keys(runtime_data, runtime_current_keys) \
or typeof(runtime_data.get(&"runtime_schema_version", null)) != TYPE_INT \
or typeof(runtime_data.get(&"world_seed", null)) != TYPE_INT \
@@ -688,6 +695,10 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
return false
if runtime_schema_version >= STAIRS_RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"legacy_stairs", null) is Dictionary:
return false
if runtime_schema_version < AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION and runtime_data.has(&"auto_purchase_plans"):
return false
if runtime_schema_version >= AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"auto_purchase_plans", null) is Dictionary:
return false
var restored_catalog := MvpContentCatalog.new()
var restored_catalog_report := restored_catalog.rebuild()
if not bool(restored_catalog_report.get(&"valid", false)):
@@ -738,6 +749,13 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
var restored_queue := CommandQueue.new()
if not restored_queue.restore_snapshot(runtime_data[&"command_queue"]):
return false
if runtime_schema_version < AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION:
# Old saves must not reinterpret an unsupported queued action as a new
# paid feature when loading into this release. Failed historical actions
# remain ordinary recorded history; only resumable pending work is gated.
for row: Dictionary in restored_queue.create_snapshot()["pending"]:
if row["command_type"] == "set_supply_auto_purchase":
return false
var restored_id_generator := StableIdGenerator.new()
if not restored_id_generator.restore_snapshot(runtime_data[&"id_generator"]):
return false
@@ -1203,6 +1221,10 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
return false
if restored_kitchen.current_minute != restored_runtime_domain_minute or not restored_kitchen.validate_crosslinks():
return false
var restored_auto_purchase := AutoPurchasePlansType.new()
if runtime_schema_version >= AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION:
if not restored_auto_purchase.restore_snapshot(runtime_data[&"auto_purchase_plans"], restored_facilities, restored_economy, restored_runtime_domain_minute):
return false
if not SupplyLogisticsBridge.validate_crosslinks(restored_logistics, restored_staff, restored_inventory, restored_facilities):
return false
var restored_weather := WeatherTimeline.new()
@@ -1271,6 +1293,7 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
economy_service = restored_economy
inventory_service = restored_inventory
kitchen_production = restored_kitchen
auto_purchase_plans = restored_auto_purchase
logistics_dispatcher = restored_dispatcher
logistics_requests = restored_logistics
facility_service = restored_facilities
@@ -1966,6 +1989,7 @@ func _build_operations_external_state() -> Dictionary:
&"upkeep_settlement": upkeep_settlement.create_snapshot(),
&"inventory": inventory_service.create_snapshot(),
&"kitchen_production": kitchen_production.create_snapshot(),
&"auto_purchase_plans": auto_purchase_plans.create_snapshot(),
&"facilities": facility_service.create_snapshot(),
&"facility_cleanliness": facility_cleanliness.create_snapshot(),
&"guests": guest_service.create_snapshot(),
@@ -2023,7 +2047,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
var saved: Dictionary = token
var required_dictionary_keys: Array[StringName] = [
&"facility_upkeep", &"upkeep_settlement", &"facility_cleanliness",
&"game_state", &"command_queue", &"economy", &"inventory", &"kitchen_production", &"facilities", &"guests",
&"game_state", &"command_queue", &"economy", &"inventory", &"kitchen_production", &"auto_purchase_plans", &"facilities", &"guests",
&"staff", &"relationships", &"events", &"authority", &"world", &"path", &"elevators", &"legacy_stairs",
&"excavation", &"spatial_subslots", &"route_projection",
&"facility_infrastructure",
@@ -2066,6 +2090,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
var facilities_ok := facility_service.restore_snapshot(saved[&"facilities"])
facilities_ok = facility_cleanliness.restore_snapshot(saved.get(&"facility_cleanliness", {})) and facilities_ok
var kitchen_ok := kitchen_production.restore_snapshot(saved[&"kitchen_production"])
var auto_purchase_ok := auto_purchase_plans.restore_snapshot(saved[&"auto_purchase_plans"], facility_service, economy_service, int(saved[&"captured_minute"]))
var guests_ok := guest_service.restore_snapshot(saved[&"guests"])
var staff_ok := staff_service.restore_snapshot(saved[&"staff"])
var relationships_ok := relationship_service.restore_snapshot(saved[&"relationships"])
@@ -2116,7 +2141,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
rng_ok = false
_discard_operations_domain_events()
var participants_ok := upkeep_ok and state_ok and command_queue_ok and economy_ok and inventory_ok and kitchen_ok \
var participants_ok := upkeep_ok and state_ok and command_queue_ok and economy_ok and inventory_ok and kitchen_ok and auto_purchase_ok \
and facilities_ok and guests_ok and staff_ok and relationships_ok \
and events_ok and authority_ok and world_ok and spatial_ok and path_ok \
and route_ok and infrastructure_ok and excavation_ok \
@@ -2173,6 +2198,7 @@ func _operations_external_state_matches(saved: Dictionary) -> bool:
or not _strict_variants_equal(economy_service.create_snapshot(), saved[&"economy"]) \
or not _strict_variants_equal(inventory_service.create_snapshot(), saved[&"inventory"]) \
or not _strict_variants_equal(kitchen_production.create_snapshot(), saved[&"kitchen_production"]) \
or not _strict_variants_equal(auto_purchase_plans.create_snapshot(), saved[&"auto_purchase_plans"]) \
or not _strict_variants_equal(facility_service.create_snapshot(), saved[&"facilities"]) \
or not _strict_variants_equal(facility_cleanliness.create_snapshot(), saved.get(&"facility_cleanliness", {})) \
or not _strict_variants_equal(guest_service.create_snapshot(), saved[&"guests"]) \
@@ -5081,6 +5107,7 @@ func _initialize_s2_operations() -> void:
facility_service.current_minute = _clock.absolute_minute
_configure_facility_boundaries()
_connect_facility_signals()
auto_purchase_plans = AutoPurchasePlansType.new()
kitchen_production = KitchenProductionService.new()
kitchen_production.configure(inventory_service, facility_service, definition_registry, _clock.absolute_minute)
@@ -7541,6 +7568,7 @@ func _execute_demolish_facility(command: SimulationCommand) -> CommandResult:
result.command_id = command.command_id
if result.is_success():
kitchen_production.remove_plan(instance_id)
auto_purchase_plans.remove_warehouse(instance_id)
return result
@@ -8184,6 +8212,7 @@ func _advance_inventory_and_logistics(absolute_minute: int) -> void:
inventory_service.current_minute = absolute_minute
inventory_service.process_expired(absolute_minute, &"inventory.waste")
if int(_state.get_resource(&"mana")) > 0:
supply_management.advance_auto_purchases(self, absolute_minute)
supply_management.ensure_deliveries(self, absolute_minute)
supply_logistics.tick(absolute_minute)
@@ -12138,6 +12167,10 @@ func _execute_purchase_supplies(command: SimulationCommand) -> CommandResult:
return supply_management.purchase(self, command)
func _execute_set_supply_auto_purchase(command: SimulationCommand) -> CommandResult:
return supply_management.set_auto_purchase(self, command)
func _execute_set_kitchen_production(command: SimulationCommand) -> CommandResult:
return supply_management.set_production(self, command)
@@ -12965,7 +12998,7 @@ func _prepare_construction_dimension_snapshot(job_id: StringName) -> Dictionary:
## Read-only saved-game preflight used by the load menu before confirmation.
func preview_facility_dimensions_at_path(save_path: String) -> Dictionary:
var loaded := SaveService.load_snapshot(save_path)
var loaded := SaveService.inspect_snapshot(save_path)
if not loaded[&"ok"]: return loaded
var isolated: Variant = get_script().new()
var preview_clock: Variant = load("res://autoload/game_clock.gd").new()