feat: add weak-villager and damaged-roof village needs
This commit is contained in:
@@ -232,6 +232,8 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
|||||||
return
|
return
|
||||||
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
|
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
|
||||||
return
|
return
|
||||||
|
if _is_weak_villager_state(npc):
|
||||||
|
record_narrative_event(SimulationIds.EVENT_VILLAGER_WEAK, npc.id)
|
||||||
var helper := get_active_opportunity_helper()
|
var helper := get_active_opportunity_helper()
|
||||||
var animal_feed_available := animal_care.has_available_feed_target(npc.id)
|
var animal_feed_available := animal_care.has_available_feed_target(npc.id)
|
||||||
var selection := action_selector.select_action(
|
var selection := action_selector.select_action(
|
||||||
@@ -249,6 +251,16 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
|||||||
record_narrative_event(SimulationIds.EVENT_TASK_STARTED, npc.id, &"", display_name)
|
record_narrative_event(SimulationIds.EVENT_TASK_STARTED, npc.id, &"", display_name)
|
||||||
|
|
||||||
|
|
||||||
|
func _is_weak_villager_state(npc: SimNPC) -> bool:
|
||||||
|
return (
|
||||||
|
npc.is_starving
|
||||||
|
and npc.energy < 25.0
|
||||||
|
and village.food <= 0.0
|
||||||
|
and npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) < 1.0
|
||||||
|
and opportunity_system.get_open_opportunity() == null
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _handle_npc_death(npc: SimNPC, previous_task: StringName, previous_target: StringName) -> void:
|
func _handle_npc_death(npc: SimNPC, previous_task: StringName, previous_target: StringName) -> void:
|
||||||
if not previous_target.is_empty():
|
if not previous_target.is_empty():
|
||||||
release_npc_reservation(npc.id)
|
release_npc_reservation(npc.id)
|
||||||
@@ -301,6 +313,8 @@ func _apply_action_completion(
|
|||||||
npc.energy = minf(npc.energy + 40.0, 100.0)
|
npc.energy = minf(npc.energy + 40.0, 100.0)
|
||||||
npc.position = npc.home_position
|
npc.position = npc.home_position
|
||||||
record_narrative_event(SimulationIds.EVENT_NPC_SLEPT, npc.id)
|
record_narrative_event(SimulationIds.EVENT_NPC_SLEPT, npc.id)
|
||||||
|
if village.safety < 25.0 and opportunity_system.get_open_opportunity() == null:
|
||||||
|
record_narrative_event(SimulationIds.EVENT_HOME_DAMAGED, npc.id)
|
||||||
SimulationIds.ACTION_FEED_ANIMAL:
|
SimulationIds.ACTION_FEED_ANIMAL:
|
||||||
pass
|
pass
|
||||||
SimulationIds.ACTION_PATROL, SimulationIds.ACTION_STUDY, SimulationIds.ACTION_REST, SimulationIds.ACTION_WANDER:
|
SimulationIds.ACTION_PATROL, SimulationIds.ACTION_STUDY, SimulationIds.ACTION_REST, SimulationIds.ACTION_WANDER:
|
||||||
|
|||||||
@@ -53,9 +53,13 @@ const EVENT_TASK_STARTED := &"task_started"
|
|||||||
const EVENT_TASK_BLOCKED := &"task_blocked"
|
const EVENT_TASK_BLOCKED := &"task_blocked"
|
||||||
const EVENT_RESOURCE_DEPLETED := &"resource_depleted"
|
const EVENT_RESOURCE_DEPLETED := &"resource_depleted"
|
||||||
const EVENT_ANIMAL_FED := &"animal_fed"
|
const EVENT_ANIMAL_FED := &"animal_fed"
|
||||||
|
const EVENT_VILLAGER_WEAK := &"villager_weak"
|
||||||
|
const EVENT_HOME_DAMAGED := &"home_damaged"
|
||||||
|
|
||||||
const OPPORTUNITY_RESTOCK_EMPTY_PANTRY := &"restock_empty_pantry"
|
const OPPORTUNITY_RESTOCK_EMPTY_PANTRY := &"restock_empty_pantry"
|
||||||
const OPPORTUNITY_SUPPLY_MISSING_WOOD := &"supply_missing_wood"
|
const OPPORTUNITY_SUPPLY_MISSING_WOOD := &"supply_missing_wood"
|
||||||
|
const OPPORTUNITY_FEED_WEAK_VILLAGER := &"feed_weak_villager"
|
||||||
|
const OPPORTUNITY_REPAIR_HOME_ROOF := &"repair_home_roof"
|
||||||
const OPPORTUNITY_STATUS_OPEN := &"open"
|
const OPPORTUNITY_STATUS_OPEN := &"open"
|
||||||
const OPPORTUNITY_STATUS_RESOLVED := &"resolved"
|
const OPPORTUNITY_STATUS_RESOLVED := &"resolved"
|
||||||
const OPPORTUNITY_STATUS_INVALIDATED := &"invalidated"
|
const OPPORTUNITY_STATUS_INVALIDATED := &"invalidated"
|
||||||
|
|||||||
@@ -248,6 +248,14 @@ static func is_knowable_event(event: EconomicEventRecord) -> bool:
|
|||||||
)
|
)
|
||||||
and float(event.data.get("required_amount", 0.0)) > 0.0
|
and float(event.data.get("required_amount", 0.0)) > 0.0
|
||||||
)
|
)
|
||||||
|
if (
|
||||||
|
event_type
|
||||||
|
in [
|
||||||
|
SimulationIds.EVENT_VILLAGER_WEAK,
|
||||||
|
SimulationIds.EVENT_HOME_DAMAGED,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
return int(event.data["actor_id"]) >= 0
|
||||||
if float(event.data["amount"]) <= 0.0:
|
if float(event.data["amount"]) <= 0.0:
|
||||||
return false
|
return false
|
||||||
if event_type == SimulationIds.EVENT_STORAGE_DEPOSITED:
|
if event_type == SimulationIds.EVENT_STORAGE_DEPOSITED:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class_name VillageOpportunitySystem
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const CARE_HUNGER_THRESHOLD := 80.0
|
const CARE_HUNGER_THRESHOLD := 80.0
|
||||||
|
const WEAK_ENERGY_THRESHOLD := 25.0
|
||||||
|
|
||||||
var opportunities: Array[OpportunityStateRecord] = []
|
var opportunities: Array[OpportunityStateRecord] = []
|
||||||
var next_opportunity_id := 0
|
var next_opportunity_id := 0
|
||||||
@@ -29,7 +30,13 @@ func consider_event(
|
|||||||
return opened
|
return opened
|
||||||
if woodpile == null:
|
if woodpile == null:
|
||||||
return null
|
return null
|
||||||
return _try_open_wood(event, current_tick, woodpile, npcs, knowledge_system)
|
opened = _try_open_wood(event, current_tick, woodpile, npcs, knowledge_system)
|
||||||
|
if opened != null:
|
||||||
|
return opened
|
||||||
|
opened = _try_open_weak(event, current_tick, pantry, npcs, knowledge_system)
|
||||||
|
if opened != null:
|
||||||
|
return opened
|
||||||
|
return _try_open_roof(event, current_tick, woodpile, npcs, knowledge_system)
|
||||||
|
|
||||||
|
|
||||||
func maintain_open_opportunity(
|
func maintain_open_opportunity(
|
||||||
@@ -42,7 +49,14 @@ func maintain_open_opportunity(
|
|||||||
var opportunity := get_open_opportunity()
|
var opportunity := get_open_opportunity()
|
||||||
if opportunity == null or current_tick < opportunity.get_created_tick():
|
if opportunity == null or current_tick < opportunity.get_created_tick():
|
||||||
return null
|
return null
|
||||||
if opportunity.get_opportunity_type() != SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
if (
|
||||||
|
opportunity.get_opportunity_type()
|
||||||
|
not in [
|
||||||
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD,
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER,
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF,
|
||||||
|
]
|
||||||
|
):
|
||||||
return null
|
return null
|
||||||
var interested := _find_npc(opportunity.get_interested_npc_id(), npcs)
|
var interested := _find_npc(opportunity.get_interested_npc_id(), npcs)
|
||||||
if interested == null or interested.is_dead:
|
if interested == null or interested.is_dead:
|
||||||
@@ -53,14 +67,16 @@ func maintain_open_opportunity(
|
|||||||
)
|
)
|
||||||
else null
|
else null
|
||||||
)
|
)
|
||||||
|
var condition_is_stale := current_tick - opportunity.get_created_tick() >= maxi(max_age, 1)
|
||||||
|
if opportunity.get_opportunity_type() != SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
var target_storage := _get_target_storage(opportunity, pantry, woodpile)
|
var target_storage := _get_target_storage(opportunity, pantry, woodpile)
|
||||||
var condition_is_stale := (
|
condition_is_stale = (
|
||||||
target_storage == null
|
condition_is_stale
|
||||||
|
or target_storage == null
|
||||||
or (
|
or (
|
||||||
target_storage.get_amount(opportunity.get_resource_id())
|
target_storage.get_amount(opportunity.get_resource_id())
|
||||||
>= opportunity.get_target_amount()
|
>= opportunity.get_target_amount()
|
||||||
)
|
)
|
||||||
or current_tick - opportunity.get_created_tick() >= maxi(max_age, 1)
|
|
||||||
)
|
)
|
||||||
if not condition_is_stale:
|
if not condition_is_stale:
|
||||||
return null
|
return null
|
||||||
@@ -452,6 +468,95 @@ func _try_open_wood(
|
|||||||
return opportunity
|
return opportunity
|
||||||
|
|
||||||
|
|
||||||
|
func _try_open_weak(
|
||||||
|
event: EconomicEventRecord,
|
||||||
|
current_tick: int,
|
||||||
|
pantry: StorageStateRecord,
|
||||||
|
npcs: Array[SimNPC],
|
||||||
|
knowledge_system: EventKnowledgeSystem
|
||||||
|
) -> OpportunityStateRecord:
|
||||||
|
if knowledge_system == null or get_open_opportunity() != null:
|
||||||
|
return null
|
||||||
|
if not _is_weak_trigger(event):
|
||||||
|
return null
|
||||||
|
if pantry.get_amount(SimulationIds.RESOURCE_FOOD) > 0.0:
|
||||||
|
return null
|
||||||
|
var actor := _find_npc(int(event.data["actor_id"]), npcs)
|
||||||
|
if (
|
||||||
|
actor == null
|
||||||
|
or actor.is_dead
|
||||||
|
or actor.hunger < CARE_HUNGER_THRESHOLD
|
||||||
|
or actor.energy >= WEAK_ENERGY_THRESHOLD
|
||||||
|
or not knowledge_system.knows_event(actor.id, int(event.data["event_id"]))
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
var opportunity := OpportunityStateRecord.create(
|
||||||
|
next_opportunity_id,
|
||||||
|
current_tick,
|
||||||
|
int(event.data["event_id"]),
|
||||||
|
actor.id,
|
||||||
|
OpportunityStateRecord.DEFAULT_TARGET_AMOUNT,
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER,
|
||||||
|
SimulationIds.STORAGE_VILLAGE_PANTRY,
|
||||||
|
SimulationIds.RESOURCE_FOOD
|
||||||
|
)
|
||||||
|
next_opportunity_id += 1
|
||||||
|
opportunities.append(opportunity)
|
||||||
|
return opportunity
|
||||||
|
|
||||||
|
|
||||||
|
func _try_open_roof(
|
||||||
|
event: EconomicEventRecord,
|
||||||
|
current_tick: int,
|
||||||
|
woodpile: StorageStateRecord,
|
||||||
|
npcs: Array[SimNPC],
|
||||||
|
knowledge_system: EventKnowledgeSystem
|
||||||
|
) -> OpportunityStateRecord:
|
||||||
|
if knowledge_system == null or get_open_opportunity() != null:
|
||||||
|
return null
|
||||||
|
if not _is_home_damaged_trigger(event):
|
||||||
|
return null
|
||||||
|
if (
|
||||||
|
woodpile.get_amount(SimulationIds.RESOURCE_WOOD)
|
||||||
|
>= OpportunityStateRecord.DEFAULT_TARGET_AMOUNT
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
var actor := _find_npc(int(event.data["actor_id"]), npcs)
|
||||||
|
if (
|
||||||
|
actor == null
|
||||||
|
or actor.is_dead
|
||||||
|
or not knowledge_system.knows_event(actor.id, int(event.data["event_id"]))
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
var opportunity := OpportunityStateRecord.create(
|
||||||
|
next_opportunity_id,
|
||||||
|
current_tick,
|
||||||
|
int(event.data["event_id"]),
|
||||||
|
actor.id,
|
||||||
|
OpportunityStateRecord.DEFAULT_TARGET_AMOUNT,
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF,
|
||||||
|
SimulationIds.STORAGE_VILLAGE_WOODPILE,
|
||||||
|
SimulationIds.RESOURCE_WOOD
|
||||||
|
)
|
||||||
|
next_opportunity_id += 1
|
||||||
|
opportunities.append(opportunity)
|
||||||
|
return opportunity
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_weak_trigger(event: EconomicEventRecord) -> bool:
|
||||||
|
return (
|
||||||
|
StringName(event.data["event_type"]) == SimulationIds.EVENT_VILLAGER_WEAK
|
||||||
|
and int(event.data["actor_id"]) >= 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_home_damaged_trigger(event: EconomicEventRecord) -> bool:
|
||||||
|
return (
|
||||||
|
StringName(event.data["event_type"]) == SimulationIds.EVENT_HOME_DAMAGED
|
||||||
|
and int(event.data["actor_id"]) >= 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _try_resolve(
|
func _try_resolve(
|
||||||
event: EconomicEventRecord,
|
event: EconomicEventRecord,
|
||||||
pantry: StorageStateRecord,
|
pantry: StorageStateRecord,
|
||||||
@@ -462,6 +567,17 @@ func _try_resolve(
|
|||||||
var opportunity := get_open_opportunity()
|
var opportunity := get_open_opportunity()
|
||||||
if opportunity == null:
|
if opportunity == null:
|
||||||
return null
|
return null
|
||||||
|
if (
|
||||||
|
opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
|
||||||
|
and _is_valid_supply(event, opportunity, npcs, resource_states)
|
||||||
|
):
|
||||||
|
if int(event.data["tick"]) < opportunity.get_created_tick():
|
||||||
|
return null
|
||||||
|
if int(event.data["event_id"]) <= opportunity.get_trigger_event_id():
|
||||||
|
return null
|
||||||
|
if not opportunity.resolve(int(event.data["event_id"]), int(event.data["tick"])):
|
||||||
|
return null
|
||||||
|
return opportunity
|
||||||
var target_storage := _get_target_storage(opportunity, pantry, woodpile)
|
var target_storage := _get_target_storage(opportunity, pantry, woodpile)
|
||||||
if (
|
if (
|
||||||
target_storage == null
|
target_storage == null
|
||||||
@@ -528,6 +644,13 @@ static func _is_valid_supply(
|
|||||||
npcs: Array[SimNPC],
|
npcs: Array[SimNPC],
|
||||||
resource_states: Dictionary
|
resource_states: Dictionary
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
if opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
return (
|
||||||
|
StringName(event.data["event_type"]) == SimulationIds.EVENT_ITEM_CONSUMED
|
||||||
|
and int(event.data["actor_id"]) == opportunity.get_interested_npc_id()
|
||||||
|
and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
|
||||||
|
and float(event.data["amount"]) > 0.0
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
StringName(event.data["item_id"]) != opportunity.get_resource_id()
|
StringName(event.data["item_id"]) != opportunity.get_resource_id()
|
||||||
or StringName(event.data["destination_id"]) != opportunity.get_target_id()
|
or StringName(event.data["destination_id"]) != opportunity.get_target_id()
|
||||||
|
|||||||
@@ -95,13 +95,13 @@ static func from_dictionary(record_data: Dictionary) -> OpportunityStateRecord:
|
|||||||
if opportunity_id < 0 or created_tick < 0 or trigger_event_id < 0 or interested_npc_id < 0:
|
if opportunity_id < 0 or created_tick < 0 or trigger_event_id < 0 or interested_npc_id < 0:
|
||||||
return null
|
return null
|
||||||
match opportunity_type:
|
match opportunity_type:
|
||||||
SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY:
|
SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY, SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
if (
|
if (
|
||||||
target_id != SimulationIds.STORAGE_VILLAGE_PANTRY
|
target_id != SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||||
or resource_id != SimulationIds.RESOURCE_FOOD
|
or resource_id != SimulationIds.RESOURCE_FOOD
|
||||||
):
|
):
|
||||||
return null
|
return null
|
||||||
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD, SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
if (
|
if (
|
||||||
target_id != SimulationIds.STORAGE_VILLAGE_WOODPILE
|
target_id != SimulationIds.STORAGE_VILLAGE_WOODPILE
|
||||||
or resource_id != SimulationIds.RESOURCE_WOOD
|
or resource_id != SimulationIds.RESOURCE_WOOD
|
||||||
|
|||||||
@@ -573,6 +573,12 @@ static func _is_valid_opportunity(
|
|||||||
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
||||||
if not _is_valid_missing_wood_trigger(trigger_event, opportunity, npc_ids):
|
if not _is_valid_missing_wood_trigger(trigger_event, opportunity, npc_ids):
|
||||||
return false
|
return false
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
if not _is_valid_weak_trigger(trigger_event, opportunity, npc_ids):
|
||||||
|
return false
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
|
if not _is_valid_home_damaged_trigger(trigger_event, opportunity, npc_ids):
|
||||||
|
return false
|
||||||
_:
|
_:
|
||||||
return false
|
return false
|
||||||
var trigger_tick := int(trigger_event.data["tick"])
|
var trigger_tick := int(trigger_event.data["tick"])
|
||||||
@@ -594,25 +600,41 @@ static func _is_valid_opportunity(
|
|||||||
"%d:%d" % [opportunity.get_interested_npc_id(), opportunity.get_trigger_event_id()]
|
"%d:%d" % [opportunity.get_interested_npc_id(), opportunity.get_trigger_event_id()]
|
||||||
)
|
)
|
||||||
var evidence := knowledge_records_by_key.get(evidence_key) as KnownEventStateRecord
|
var evidence := knowledge_records_by_key.get(evidence_key) as KnownEventStateRecord
|
||||||
return (
|
var is_weak_need := (
|
||||||
resolution_event_id == OpportunityStateRecord.NO_EVENT_ID
|
opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
|
||||||
and resolved_tick == -1
|
|
||||||
and interested_record != null
|
|
||||||
and (
|
|
||||||
opportunity.get_opportunity_type() != SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD
|
|
||||||
or not bool(interested_record.data["is_dead"])
|
|
||||||
)
|
)
|
||||||
and target_storage != null
|
var interested_alive := (
|
||||||
|
interested_record != null and not bool(interested_record.data["is_dead"])
|
||||||
|
)
|
||||||
|
var storage_satisfied := (
|
||||||
|
is_weak_need
|
||||||
|
or (
|
||||||
|
target_storage != null
|
||||||
and (
|
and (
|
||||||
target_storage.get_amount(opportunity.get_resource_id())
|
target_storage.get_amount(opportunity.get_resource_id())
|
||||||
< opportunity.get_target_amount()
|
< opportunity.get_target_amount()
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
resolution_event_id == OpportunityStateRecord.NO_EVENT_ID
|
||||||
|
and resolved_tick == -1
|
||||||
|
and interested_alive
|
||||||
|
and target_storage != null
|
||||||
|
and storage_satisfied
|
||||||
and evidence != null
|
and evidence != null
|
||||||
and evidence.get_acquired_tick() <= created_tick
|
and evidence.get_acquired_tick() <= created_tick
|
||||||
)
|
)
|
||||||
if status == OpportunityStateRecord.STATUS_INVALIDATED:
|
if status == OpportunityStateRecord.STATUS_INVALIDATED:
|
||||||
if (
|
if (
|
||||||
opportunity.get_opportunity_type() != SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD
|
(
|
||||||
|
opportunity.get_opportunity_type()
|
||||||
|
not in [
|
||||||
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD,
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER,
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF,
|
||||||
|
]
|
||||||
|
)
|
||||||
or resolution_event_id != OpportunityStateRecord.NO_EVENT_ID
|
or resolution_event_id != OpportunityStateRecord.NO_EVENT_ID
|
||||||
or resolved_tick != -1
|
or resolved_tick != -1
|
||||||
or opportunity.get_closed_tick() < created_tick
|
or opportunity.get_closed_tick() < created_tick
|
||||||
@@ -681,6 +703,26 @@ static func _is_valid_pantry_empty_trigger(event: EconomicEventRecord, npc_ids:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_valid_weak_trigger(
|
||||||
|
event: EconomicEventRecord, opportunity: OpportunityStateRecord, npc_ids: Dictionary
|
||||||
|
) -> bool:
|
||||||
|
return (
|
||||||
|
npc_ids.has(int(event.data["actor_id"]))
|
||||||
|
and int(event.data["actor_id"]) == opportunity.get_interested_npc_id()
|
||||||
|
and StringName(event.data["event_type"]) == SimulationIds.EVENT_VILLAGER_WEAK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_valid_home_damaged_trigger(
|
||||||
|
event: EconomicEventRecord, opportunity: OpportunityStateRecord, npc_ids: Dictionary
|
||||||
|
) -> bool:
|
||||||
|
return (
|
||||||
|
npc_ids.has(int(event.data["actor_id"]))
|
||||||
|
and int(event.data["actor_id"]) == opportunity.get_interested_npc_id()
|
||||||
|
and StringName(event.data["event_type"]) == SimulationIds.EVENT_HOME_DAMAGED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
static func _is_valid_relationship_cause(
|
static func _is_valid_relationship_cause(
|
||||||
cause_event: EconomicEventRecord, subject_id: int, is_player_subject: bool
|
cause_event: EconomicEventRecord, subject_id: int, is_player_subject: bool
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -721,6 +763,15 @@ static func _is_valid_supply_resolution(
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
var actor_id := int(event.data["actor_id"])
|
var actor_id := int(event.data["actor_id"])
|
||||||
var event_type := StringName(event.data["event_type"])
|
var event_type := StringName(event.data["event_type"])
|
||||||
|
if (
|
||||||
|
opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
|
||||||
|
and event_type == SimulationIds.EVENT_ITEM_CONSUMED
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
actor_id == opportunity.get_interested_npc_id()
|
||||||
|
and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
|
||||||
|
and float(event.data["amount"]) > 0.0
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
StringName(event.data["destination_id"]) != opportunity.get_target_id()
|
StringName(event.data["destination_id"]) != opportunity.get_target_id()
|
||||||
or StringName(event.data["item_id"]) != opportunity.get_resource_id()
|
or StringName(event.data["item_id"]) != opportunity.get_resource_id()
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
_test_feed_weak_villager()
|
||||||
|
_test_repair_home_roof()
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _test_feed_weak_villager() -> void:
|
||||||
|
var manager := _create_manager(951)
|
||||||
|
var weak: SimNPC = manager.npcs[0]
|
||||||
|
var supplier: SimNPC = manager.npcs[1]
|
||||||
|
_empty_pantry(manager)
|
||||||
|
for npc in manager.npcs:
|
||||||
|
npc.position = Vector3(40.0 + npc.id * 10.0, 0.0, 0.0)
|
||||||
|
weak.position = Vector3.ZERO
|
||||||
|
supplier.position = Vector3(2.0, 0.0, 0.0)
|
||||||
|
weak.hunger = 95.0
|
||||||
|
weak.is_starving = true
|
||||||
|
weak.energy = 10.0
|
||||||
|
manager.economy.withdraw_to_inventory(weak, SimulationIds.RESOURCE_FOOD, 1.0)
|
||||||
|
weak.task_state = SimNPC.TASK_STATE_IDLE
|
||||||
|
weak.task_complete = true
|
||||||
|
manager.call("_select_action_if_idle", weak, SimNPC.TASK_STATE_IDLE)
|
||||||
|
var opportunity: OpportunityStateRecord = manager.get_active_opportunity()
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
opportunity != null
|
||||||
|
and opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
|
||||||
|
and opportunity.get_interested_npc_id() == weak.id
|
||||||
|
and opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
||||||
|
),
|
||||||
|
"A starving weak villager with an empty pantry should open a feed-weak-villager need"
|
||||||
|
)
|
||||||
|
if opportunity == null:
|
||||||
|
manager.free()
|
||||||
|
return
|
||||||
|
|
||||||
|
supplier.position = Vector3(2.0, 0.0, 0.0)
|
||||||
|
var supplier_relationship: RelationshipStateRecord = (
|
||||||
|
manager.relationship_system.get_relationship(supplier.id, weak.id)
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
supplier_relationship != null
|
||||||
|
and (
|
||||||
|
supplier_relationship.increase_trust(0.15, RelationshipStateRecord.NO_CAUSE_EVENT)
|
||||||
|
> 0.0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"Feed-weak setup should make the supplier a trusted known helper"
|
||||||
|
)
|
||||||
|
supplier.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
|
||||||
|
var helper: OpportunityHelperResult = manager.get_active_opportunity_helper()
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
helper != null
|
||||||
|
and helper.helper_npc_id == supplier.id
|
||||||
|
and helper.action_id == SimulationIds.ACTION_DEPOSIT_FOOD
|
||||||
|
),
|
||||||
|
"A trusted informed supplier should be derived as the capable helper"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
is_equal_approx(
|
||||||
|
manager.economy.deposit_inventory(supplier, SimulationIds.RESOURCE_FOOD), 1.0
|
||||||
|
),
|
||||||
|
"A real supplier should restock the pantry for the weak villager"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
manager.get_active_opportunity() != null,
|
||||||
|
"Stocking the pantry alone must not close a feed-weak-villager need"
|
||||||
|
)
|
||||||
|
weak.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
|
||||||
|
_check(manager.economy.consume_npc_food(weak), "The weak villager should eat the supplied food")
|
||||||
|
var resolved: OpportunityStateRecord = manager.get_latest_opportunity_for_npc(weak.id)
|
||||||
|
var resolution: EconomicEventRecord = manager.get_opportunity_resolution_event(resolved)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
resolved != null
|
||||||
|
and resolved.get_status() == OpportunityStateRecord.STATUS_RESOLVED
|
||||||
|
and resolution != null
|
||||||
|
and (StringName(resolution.data["event_type"]) == SimulationIds.EVENT_ITEM_CONSUMED)
|
||||||
|
and int(resolution.data["actor_id"]) == weak.id
|
||||||
|
),
|
||||||
|
"A weak-villager need should resolve only when the interested villager actually eats"
|
||||||
|
)
|
||||||
|
|
||||||
|
var saved_json: String = manager.serialize_state()
|
||||||
|
var restored := _create_manager(952)
|
||||||
|
_check(
|
||||||
|
restored.restore_state_from_json(saved_json),
|
||||||
|
"A feed-weak-villager need should survive save and restore"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
restored.get_state_checksum() == manager.get_state_checksum()
|
||||||
|
and (
|
||||||
|
restored.get_latest_opportunity_for_npc(weak.id).get_status()
|
||||||
|
== OpportunityStateRecord.STATUS_RESOLVED
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"Restore should preserve the resolved feed-weak history and checksum"
|
||||||
|
)
|
||||||
|
restored.free()
|
||||||
|
|
||||||
|
_empty_pantry(manager)
|
||||||
|
weak.hunger = 95.0
|
||||||
|
weak.is_starving = true
|
||||||
|
weak.energy = 5.0
|
||||||
|
weak.task_state = SimNPC.TASK_STATE_IDLE
|
||||||
|
weak.task_complete = true
|
||||||
|
manager.call("_select_action_if_idle", weak, SimNPC.TASK_STATE_IDLE)
|
||||||
|
var reopened: OpportunityStateRecord = manager.get_active_opportunity()
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
reopened != null
|
||||||
|
and reopened.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
|
||||||
|
),
|
||||||
|
"A renewed weak condition should open a new feed-weak-villager need"
|
||||||
|
)
|
||||||
|
var created_tick: int = reopened.get_created_tick()
|
||||||
|
var invalidated: OpportunityStateRecord = manager.opportunity_system.maintain_open_opportunity(
|
||||||
|
created_tick + manager.get_knowledge_review_interval(),
|
||||||
|
manager.get_knowledge_review_interval(),
|
||||||
|
manager.npcs,
|
||||||
|
manager.get_pantry(),
|
||||||
|
manager.get_woodpile()
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
invalidated != null
|
||||||
|
and invalidated.get_status() == OpportunityStateRecord.STATUS_INVALIDATED
|
||||||
|
and (
|
||||||
|
invalidated.get_invalidation_reason()
|
||||||
|
== SimulationIds.OPPORTUNITY_INVALIDATED_EVIDENCE_STALE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"An unresolved weak-villager need should close as stale after one simulated day"
|
||||||
|
)
|
||||||
|
manager.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _test_repair_home_roof() -> void:
|
||||||
|
var manager := _create_manager(953)
|
||||||
|
var homeowner: SimNPC = manager.npcs[0]
|
||||||
|
var supplier: SimNPC = manager.npcs[1]
|
||||||
|
var woodpile: StorageStateRecord = manager.get_woodpile()
|
||||||
|
woodpile.withdraw(SimulationIds.RESOURCE_WOOD, woodpile.get_amount(SimulationIds.RESOURCE_WOOD))
|
||||||
|
manager.economy.sync_resource(SimulationIds.RESOURCE_WOOD)
|
||||||
|
manager.village.safety = 15.0
|
||||||
|
homeowner.position = Vector3.ZERO
|
||||||
|
var definition := SimulationDefinitions.get_action(SimulationIds.ACTION_SLEEP)
|
||||||
|
manager.call("_apply_action_completion", homeowner, SimulationIds.ACTION_SLEEP, definition)
|
||||||
|
var opportunity: OpportunityStateRecord = manager.get_active_opportunity()
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
opportunity != null
|
||||||
|
and opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF
|
||||||
|
and opportunity.get_interested_npc_id() == homeowner.id
|
||||||
|
and opportunity.get_resource_id() == SimulationIds.RESOURCE_WOOD
|
||||||
|
),
|
||||||
|
"A sleepless night in a low-safety village should open a damaged-roof wood need"
|
||||||
|
)
|
||||||
|
if opportunity == null:
|
||||||
|
manager.free()
|
||||||
|
return
|
||||||
|
|
||||||
|
supplier.add_inventory(SimulationIds.RESOURCE_WOOD, 1.0)
|
||||||
|
_check(
|
||||||
|
is_equal_approx(
|
||||||
|
manager.economy.deposit_inventory(supplier, SimulationIds.RESOURCE_WOOD), 1.0
|
||||||
|
),
|
||||||
|
"A real supplier should deposit wood for the roof repair"
|
||||||
|
)
|
||||||
|
var resolved: OpportunityStateRecord = manager.get_latest_opportunity_for_npc(homeowner.id)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
resolved != null
|
||||||
|
and resolved.get_status() == OpportunityStateRecord.STATUS_RESOLVED
|
||||||
|
and manager.get_active_opportunity() == null
|
||||||
|
),
|
||||||
|
"A real wood deposit should resolve the damaged-roof need"
|
||||||
|
)
|
||||||
|
manager.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _empty_pantry(manager: Node) -> void:
|
||||||
|
var pantry: StorageStateRecord = manager.get_pantry()
|
||||||
|
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
|
||||||
|
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
||||||
|
|
||||||
|
|
||||||
|
func _create_manager(seed_value: int = 951) -> Node:
|
||||||
|
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||||
|
manager.simulation_seed = seed_value
|
||||||
|
manager.debug_logs = false
|
||||||
|
var home_positions: Array[Vector3] = [
|
||||||
|
Vector3(0.0, 0.0, 0.0),
|
||||||
|
Vector3(2.0, 0.0, 0.0),
|
||||||
|
Vector3(10.0, 0.0, 0.0),
|
||||||
|
Vector3(12.0, 0.0, 0.0),
|
||||||
|
Vector3(30.0, 0.0, 30.0),
|
||||||
|
Vector3(40.0, 0.0, 40.0),
|
||||||
|
]
|
||||||
|
manager.home_positions = home_positions
|
||||||
|
root.add_child(manager)
|
||||||
|
manager.set_process(false)
|
||||||
|
return manager
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Village care opportunities passed: weak villager + damaged roof")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cw18rdabwuedd
|
||||||
+65
-4
@@ -268,13 +268,39 @@ func _build_active_opportunity_display() -> String:
|
|||||||
var resource_name := String(opportunity.get_resource_id()).capitalize()
|
var resource_name := String(opportunity.get_resource_id()).capitalize()
|
||||||
var current_amount := _get_opportunity_current_amount(opportunity)
|
var current_amount := _get_opportunity_current_amount(opportunity)
|
||||||
var helper_text := _build_opportunity_helper_display()
|
var helper_text := _build_opportunity_helper_display()
|
||||||
if opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
match opportunity.get_opportunity_type():
|
||||||
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
||||||
var action_name := _get_opportunity_action_name(opportunity)
|
var action_name := _get_opportunity_action_name(opportunity)
|
||||||
return (
|
return (
|
||||||
"\n\nVillage need\n"
|
"\n\nVillage need\n"
|
||||||
+ "◆ Supply wood for blocked work\n"
|
+ "◆ Supply wood for blocked work\n"
|
||||||
+ "%s could not finish %s\n" % [interested_name, action_name]
|
+ "%s could not finish %s\n" % [interested_name, action_name]
|
||||||
+ "%s %.0f / %.0f" % [resource_name, current_amount, opportunity.get_target_amount()]
|
+ (
|
||||||
|
"%s %.0f / %.0f"
|
||||||
|
% [resource_name, current_amount, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
|
+ helper_text
|
||||||
|
)
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
return (
|
||||||
|
"\n\nVillage need\n"
|
||||||
|
+ "◆ Bring food to %s\n" % interested_name
|
||||||
|
+ "%s is too weak to gather\n" % interested_name
|
||||||
|
+ (
|
||||||
|
"%s %.0f / %.0f"
|
||||||
|
% [resource_name, current_amount, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
|
+ helper_text
|
||||||
|
)
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
|
return (
|
||||||
|
"\n\nVillage need\n"
|
||||||
|
+ "◆ Repair %s's roof\n" % interested_name
|
||||||
|
+ "%s is worried about their damaged home\n" % interested_name
|
||||||
|
+ (
|
||||||
|
"%s %.0f / %.0f"
|
||||||
|
% [resource_name, current_amount, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
+ helper_text
|
+ helper_text
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
@@ -328,7 +354,8 @@ func _build_npc_opportunity_display(npc: SimNPC) -> String:
|
|||||||
var resource_name := String(opportunity.get_resource_id()).capitalize()
|
var resource_name := String(opportunity.get_resource_id()).capitalize()
|
||||||
if opportunity.get_status() == OpportunityStateRecord.STATUS_OPEN:
|
if opportunity.get_status() == OpportunityStateRecord.STATUS_OPEN:
|
||||||
var current_amount := _get_opportunity_current_amount(opportunity)
|
var current_amount := _get_opportunity_current_amount(opportunity)
|
||||||
if opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
match opportunity.get_opportunity_type():
|
||||||
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
||||||
return (
|
return (
|
||||||
"Open village need\n"
|
"Open village need\n"
|
||||||
+ "◆ Find wood for %s\n" % _get_opportunity_action_name(opportunity)
|
+ "◆ Find wood for %s\n" % _get_opportunity_action_name(opportunity)
|
||||||
@@ -337,6 +364,24 @@ func _build_npc_opportunity_display(npc: SimNPC) -> String:
|
|||||||
% [resource_name, current_amount, opportunity.get_target_amount()]
|
% [resource_name, current_amount, opportunity.get_target_amount()]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
return (
|
||||||
|
"Open village need\n"
|
||||||
|
+ "◆ %s is too weak to gather\n" % npc.npc_name
|
||||||
|
+ (
|
||||||
|
"%s %.0f / %.0f\n\n"
|
||||||
|
% [resource_name, current_amount, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
|
return (
|
||||||
|
"Open village need\n"
|
||||||
|
+ "◆ %s's roof needs wood\n" % npc.npc_name
|
||||||
|
+ (
|
||||||
|
"%s %.0f / %.0f\n\n"
|
||||||
|
% [resource_name, current_amount, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
"Open village need\n"
|
"Open village need\n"
|
||||||
+ "◆ Restock the empty pantry\n"
|
+ "◆ Restock the empty pantry\n"
|
||||||
@@ -364,12 +409,28 @@ func _build_npc_opportunity_display(npc: SimNPC) -> String:
|
|||||||
if resolution_event != null:
|
if resolution_event != null:
|
||||||
var actor_id := int(resolution_event.data["actor_id"])
|
var actor_id := int(resolution_event.data["actor_id"])
|
||||||
actor_name = "Player" if actor_id < 0 else _get_npc_name(actor_id)
|
actor_name = "Player" if actor_id < 0 else _get_npc_name(actor_id)
|
||||||
if opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
match opportunity.get_opportunity_type():
|
||||||
|
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
||||||
return (
|
return (
|
||||||
"Resolved village need\n"
|
"Resolved village need\n"
|
||||||
+ "◆ %s supplied the woodpile\n" % actor_name
|
+ "◆ %s supplied the woodpile\n" % actor_name
|
||||||
+ "%s target %.0f\n\n" % [resource_name, opportunity.get_target_amount()]
|
+ "%s target %.0f\n\n" % [resource_name, opportunity.get_target_amount()]
|
||||||
)
|
)
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
return (
|
||||||
|
"Resolved village need\n"
|
||||||
|
+ (
|
||||||
|
"◆ %s ate the supplied food\n"
|
||||||
|
% _get_npc_name(opportunity.get_interested_npc_id())
|
||||||
|
)
|
||||||
|
+ "%s target %.0f\n\n" % [resource_name, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
|
return (
|
||||||
|
"Resolved village need\n"
|
||||||
|
+ "◆ %s repaired the roof\n" % actor_name
|
||||||
|
+ "%s target %.0f\n\n" % [resource_name, opportunity.get_target_amount()]
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
"Resolved village need\n"
|
"Resolved village need\n"
|
||||||
+ "◆ %s restocked the pantry\n" % actor_name
|
+ "◆ %s restocked the pantry\n" % actor_name
|
||||||
|
|||||||
@@ -201,6 +201,11 @@ func _on_state_restored() -> void:
|
|||||||
|
|
||||||
func _build_need_message(opportunity: OpportunityStateRecord) -> String:
|
func _build_need_message(opportunity: OpportunityStateRecord) -> String:
|
||||||
var interested_name := _get_npc_name(opportunity.get_interested_npc_id())
|
var interested_name := _get_npc_name(opportunity.get_interested_npc_id())
|
||||||
|
match opportunity.get_opportunity_type():
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
return "%s is too weak to gather food." % interested_name
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
|
return "%s's roof needs wood." % interested_name
|
||||||
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD:
|
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD:
|
||||||
return "%s worries over the empty pantry." % interested_name
|
return "%s worries over the empty pantry." % interested_name
|
||||||
var trigger: EconomicEventRecord = simulation_manager.get_opportunity_trigger_event(opportunity)
|
var trigger: EconomicEventRecord = simulation_manager.get_opportunity_trigger_event(opportunity)
|
||||||
@@ -227,6 +232,11 @@ func _get_destination_name(target_id: StringName) -> String:
|
|||||||
|
|
||||||
|
|
||||||
func _get_need_name(opportunity: OpportunityStateRecord) -> String:
|
func _get_need_name(opportunity: OpportunityStateRecord) -> String:
|
||||||
|
match opportunity.get_opportunity_type():
|
||||||
|
SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||||
|
return "weak-villager plea"
|
||||||
|
SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||||
|
return "damaged-roof report"
|
||||||
return (
|
return (
|
||||||
"empty-pantry worry"
|
"empty-pantry worry"
|
||||||
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
||||||
|
|||||||
Reference in New Issue
Block a user