feat: add knowledge-gated pantry opportunities
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
class_name FoodShortageOpportunitySystem
|
||||
extends RefCounted
|
||||
|
||||
const CARE_HUNGER_THRESHOLD := 80.0
|
||||
|
||||
var opportunities: Array[OpportunityStateRecord] = []
|
||||
var next_opportunity_id := 0
|
||||
|
||||
|
||||
func consider_event(
|
||||
event: EconomicEventRecord,
|
||||
current_tick: int,
|
||||
pantry: StorageStateRecord,
|
||||
npcs: Array[SimNPC],
|
||||
knowledge_system: EventKnowledgeSystem,
|
||||
resource_states: Dictionary
|
||||
) -> OpportunityStateRecord:
|
||||
if event == null or pantry == null or current_tick < 0:
|
||||
return null
|
||||
var event_tick := int(event.data["tick"])
|
||||
if event_tick < 0 or event_tick > current_tick:
|
||||
return null
|
||||
var resolved := _try_resolve(event, pantry, npcs, resource_states)
|
||||
if resolved != null:
|
||||
return resolved
|
||||
return _try_open(event, current_tick, pantry, npcs, knowledge_system)
|
||||
|
||||
|
||||
func restore(records: Array[OpportunityStateRecord], restored_next_id: int) -> void:
|
||||
opportunities = records.duplicate()
|
||||
opportunities.sort_custom(_sort_by_id)
|
||||
next_opportunity_id = maxi(restored_next_id, 0)
|
||||
|
||||
|
||||
func get_all_sorted() -> Array[OpportunityStateRecord]:
|
||||
var sorted := opportunities.duplicate()
|
||||
sorted.sort_custom(_sort_by_id)
|
||||
return sorted
|
||||
|
||||
|
||||
func get_open_sorted() -> Array[OpportunityStateRecord]:
|
||||
var open_records: Array[OpportunityStateRecord] = []
|
||||
for opportunity in opportunities:
|
||||
if opportunity.is_open():
|
||||
open_records.append(opportunity)
|
||||
open_records.sort_custom(_sort_by_id)
|
||||
return open_records
|
||||
|
||||
|
||||
func get_open_opportunity() -> OpportunityStateRecord:
|
||||
var open_records := get_open_sorted()
|
||||
return open_records[0] if not open_records.is_empty() else null
|
||||
|
||||
|
||||
func get_latest() -> OpportunityStateRecord:
|
||||
var sorted := get_all_sorted()
|
||||
return sorted[-1] if not sorted.is_empty() else null
|
||||
|
||||
|
||||
func get_latest_for_npc(npc_id: int) -> OpportunityStateRecord:
|
||||
for index in range(opportunities.size() - 1, -1, -1):
|
||||
var opportunity := opportunities[index]
|
||||
if opportunity.get_interested_npc_id() == npc_id:
|
||||
return opportunity
|
||||
return null
|
||||
|
||||
|
||||
func _try_open(
|
||||
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 pantry.get_amount(SimulationIds.RESOURCE_FOOD) > 0.0:
|
||||
return null
|
||||
if not _is_food_withdrawal(event):
|
||||
return null
|
||||
var interested_npc := _select_interested_npc(
|
||||
int(event.data["event_id"]), npcs, knowledge_system
|
||||
)
|
||||
if interested_npc == null:
|
||||
return null
|
||||
var opportunity := OpportunityStateRecord.create(
|
||||
next_opportunity_id, current_tick, int(event.data["event_id"]), interested_npc.id
|
||||
)
|
||||
next_opportunity_id += 1
|
||||
opportunities.append(opportunity)
|
||||
return opportunity
|
||||
|
||||
|
||||
func _try_resolve(
|
||||
event: EconomicEventRecord,
|
||||
pantry: StorageStateRecord,
|
||||
npcs: Array[SimNPC],
|
||||
resource_states: Dictionary
|
||||
) -> OpportunityStateRecord:
|
||||
var opportunity := get_open_opportunity()
|
||||
if opportunity == null:
|
||||
return null
|
||||
if pantry.get_amount(SimulationIds.RESOURCE_FOOD) < opportunity.get_target_amount():
|
||||
return null
|
||||
if not _is_valid_supply(event, npcs, resource_states):
|
||||
return null
|
||||
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
|
||||
|
||||
|
||||
func _select_interested_npc(
|
||||
event_id: int, npcs: Array[SimNPC], knowledge_system: EventKnowledgeSystem
|
||||
) -> SimNPC:
|
||||
var candidates: Array[SimNPC] = []
|
||||
for npc in npcs:
|
||||
if npc.is_dead or npc.hunger < CARE_HUNGER_THRESHOLD:
|
||||
continue
|
||||
if not knowledge_system.knows_event(npc.id, event_id):
|
||||
continue
|
||||
candidates.append(npc)
|
||||
candidates.sort_custom(_sort_by_care)
|
||||
return candidates[0] if not candidates.is_empty() else null
|
||||
|
||||
|
||||
static func _is_food_withdrawal(event: EconomicEventRecord) -> bool:
|
||||
var actor_id := int(event.data["actor_id"])
|
||||
return (
|
||||
StringName(event.data["event_type"]) == SimulationIds.EVENT_STORAGE_WITHDRAWN
|
||||
and actor_id >= 0
|
||||
and StringName(event.data["source_id"]) == SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
and StringName(event.data["destination_id"]) == SimulationIds.npc_inventory_id(actor_id)
|
||||
and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
|
||||
and float(event.data["amount"]) > 0.0
|
||||
)
|
||||
|
||||
|
||||
static func _is_valid_supply(
|
||||
event: EconomicEventRecord, npcs: Array[SimNPC], resource_states: Dictionary
|
||||
) -> bool:
|
||||
if (
|
||||
StringName(event.data["item_id"]) != SimulationIds.RESOURCE_FOOD
|
||||
or StringName(event.data["destination_id"]) != SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
or float(event.data["amount"]) <= 0.0
|
||||
):
|
||||
return false
|
||||
var event_type := StringName(event.data["event_type"])
|
||||
var actor_id := int(event.data["actor_id"])
|
||||
if event_type == SimulationIds.EVENT_STORAGE_DEPOSITED:
|
||||
return (
|
||||
_find_npc(actor_id, npcs) != null
|
||||
and StringName(event.data["source_id"]) == SimulationIds.npc_inventory_id(actor_id)
|
||||
)
|
||||
if event_type != SimulationIds.EVENT_RESOURCE_EXTRACTED or actor_id != -1:
|
||||
return false
|
||||
var resource_state := (
|
||||
resource_states.get(StringName(event.data["source_id"])) as ResourceStateRecord
|
||||
)
|
||||
return (
|
||||
resource_state != null
|
||||
and resource_state.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
||||
and resource_state.can_player_use_resource()
|
||||
)
|
||||
|
||||
|
||||
static func _sort_by_care(first: SimNPC, second: SimNPC) -> bool:
|
||||
if first.hunger != second.hunger:
|
||||
return first.hunger > second.hunger
|
||||
return first.id < second.id
|
||||
|
||||
|
||||
static func _find_npc(npc_id: int, npcs: Array[SimNPC]) -> SimNPC:
|
||||
for npc in npcs:
|
||||
if npc.id == npc_id:
|
||||
return npc
|
||||
return null
|
||||
|
||||
|
||||
static func _sort_by_id(first: OpportunityStateRecord, second: OpportunityStateRecord) -> bool:
|
||||
return first.get_opportunity_id() < second.get_opportunity_id()
|
||||
@@ -0,0 +1 @@
|
||||
uid://r6gpw0hx8oe2
|
||||
Reference in New Issue
Block a user