Connect paid food purchasing, kitchen production, and porter deliveries

This commit is contained in:
Takeshi
2026-09-09 09:46:33 +09:00
parent 29a660aed0
commit 627b200f00
42 changed files with 3787 additions and 84 deletions
+84 -19
View File
@@ -15,7 +15,8 @@ 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 := 6
const RUNTIME_SNAPSHOT_VERSION := 7
const INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION := 6
const LEGACY_RUNTIME_SNAPSHOT_VERSION := 1
const DEFENSE_RUNTIME_SNAPSHOT_VERSION := 2
const PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION := 3
@@ -86,6 +87,9 @@ var operating_resource_state: LedgerBackedResourceState
var inventory_service := InventoryService.new()
var logistics_dispatcher := LogisticsDispatcher.new()
var logistics_requests: Dictionary = {}
var kitchen_production := KitchenProductionService.new()
var supply_management := SupplyManagementService.new()
var supply_logistics := SupplyLogisticsBridge.new()
var facility_service := FacilityService.new()
var guest_service := GuestService.new()
var staff_service := StaffService.new()
@@ -227,6 +231,8 @@ func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_S
latest_save_operation_result.clear()
latest_restore_safety_report.clear()
_initialize_rng_streams()
supply_logistics = SupplyLogisticsBridge.new()
supply_logistics.configure(self)
_executors.clear()
register_command_executor(&"change_resources", Callable(self, "_execute_change_resources"))
register_command_executor(&"spend_resources", Callable(self, "_execute_spend_resources"))
@@ -240,6 +246,8 @@ func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_S
register_command_executor(&"update_progression_metrics", Callable(self, "_execute_update_progression_metrics"))
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_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"))
register_command_executor(&"dispatch_logistics", Callable(self, "_execute_dispatch_logistics"))
@@ -452,6 +460,7 @@ func create_snapshot() -> Dictionary:
&"mana": mana_network_service.create_snapshot(),
&"economy": economy_service.create_snapshot(),
&"inventory": inventory_service.create_snapshot(),
&"kitchen_production": kitchen_production.create_snapshot(),
&"logistics_dispatcher": logistics_dispatcher.create_snapshot(),
&"logistics_requests": _serialize_logistics_requests(),
&"facilities": facility_service.create_snapshot(),
@@ -515,13 +524,14 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION,
CONTENT_RUNTIME_SNAPSHOT_VERSION,
OPERATIONS_RUNTIME_SNAPSHOT_VERSION,
INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION,
RUNTIME_SNAPSHOT_VERSION,
]:
return false
if runtime_schema_version < OPERATIONS_RUNTIME_SNAPSHOT_VERSION \
and not _legacy_operations_history_is_absent(runtime_data):
return false
if runtime_schema_version < RUNTIME_SNAPSHOT_VERSION \
if runtime_schema_version < INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION \
and not _legacy_infrastructure_history_is_absent(runtime_data):
return false
if runtime_schema_version >= OPERATIONS_RUNTIME_SNAPSHOT_VERSION:
@@ -537,11 +547,13 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
&"operations", &"relationships", &"event_definitions", &"events",
&"authority", &"defense", &"construction",
]
if runtime_schema_version == RUNTIME_SNAPSHOT_VERSION:
if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION:
runtime_current_keys.append_array([
&"excavation", &"spatial_subslots", &"route_projection",
&"facility_infrastructure",
])
if runtime_schema_version >= RUNTIME_SNAPSHOT_VERSION:
runtime_current_keys.append(&"kitchen_production")
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 \
@@ -562,13 +574,17 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
if runtime_schema_version >= OPERATIONS_RUNTIME_SNAPSHOT_VERSION \
and not runtime_data.get(&"operations", null) is Dictionary:
return false
if runtime_schema_version >= RUNTIME_SNAPSHOT_VERSION:
if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION:
for infrastructure_key: StringName in [
&"excavation", &"spatial_subslots", &"route_projection",
&"facility_infrastructure",
]:
if not runtime_data.get(infrastructure_key, null) is Dictionary:
return false
if runtime_schema_version < RUNTIME_SNAPSHOT_VERSION and runtime_data.has(&"kitchen_production"):
return false
if runtime_schema_version >= RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"kitchen_production", 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)):
@@ -642,7 +658,7 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
var restored_spatial := SpatialSubslotService.new()
var restored_route_adapter := RouteSubslotPathAdapter.new()
var restored_infrastructure_projector := FacilityInfrastructureProjector.new()
if runtime_schema_version >= RUNTIME_SNAPSHOT_VERSION:
if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION:
# Configure against an empty Spatial source before any managed namespace is
# present. Then restore the source, rebuild the base+ROUTE topology, and
# finally restore Path traffic/actors and the two metadata owners.
@@ -959,7 +975,7 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
var restored_excavation := ExcavationService.new(
restored_world, restored_construction_resource
)
if runtime_schema_version >= RUNTIME_SNAPSHOT_VERSION:
if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION:
if not restored_excavation.restore_snapshot(runtime_data[&"excavation"]):
return false
else:
@@ -1002,6 +1018,15 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
restored_infrastructure_projector
):
return false
var restored_kitchen := KitchenProductionService.new()
restored_kitchen.configure(restored_inventory, restored_facilities, definition_registry, restored_runtime_domain_minute)
if runtime_schema_version >= RUNTIME_SNAPSHOT_VERSION:
if not restored_kitchen.restore_snapshot(runtime_data[&"kitchen_production"]):
return false
if restored_kitchen.current_minute != restored_runtime_domain_minute or not restored_kitchen.validate_crosslinks():
return false
if not SupplyLogisticsBridge.validate_crosslinks(restored_logistics, restored_staff, restored_inventory, restored_facilities):
return false
var restored_world_seed := int(runtime_data.get(&"world_seed", DEFAULT_WORLD_SEED))
var restored_streams := {}
var saved_streams: Dictionary = runtime_data[&"rng_streams"]
@@ -1044,11 +1069,13 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
mana_network_service = restored_mana
economy_service = restored_economy
inventory_service = restored_inventory
kitchen_production = restored_kitchen
logistics_dispatcher = restored_dispatcher
logistics_requests = restored_logistics
facility_service = restored_facilities
guest_service = restored_guests
staff_service = restored_staff
supply_logistics.configure(self)
mvp_content_catalog = restored_catalog
mvp_content_report = restored_catalog_report.duplicate(true)
progression_service = restored_progression
@@ -1652,6 +1679,7 @@ func _build_operations_external_state() -> Dictionary:
&"next_command_id": _next_command_id,
&"economy": economy_service.create_snapshot(),
&"inventory": inventory_service.create_snapshot(),
&"kitchen_production": kitchen_production.create_snapshot(),
&"facilities": facility_service.create_snapshot(),
&"guests": guest_service.create_snapshot(),
&"staff": staff_service.create_snapshot(),
@@ -1687,12 +1715,13 @@ func _operations_external_committed_minute() -> int:
func _runtime_aggregate_committed_minute() -> int:
if facility_service == null or guest_service == null or inventory_service == null \
or staff_service == null:
or staff_service == null or kitchen_production == null:
return -1
var minute := facility_service.current_minute
if minute < 0 or guest_service.current_minute != minute \
or inventory_service.current_minute != minute \
or staff_service.current_minute != minute:
or staff_service.current_minute != minute \
or kitchen_production.current_minute != minute:
return -1
return minute
@@ -1703,7 +1732,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
return false
var saved: Dictionary = token
var required_dictionary_keys: Array[StringName] = [
&"game_state", &"command_queue", &"economy", &"inventory", &"facilities", &"guests",
&"game_state", &"command_queue", &"economy", &"inventory", &"kitchen_production", &"facilities", &"guests",
&"staff", &"relationships", &"events", &"authority", &"world", &"path",
&"excavation", &"spatial_subslots", &"route_projection",
&"facility_infrastructure",
@@ -1743,6 +1772,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
var economy_ok := economy_service.restore_snapshot(saved[&"economy"])
var inventory_ok := inventory_service.restore_snapshot(saved[&"inventory"])
var facilities_ok := facility_service.restore_snapshot(saved[&"facilities"])
var kitchen_ok := kitchen_production.restore_snapshot(saved[&"kitchen_production"])
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"])
@@ -1791,7 +1821,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
rng_ok = false
_discard_operations_domain_events()
var participants_ok := state_ok and command_queue_ok and economy_ok and inventory_ok \
var participants_ok := state_ok and command_queue_ok and economy_ok and inventory_ok and kitchen_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 \
@@ -1845,6 +1875,7 @@ func _operations_external_state_matches(saved: Dictionary) -> bool:
or _next_command_id != int(saved[&"next_command_id"]) \
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(facility_service.create_snapshot(), saved[&"facilities"]) \
or not _strict_variants_equal(guest_service.create_snapshot(), saved[&"guests"]) \
or not _strict_variants_equal(staff_service.create_snapshot(), saved[&"staff"]) \
@@ -4679,6 +4710,8 @@ func _initialize_s2_operations() -> void:
facility_service.current_minute = _clock.absolute_minute
_configure_facility_boundaries()
_connect_facility_signals()
kitchen_production = KitchenProductionService.new()
kitchen_production.configure(inventory_service, facility_service, definition_registry, _clock.absolute_minute)
func _initialize_s3_actors() -> void:
guest_service = GuestService.new()
@@ -5354,7 +5387,11 @@ func _execute_dispatch_logistics(command: SimulationCommand) -> CommandResult:
var carriers: Variant = command.payload.get(&"carriers", command.payload.get("carriers", null))
if not carriers is Dictionary:
return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "carriers must be a Dictionary.")
var assignments := logistics_dispatcher.dispatch(_sorted_logistics_requests(), carriers, _runtime_event_minute())
var manual_requests: Array[LogisticsRequest] = []
for request: LogisticsRequest in _sorted_logistics_requests():
if not String(request.request_id).begins_with(SupplyLogisticsBridge.REQUEST_PREFIX):
manual_requests.append(request)
var assignments := logistics_dispatcher.dispatch(manual_requests, carriers, _runtime_event_minute())
return CommandResult.succeeded(command.command_id, {
&"assignments": assignments,
&"report": logistics_dispatcher.last_report.duplicate(true),
@@ -5362,6 +5399,8 @@ func _execute_dispatch_logistics(command: SimulationCommand) -> CommandResult:
func _execute_advance_logistics_request(command: SimulationCommand) -> CommandResult:
var request_id := StringName(command.payload.get(&"request_id", command.payload.get("request_id", "")))
if command.command_id > 0 and String(request_id).begins_with(SupplyLogisticsBridge.REQUEST_PREFIX):
return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Supply deliveries advance only after the porter arrives.", {&"reason": &"SUPPLY_DRIVER_REQUIRED"})
if not logistics_requests.has(request_id):
return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Logistics request was not found.")
var request: LogisticsRequest = logistics_requests[request_id]
@@ -6844,6 +6883,9 @@ func _execute_demolish_facility(command: SimulationCommand) -> CommandResult:
"A Facility assigned to a non-terminal Defense Raid cannot be demolished.",
{&"raid_id": defense_owner}
)
var supply_guard: Dictionary = supply_management.validate_demolition(self, instance_id)
if not bool(supply_guard.get(&"ok", false)):
return _staff_assignment_failure(command.command_id, StringName(supply_guard.get(&"reason", &"SUPPLY_IN_USE")), "Facility still owns supply stock, a production plan, or an active delivery.")
for assignment: Dictionary in staff_service.get_work_assignments():
if StringName(assignment[&"facility_id"]) == instance_id:
return _staff_assignment_failure(command.command_id, &"STAFF_ASSIGNED", "Unassign scheduled Staff before demolishing this facility.")
@@ -6905,6 +6947,8 @@ func _execute_demolish_facility(command: SimulationCommand) -> CommandResult:
return demolished
var result := construction_service.demolish_facility(instance_id)
result.command_id = command.command_id
if result.is_success():
kitchen_production.remove_plan(instance_id)
return result
@@ -7527,11 +7571,14 @@ func _advance_inventory_and_logistics(absolute_minute: int) -> void:
return
inventory_service.current_minute = absolute_minute
inventory_service.process_expired(absolute_minute, &"inventory.waste")
supply_management.ensure_deliveries(self, absolute_minute)
supply_logistics.tick(absolute_minute)
func _advance_facilities(absolute_minute: int) -> void:
if _operations_tick_blocked:
return
facility_service.advance_one_minute(absolute_minute)
kitchen_production.advance_one_minute(absolute_minute)
func _advance_guest_vitals(absolute_minute: int) -> void:
if _operations_tick_blocked:
@@ -7763,7 +7810,8 @@ func _advance_runtime_path_traffic(absolute_minute: int) -> void:
continue
var task := staff_service.get_task(staff.current_task_id)
if task != null and task.state == StaffTask.State.MOVING \
and task.assigned_staff_id == staff.staff_id:
and task.assigned_staff_id == staff.staff_id \
and supply_logistics.can_move_task(task.task_id):
_advance_staff_task_travel(task, actor_id, absolute_minute)
else:
_record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute)
@@ -8056,13 +8104,14 @@ func _recover_stuck_actor(
elif kind == &"staff":
var staff := staff_service.get_staff(entity_id)
if staff != null and not staff.current_task_id.is_empty():
var cancelled := staff_service.cancel_task(
staff.current_task_id,
&"PATH_STUCK",
absolute_minute
)
action_ok = cancelled.is_success()
action = &"STAFF_TASK_CANCELLED" if action_ok else &"STAFF_TASK_CANCEL_FAILED"
if supply_logistics.owns_task(staff.current_task_id):
var recovery: Dictionary = supply_logistics.handle_path_stuck(staff.current_task_id, absolute_minute)
action_ok = bool(recovery.get(&"ok", false))
action = StringName(recovery.get(&"action", &"SUPPLY_RECOVERY_FAILED"))
else:
var cancelled := staff_service.cancel_task(staff.current_task_id, &"PATH_STUCK", absolute_minute)
action_ok = cancelled.is_success()
action = &"STAFF_TASK_CANCELLED" if action_ok else &"STAFF_TASK_CANCEL_FAILED"
if not action_ok:
path_service.restore_snapshot(path_before)
guest_service.restore_snapshot(guests_before)
@@ -11112,3 +11161,19 @@ func _publish_snapshot(_absolute_minute: int) -> void:
latest_snapshot = create_snapshot()
latest_digest = SimulationDigest.compute(latest_snapshot)
snapshot_published.emit(latest_snapshot, latest_digest)
func _execute_purchase_supplies(command: SimulationCommand) -> CommandResult:
return supply_management.purchase(self, command)
func _execute_set_kitchen_production(command: SimulationCommand) -> CommandResult:
return supply_management.set_production(self, command)
func get_supply_management_report() -> Dictionary:
return supply_management.get_report(self)
func preview_supply_purchase(warehouse_id: StringName, item_id: StringName, quantity: int) -> Dictionary:
return supply_management.preview_purchase(self, warehouse_id, item_id, quantity)