feat: runtime familiarity from shared work and utility exposure
NPCs who target the same resource or site concurrently gain mutual familiarity +0.1 per encounter, capped at 1.0. Action selection now receives the full NPC array and adds a +0.3 utility bonus for actions that familiar NPCs are currently performing - NPCs gravitate toward their housemates' professions. Inspector shows the highest-familiarity NPC with a percentage score under the resource lines.
This commit is contained in:
@@ -127,7 +127,7 @@ func simulate_tick() -> void:
|
|||||||
and old_state in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]
|
and old_state in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]
|
||||||
and npc.task_state in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]
|
and npc.task_state in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]
|
||||||
):
|
):
|
||||||
var selection := action_selector.select_action(npc, village, clock.time_of_day())
|
var selection := action_selector.select_action(npc, village, clock.time_of_day(), npcs)
|
||||||
if selection != null:
|
if selection != null:
|
||||||
latest_decisions[npc.id] = selection
|
latest_decisions[npc.id] = selection
|
||||||
npc_decision_recorded.emit(npc, selection)
|
npc_decision_recorded.emit(npc, selection)
|
||||||
@@ -360,6 +360,23 @@ func notify_npc_arrived(npc_id: int) -> void:
|
|||||||
npc.has_travel_target = false
|
npc.has_travel_target = false
|
||||||
npc.start_working()
|
npc.start_working()
|
||||||
|
|
||||||
|
if npc.target_id != &"":
|
||||||
|
for other in npcs:
|
||||||
|
if other.id == npc.id or other.is_dead:
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
other.target_id == npc.target_id
|
||||||
|
and other.task_state in [
|
||||||
|
SimNPC.TASK_STATE_TRAVELING, SimNPC.TASK_STATE_WORKING
|
||||||
|
]
|
||||||
|
):
|
||||||
|
npc.familiarity[other.id] = minf(
|
||||||
|
float(npc.familiarity.get(other.id, 0.0)) + 0.1, 1.0
|
||||||
|
)
|
||||||
|
other.familiarity[npc.id] = minf(
|
||||||
|
float(other.familiarity.get(npc.id, 0.0)) + 0.1, 1.0
|
||||||
|
)
|
||||||
|
|
||||||
if debug_logs:
|
if debug_logs:
|
||||||
print(
|
print(
|
||||||
"[SimulationManager] ",
|
"[SimulationManager] ",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const WORK_BEGIN := 0.28
|
|||||||
const WORK_END := 0.85
|
const WORK_END := 0.85
|
||||||
|
|
||||||
|
|
||||||
func select_action(npc: SimNPC, village: SimVillage, time_of_day: float = 0.5) -> ActionSelectionResult:
|
func select_action(npc: SimNPC, village: SimVillage, time_of_day: float = 0.5, all_npcs: Array = []) -> ActionSelectionResult:
|
||||||
if npc.is_dead:
|
if npc.is_dead:
|
||||||
return null
|
return null
|
||||||
|
|
||||||
@@ -120,10 +120,10 @@ func select_action(npc: SimNPC, village: SimVillage, time_of_day: float = 0.5) -
|
|||||||
SimulationIds.ACTION_WANDER, -1.0, "Discretionary wander"
|
SimulationIds.ACTION_WANDER, -1.0, "Discretionary wander"
|
||||||
)
|
)
|
||||||
|
|
||||||
return _choose_best_work_action(npc, village)
|
return _choose_best_work_action(npc, village, all_npcs)
|
||||||
|
|
||||||
|
|
||||||
func _choose_best_work_action(npc: SimNPC, village: SimVillage) -> ActionSelectionResult:
|
func _choose_best_work_action(npc: SimNPC, village: SimVillage, all_npcs: Array) -> ActionSelectionResult:
|
||||||
var scores := {
|
var scores := {
|
||||||
SimulationIds.ACTION_GATHER_FOOD:
|
SimulationIds.ACTION_GATHER_FOOD:
|
||||||
_calculate_score(
|
_calculate_score(
|
||||||
@@ -170,6 +170,16 @@ func _choose_best_work_action(npc: SimNPC, village: SimVillage) -> ActionSelecti
|
|||||||
0.75
|
0.75
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
for familiar_id in npc.familiarity:
|
||||||
|
if not familiar_id is int:
|
||||||
|
continue
|
||||||
|
var other_task: StringName
|
||||||
|
for other in all_npcs:
|
||||||
|
if other.id == familiar_id and not other.is_dead:
|
||||||
|
other_task = other.current_task
|
||||||
|
break
|
||||||
|
if other_task in scores:
|
||||||
|
scores[other_task] = float(scores[other_task]) + 0.3
|
||||||
var best_action := SimulationIds.ACTION_GATHER_FOOD
|
var best_action := SimulationIds.ACTION_GATHER_FOOD
|
||||||
var best_score: float = scores[best_action]
|
var best_score: float = scores[best_action]
|
||||||
for action_id in [
|
for action_id in [
|
||||||
|
|||||||
+25
-1
@@ -132,12 +132,14 @@ func _refresh_npc_inspector() -> void:
|
|||||||
if not score_rows.is_empty():
|
if not score_rows.is_empty():
|
||||||
score_text = "\n\nUtility\n" + "\n".join(score_rows)
|
score_text = "\n\nUtility\n" + "\n".join(score_rows)
|
||||||
var event_text := _build_event_history(npc)
|
var event_text := _build_event_history(npc)
|
||||||
|
var housemate_text := _build_housemate_display(npc)
|
||||||
npc_inspector_label.text = (
|
npc_inspector_label.text = (
|
||||||
"%s · %s\n"
|
"%s · %s\n"
|
||||||
+ "Task: %s (%s)\n"
|
+ "Task: %s (%s)\n"
|
||||||
+ "Destination: %s\n"
|
+ "Destination: %s\n"
|
||||||
+ "Hunger: %.0f Energy: %.0f\n"
|
+ "Hunger: %.0f Energy: %.0f\n"
|
||||||
+ "Carrying food: %.0f wood: %.0f\n\n"
|
+ "Carrying food: %.0f wood: %.0f\n"
|
||||||
|
+ "%s\n"
|
||||||
+ "Why\n%s%s\n\n"
|
+ "Why\n%s%s\n\n"
|
||||||
+ "Recent\n%s\n\n"
|
+ "Recent\n%s\n\n"
|
||||||
+ "[Tab] Next villager [F10] Cinematic/debug [F12] Demo reset"
|
+ "[Tab] Next villager [F10] Cinematic/debug [F12] Demo reset"
|
||||||
@@ -151,6 +153,7 @@ func _refresh_npc_inspector() -> void:
|
|||||||
npc.energy,
|
npc.energy,
|
||||||
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
||||||
npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD),
|
npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD),
|
||||||
|
housemate_text,
|
||||||
reason_text,
|
reason_text,
|
||||||
score_text,
|
score_text,
|
||||||
event_text
|
event_text
|
||||||
@@ -188,3 +191,24 @@ func _build_event_history(npc: SimNPC) -> String:
|
|||||||
for event in events:
|
for event in events:
|
||||||
lines.append(event.description(name_map))
|
lines.append(event.description(name_map))
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_housemate_display(npc: SimNPC) -> String:
|
||||||
|
if npc.familiarity.is_empty():
|
||||||
|
return ""
|
||||||
|
var highest_id: int = -1
|
||||||
|
var highest_score := -1.0
|
||||||
|
for key in npc.familiarity:
|
||||||
|
var other_id: int = int(key)
|
||||||
|
var score: float = float(npc.familiarity[key])
|
||||||
|
if score > highest_score:
|
||||||
|
highest_score = score
|
||||||
|
highest_id = other_id
|
||||||
|
if highest_id < 0:
|
||||||
|
return ""
|
||||||
|
var other_name := "Someone"
|
||||||
|
for other_npc in simulation_manager.npcs:
|
||||||
|
if other_npc.id == highest_id:
|
||||||
|
other_name = other_npc.npc_name
|
||||||
|
break
|
||||||
|
return "Familiar: %s (%.0f%%)" % [other_name, highest_score * 100.0]
|
||||||
|
|||||||
Reference in New Issue
Block a user