extends RefCounted signal inventory_changed(npc: SimNPC, item_id: StringName, amount: float) signal economic_event_requested( event_type: StringName, actor_id: int, source_id: StringName, destination_id: StringName, item_id: StringName, amount: float ) signal narrative_event_requested( event_type: StringName, actor_id: int, source_id: StringName, action_display: String ) var village: SimVillage var storage_states: Dictionary = {} var debug_logs := false func configure(village_state: SimVillage, should_debug: bool) -> void: village = village_state debug_logs = should_debug func initialize_storage() -> void: if village == null: return if not storage_states.has(SimulationIds.STORAGE_VILLAGE_PANTRY): storage_states[SimulationIds.STORAGE_VILLAGE_PANTRY] = StorageStateRecord.create( SimulationIds.STORAGE_VILLAGE_PANTRY, {String(SimulationIds.RESOURCE_FOOD): village.food} ) if not storage_states.has(SimulationIds.STORAGE_VILLAGE_WOODPILE): storage_states[SimulationIds.STORAGE_VILLAGE_WOODPILE] = StorageStateRecord.create( SimulationIds.STORAGE_VILLAGE_WOODPILE, {String(SimulationIds.RESOURCE_WOOD): village.wood} ) sync_all() func restore_storage(records: Array[StorageStateRecord]) -> void: storage_states.clear() for storage_record in records: storage_states[storage_record.get_storage_id()] = storage_record # Older saves may not contain every authored storage. Preserve their village # aggregates by creating only the missing records before synchronizing. initialize_storage() func get_storage(storage_id: StringName) -> StorageStateRecord: return storage_states.get(storage_id) as StorageStateRecord func get_pantry() -> StorageStateRecord: return get_storage(SimulationIds.STORAGE_VILLAGE_PANTRY) func get_woodpile() -> StorageStateRecord: return get_storage(SimulationIds.STORAGE_VILLAGE_WOODPILE) func get_storage_for_resource(resource_id: StringName) -> StorageStateRecord: match resource_id: SimulationIds.RESOURCE_FOOD: return get_pantry() SimulationIds.RESOURCE_WOOD: return get_woodpile() return null func sync_all() -> void: if village == null: return var pantry := get_pantry() var woodpile := get_woodpile() if pantry != null: village.food = pantry.get_amount(SimulationIds.RESOURCE_FOOD) if woodpile != null: village.wood = woodpile.get_amount(SimulationIds.RESOURCE_WOOD) _refresh_village_scores() func sync_resource(resource_id: StringName) -> void: if village == null: return var storage := get_storage_for_resource(resource_id) if storage == null: return match resource_id: SimulationIds.RESOURCE_FOOD: village.food = storage.get_amount(resource_id) SimulationIds.RESOURCE_WOOD: village.wood = storage.get_amount(resource_id) _refresh_village_scores() func deposit_inventory(npc: SimNPC, item_id: StringName) -> float: var storage := get_storage_for_resource(item_id) if storage == null: return 0.0 var deposited := storage.deposit(item_id, npc.get_inventory_amount(item_id)) npc.remove_inventory(item_id, deposited) sync_resource(item_id) if deposited > 0.0: inventory_changed.emit(npc, item_id, npc.get_inventory_amount(item_id)) economic_event_requested.emit( SimulationIds.EVENT_STORAGE_DEPOSITED, npc.id, SimulationIds.npc_inventory_id(npc.id), storage.get_storage_id(), item_id, deposited ) return deposited func withdraw_to_inventory(npc: SimNPC, item_id: StringName, amount: float) -> float: var storage := get_storage_for_resource(item_id) if storage == null: return 0.0 var withdrawn := storage.withdraw(item_id, amount) npc.add_inventory(item_id, withdrawn) sync_resource(item_id) if withdrawn > 0.0: inventory_changed.emit(npc, item_id, npc.get_inventory_amount(item_id)) economic_event_requested.emit( SimulationIds.EVENT_STORAGE_WITHDRAWN, npc.id, storage.get_storage_id(), SimulationIds.npc_inventory_id(npc.id), item_id, withdrawn ) return withdrawn func consume_npc_food(npc: SimNPC) -> bool: if npc.remove_inventory(SimulationIds.RESOURCE_FOOD, 1.0) < 1.0: npc.hunger = minf(npc.hunger + 5.0, 100.0) npc.is_starving = npc.hunger >= 90.0 return false npc.hunger = maxf(npc.hunger - 55.0, 0.0) npc.starvation_ticks = 0 npc.is_starving = npc.hunger >= 90.0 inventory_changed.emit( npc, SimulationIds.RESOURCE_FOOD, npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) ) economic_event_requested.emit( SimulationIds.EVENT_ITEM_CONSUMED, npc.id, SimulationIds.npc_inventory_id(npc.id), &"consumed", SimulationIds.RESOURCE_FOOD, 1.0 ) return true func consume_completion_cost(npc: SimNPC, definition: ActionDefinition) -> bool: if definition == null or not definition.has_completion_cost(): return true var resource_id := definition.completion_cost_resource_id var required_amount := definition.completion_cost_amount var storage := get_storage_for_resource(resource_id) var available := storage.get_amount(resource_id) if storage != null else 0.0 if storage == null or available < required_amount: var reason := "%s: needs %.0f %s (%.1f available)" % [ definition.display_name, required_amount, String(resource_id).capitalize(), available, ] narrative_event_requested.emit( SimulationIds.EVENT_TASK_BLOCKED, npc.id, storage.get_storage_id() if storage != null else &"", reason ) return false var consumed := storage.withdraw(resource_id, required_amount) sync_resource(resource_id) if consumed < required_amount: return false economic_event_requested.emit( SimulationIds.EVENT_ITEM_CONSUMED, npc.id, storage.get_storage_id(), &"consumed", resource_id, consumed ) if debug_logs: print( "[VillageEconomy] %s consumed %.1f %s for %s" % [npc.npc_name, consumed, resource_id, definition.action_id] ) return true func deposit_resource(resource_id: StringName, amount: float) -> float: var storage := get_storage_for_resource(resource_id) if storage == null: return 0.0 var deposited := storage.deposit(resource_id, amount) sync_resource(resource_id) return deposited func withdraw_resource(resource_id: StringName, amount: float) -> float: var storage := get_storage_for_resource(resource_id) if storage == null: return 0.0 var withdrawn := storage.withdraw(resource_id, amount) sync_resource(resource_id) return withdrawn func _refresh_village_scores() -> void: village.update_modifiers() village.update_priorities()