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
|
||||
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
|
||||
return
|
||||
if _is_weak_villager_state(npc):
|
||||
record_narrative_event(SimulationIds.EVENT_VILLAGER_WEAK, npc.id)
|
||||
var helper := get_active_opportunity_helper()
|
||||
var animal_feed_available := animal_care.has_available_feed_target(npc.id)
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
if not previous_target.is_empty():
|
||||
release_npc_reservation(npc.id)
|
||||
@@ -301,6 +313,8 @@ func _apply_action_completion(
|
||||
npc.energy = minf(npc.energy + 40.0, 100.0)
|
||||
npc.position = npc.home_position
|
||||
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:
|
||||
pass
|
||||
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_RESOURCE_DEPLETED := &"resource_depleted"
|
||||
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_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_RESOLVED := &"resolved"
|
||||
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
|
||||
)
|
||||
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:
|
||||
return false
|
||||
if event_type == SimulationIds.EVENT_STORAGE_DEPOSITED:
|
||||
|
||||
@@ -2,6 +2,7 @@ class_name VillageOpportunitySystem
|
||||
extends RefCounted
|
||||
|
||||
const CARE_HUNGER_THRESHOLD := 80.0
|
||||
const WEAK_ENERGY_THRESHOLD := 25.0
|
||||
|
||||
var opportunities: Array[OpportunityStateRecord] = []
|
||||
var next_opportunity_id := 0
|
||||
@@ -29,7 +30,13 @@ func consider_event(
|
||||
return opened
|
||||
if woodpile == 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(
|
||||
@@ -42,7 +49,14 @@ func maintain_open_opportunity(
|
||||
var opportunity := get_open_opportunity()
|
||||
if opportunity == null or current_tick < opportunity.get_created_tick():
|
||||
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
|
||||
var interested := _find_npc(opportunity.get_interested_npc_id(), npcs)
|
||||
if interested == null or interested.is_dead:
|
||||
@@ -53,15 +67,17 @@ func maintain_open_opportunity(
|
||||
)
|
||||
else null
|
||||
)
|
||||
var target_storage := _get_target_storage(opportunity, pantry, woodpile)
|
||||
var condition_is_stale := (
|
||||
target_storage == null
|
||||
or (
|
||||
target_storage.get_amount(opportunity.get_resource_id())
|
||||
>= opportunity.get_target_amount()
|
||||
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)
|
||||
condition_is_stale = (
|
||||
condition_is_stale
|
||||
or target_storage == null
|
||||
or (
|
||||
target_storage.get_amount(opportunity.get_resource_id())
|
||||
>= opportunity.get_target_amount()
|
||||
)
|
||||
)
|
||||
or current_tick - opportunity.get_created_tick() >= maxi(max_age, 1)
|
||||
)
|
||||
if not condition_is_stale:
|
||||
return null
|
||||
return (
|
||||
@@ -452,6 +468,95 @@ func _try_open_wood(
|
||||
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(
|
||||
event: EconomicEventRecord,
|
||||
pantry: StorageStateRecord,
|
||||
@@ -462,6 +567,17 @@ func _try_resolve(
|
||||
var opportunity := get_open_opportunity()
|
||||
if opportunity == 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)
|
||||
if (
|
||||
target_storage == null
|
||||
@@ -528,6 +644,13 @@ static func _is_valid_supply(
|
||||
npcs: Array[SimNPC],
|
||||
resource_states: Dictionary
|
||||
) -> 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 (
|
||||
StringName(event.data["item_id"]) != opportunity.get_resource_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:
|
||||
return null
|
||||
match opportunity_type:
|
||||
SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY:
|
||||
SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY, SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER:
|
||||
if (
|
||||
target_id != SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
or resource_id != SimulationIds.RESOURCE_FOOD
|
||||
):
|
||||
return null
|
||||
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
||||
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD, SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF:
|
||||
if (
|
||||
target_id != SimulationIds.STORAGE_VILLAGE_WOODPILE
|
||||
or resource_id != SimulationIds.RESOURCE_WOOD
|
||||
|
||||
@@ -573,6 +573,12 @@ static func _is_valid_opportunity(
|
||||
SimulationIds.OPPORTUNITY_SUPPLY_MISSING_WOOD:
|
||||
if not _is_valid_missing_wood_trigger(trigger_event, opportunity, npc_ids):
|
||||
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
|
||||
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()]
|
||||
)
|
||||
var evidence := knowledge_records_by_key.get(evidence_key) as KnownEventStateRecord
|
||||
var is_weak_need := (
|
||||
opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
|
||||
)
|
||||
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 (
|
||||
target_storage.get_amount(opportunity.get_resource_id())
|
||||
< opportunity.get_target_amount()
|
||||
)
|
||||
)
|
||||
)
|
||||
return (
|
||||
resolution_event_id == OpportunityStateRecord.NO_EVENT_ID
|
||||
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 interested_alive
|
||||
and target_storage != null
|
||||
and (
|
||||
target_storage.get_amount(opportunity.get_resource_id())
|
||||
< opportunity.get_target_amount()
|
||||
)
|
||||
and storage_satisfied
|
||||
and evidence != null
|
||||
and evidence.get_acquired_tick() <= created_tick
|
||||
)
|
||||
if status == OpportunityStateRecord.STATUS_INVALIDATED:
|
||||
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 resolved_tick != -1
|
||||
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(
|
||||
cause_event: EconomicEventRecord, subject_id: int, is_player_subject: bool
|
||||
) -> bool:
|
||||
@@ -721,6 +763,15 @@ static func _is_valid_supply_resolution(
|
||||
) -> bool:
|
||||
var actor_id := int(event.data["actor_id"])
|
||||
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 (
|
||||
StringName(event.data["destination_id"]) != opportunity.get_target_id()
|
||||
or StringName(event.data["item_id"]) != opportunity.get_resource_id()
|
||||
|
||||
Reference in New Issue
Block a user