feat: add knowledge-gated pantry opportunities

This commit is contained in:
Rijad Zuzo
2026-07-13 00:46:56 +02:00
parent 9fcf6a0a58
commit c722e32a88
26 changed files with 2027 additions and 77 deletions
+102 -4
View File
@@ -33,6 +33,10 @@ func _ready() -> void:
simulation_manager.event_knowledge_changed.connect(_on_event_knowledge_changed)
if simulation_manager.has_signal("event_knowledge_forgotten"):
simulation_manager.event_knowledge_forgotten.connect(_on_event_knowledge_forgotten)
if simulation_manager.has_signal("opportunity_opened"):
simulation_manager.opportunity_opened.connect(_on_opportunity_opened)
if simulation_manager.has_signal("opportunity_resolved"):
simulation_manager.opportunity_resolved.connect(_on_opportunity_resolved)
if "village" in simulation_manager:
_on_village_changed(simulation_manager.village)
@@ -79,6 +83,7 @@ func _on_village_changed(village: SimVillage) -> void:
var wood_rate := ""
if rates["wood_per_day"] > 0.01:
wood_rate = " (%.0f/d)" % rates["wood_per_day"]
var opportunity_text := _build_active_opportunity_display()
village_stats_label.text = (
"""
Village [%s]
@@ -92,7 +97,7 @@ func _on_village_changed(village: SimVillage) -> void:
Food Mod: %.2f
Safety Mod: %.2f
Knowledge Mod: %.2f
%s
%s%s
"""
% [
speed_text,
@@ -106,7 +111,8 @@ func _on_village_changed(village: SimVillage) -> void:
village.food_modifier,
village.safety_modifier,
village.knowledge_modifier,
event_text
opportunity_text,
event_text,
]
)
@@ -117,6 +123,7 @@ func _on_npc_decision_recorded(_npc: SimNPC, _decision: ActionSelectionResult) -
func _on_state_restored() -> void:
selected_npc_index = 0
_refresh_village_display()
_refresh_npc_inspector()
@@ -139,6 +146,23 @@ func _on_event_knowledge_forgotten(_knower_id: int, _event: EconomicEventRecord)
_refresh_npc_inspector()
func _on_opportunity_opened(_opportunity: RefCounted) -> void:
_refresh_village_display()
_refresh_npc_inspector()
func _on_opportunity_resolved(
_opportunity: RefCounted, _resolution_event: EconomicEventRecord
) -> void:
_refresh_village_display()
_refresh_npc_inspector()
func _refresh_village_display() -> void:
if simulation_manager != null and "village" in simulation_manager:
_on_village_changed(simulation_manager.village)
func _refresh_npc_inspector() -> void:
if not debug_overlay_visible:
return
@@ -189,6 +213,7 @@ func _refresh_npc_inspector() -> void:
var event_text := _build_event_history(npc)
var relationship_text := _build_relationship_display(npc)
var memory_history_text := _build_memory_history_display(npc)
var opportunity_text := _build_npc_opportunity_display(npc)
npc_inspector_label.text = (
(
"%s · %s\n"
@@ -198,6 +223,7 @@ func _refresh_npc_inspector() -> void:
+ "Carrying food: %.0f wood: %.0f\n"
+ "%s\n"
+ "%s\n"
+ "%s"
+ "Why\n%s%s\n\n"
+ "Own history\n%s\n\n"
+ "[Tab] Next villager [F10] Cinematic/debug [F12] Demo reset"
@@ -214,6 +240,7 @@ func _refresh_npc_inspector() -> void:
npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD),
relationship_text,
memory_history_text,
opportunity_text,
reason_text,
score_text,
event_text
@@ -221,6 +248,69 @@ func _refresh_npc_inspector() -> void:
)
func _build_active_opportunity_display() -> String:
if not simulation_manager.has_method("get_active_opportunity"):
return ""
var opportunity = simulation_manager.get_active_opportunity()
if opportunity == null:
return ""
var interested_name := _get_npc_name(opportunity.get_interested_npc_id())
var resource_name := String(opportunity.get_resource_id()).capitalize()
var current_amount := 0.0
var pantry: StorageStateRecord = simulation_manager.get_pantry()
if pantry != null:
current_amount = pantry.get_amount(opportunity.get_resource_id())
return (
"\n\nVillage need\n"
+ "◆ Restock the empty pantry\n"
+ "%s is worried about the shortage\n" % interested_name
+ "%s %.0f / %.0f" % [resource_name, current_amount, opportunity.get_target_amount()]
)
func _build_npc_opportunity_display(npc: SimNPC) -> String:
if not simulation_manager.has_method("get_latest_opportunity_for_npc"):
return ""
var opportunity = simulation_manager.get_latest_opportunity_for_npc(npc.id)
if opportunity == null:
return ""
var resource_name := String(opportunity.get_resource_id()).capitalize()
if opportunity.get_status() == OpportunityStateRecord.STATUS_OPEN:
var pantry: StorageStateRecord = simulation_manager.get_pantry()
var current_amount := 0.0
if pantry != null:
current_amount = pantry.get_amount(opportunity.get_resource_id())
return (
"Open village need\n"
+ "◆ Restock the empty pantry\n"
+ (
"%s %.0f / %.0f\n\n"
% [resource_name, current_amount, opportunity.get_target_amount()]
)
)
if opportunity.get_status() != OpportunityStateRecord.STATUS_RESOLVED:
return ""
var resolution_event: EconomicEventRecord = simulation_manager.get_opportunity_resolution_event(
opportunity
)
var actor_name := "Someone"
if resolution_event != null:
var actor_id := int(resolution_event.data["actor_id"])
actor_name = "Player" if actor_id < 0 else _get_npc_name(actor_id)
return (
"Resolved village need\n"
+ "%s restocked the pantry\n" % actor_name
+ "%s target %.0f\n\n" % [resource_name, opportunity.get_target_amount()]
)
func _get_npc_name(npc_id: int) -> String:
for npc in simulation_manager.npcs:
if npc.id == npc_id:
return npc.npc_name
return "Someone"
func _get_action_name(action_id: StringName) -> String:
if action_id == SimulationIds.ACTION_IDLE:
return "Idle"
@@ -327,12 +417,20 @@ func _build_memory_history_display(npc: SimNPC) -> String:
func _build_compact_memory_summary(event: EconomicEventRecord, name_map: Dictionary) -> String:
var event_type := StringName(event.data["event_type"])
var item_id := StringName(event.data["item_id"])
if (
StringName(event.data["event_type"]) == SimulationIds.EVENT_STORAGE_DEPOSITED
and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
event_type == SimulationIds.EVENT_STORAGE_DEPOSITED
and item_id == SimulationIds.RESOURCE_FOOD
):
var actor_name: String = name_map.get(int(event.data["actor_id"]), "Someone")
return "%s stocked %.0f food" % [actor_name, float(event.data["amount"])]
if (
event_type == SimulationIds.EVENT_STORAGE_WITHDRAWN
and item_id == SimulationIds.RESOURCE_FOOD
):
var actor_name: String = name_map.get(int(event.data["actor_id"]), "Someone")
return "%s took %.0f food from the pantry" % [actor_name, float(event.data["amount"])]
return event.description(name_map)