Files
gamedev-the-steward/simulation/economy/VillageEconomy.gd
T
2026-08-12 10:02:45 +02:00

269 lines
7.5 KiB
GDScript

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,
action_id: StringName,
item_id: StringName,
required_amount: float
)
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 withdraw_exact_to_inventory(npc: SimNPC, item_id: StringName, amount: float) -> float:
if npc == null or not is_finite(amount) or amount <= 0.0:
return 0.0
var storage := get_storage_for_resource(item_id)
if storage == null or storage.get_amount(item_id) < amount:
return 0.0
return withdraw_to_inventory(npc, item_id, amount)
func remove_exact_from_inventory(npc: SimNPC, item_id: StringName, amount: float) -> float:
if (
npc == null
or not is_finite(amount)
or amount <= 0.0
or npc.get_inventory_amount(item_id) < amount
):
return 0.0
var removed := npc.remove_inventory(item_id, amount)
if not is_equal_approx(removed, amount):
npc.add_inventory(item_id, removed)
return 0.0
inventory_changed.emit(npc, item_id, npc.get_inventory_amount(item_id))
return removed
func restore_to_inventory(npc: SimNPC, item_id: StringName, amount: float) -> void:
if npc == null or not is_finite(amount) or amount <= 0.0:
return
npc.add_inventory(item_id, amount)
inventory_changed.emit(npc, item_id, npc.get_inventory_amount(item_id))
func consume_npc_food(npc: SimNPC) -> bool:
if npc == null:
return false
if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) < 1.0:
npc.hunger = minf(npc.hunger + 5.0, 100.0)
npc.is_starving = npc.hunger >= 90.0
return false
if remove_exact_from_inventory(npc, SimulationIds.RESOURCE_FOOD, 1.0) < 1.0:
return false
npc.hunger = maxf(npc.hunger - 55.0, 0.0)
npc.starvation_ticks = 0
npc.is_starving = npc.hunger >= 90.0
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,
definition.action_id,
resource_id,
required_amount
)
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()