feat: let the player talk to villagers and accept or decline personal requests

This commit is contained in:
2026-08-09 00:10:36 +02:00
parent 5e6c963d23
commit 0936ae6450
10 changed files with 520 additions and 36 deletions
+1
View File
@@ -5,6 +5,7 @@ const KIND_ANIMAL := &"animal"
const KIND_RESOURCE := &"resource"
const KIND_PANTRY := &"pantry"
const KIND_DEPOSIT := &"deposit"
const KIND_TALK := &"talk"
const KIND_GUARD := &"guard"
const KIND_STUDY := &"study"
+53
View File
@@ -0,0 +1,53 @@
class_name PlayerTalkResult
extends RefCounted
var npc_id: int
var npc_name: String
var greeting: String
var has_need: bool
var need_text: String
var quest_available: bool
var quest_reward: float
var can_accept: bool
var can_decline: bool
func _init(
talk_npc_id: int,
talk_npc_name: String,
talk_greeting: String,
talk_has_need: bool,
talk_need_text: String,
talk_quest_available: bool,
talk_quest_reward: float,
talk_can_accept: bool,
talk_can_decline: bool
) -> void:
npc_id = talk_npc_id
npc_name = talk_npc_name
greeting = talk_greeting
has_need = talk_has_need
need_text = talk_need_text
quest_available = talk_quest_available
quest_reward = talk_quest_reward
can_accept = talk_can_accept
can_decline = talk_can_decline
func cache_key() -> String:
return (
"|"
. join(
[
str(npc_id),
npc_name,
greeting,
str(has_need),
need_text,
str(quest_available),
"%.2f" % quest_reward,
str(can_accept),
str(can_decline),
]
)
)
+1
View File
@@ -0,0 +1 @@
uid://i7pjb5605cgr
+78
View File
@@ -72,6 +72,8 @@ func try_interact() -> void:
_execute_resource_interaction(context)
PlayerInteractionResult.KIND_DEPOSIT:
_execute_deposit_interaction(context)
PlayerInteractionResult.KIND_TALK:
_execute_talk_interaction(context)
PlayerInteractionResult.KIND_GUARD:
simulation_manager.add_safety(3.0)
interaction_feedback.emit(
@@ -131,9 +133,53 @@ func get_interaction_context() -> PlayerInteractionResult:
)
if is_near_storage(pantry_storage):
return _build_pantry_context()
var talk_npc := _find_talk_villager()
if talk_npc != null:
return _build_talk_context(talk_npc)
return null
func _find_talk_villager() -> SimNPC:
if (
simulation_manager == null
or world_view_manager == null
or not world_view_manager.has_method("find_nearest_active_npc_id")
):
return null
var npc_id: int = world_view_manager.find_nearest_active_npc_id(
global_position, interaction_range
)
if npc_id < 0:
return null
var npc := _find_npc(npc_id)
if npc == null or npc.is_dead:
return null
return npc
func _build_talk_context(npc: SimNPC) -> PlayerInteractionResult:
var talk: PlayerTalkResult = simulation_manager.get_villager_talk(npc.id)
if talk == null or not talk.has_need:
return null
var prompt := "Talk to %s" % npc.npc_name
if talk.quest_available:
prompt = "Accept %s's request" % npc.npc_name
elif talk.can_accept:
prompt = "Offer to help %s" % npc.npc_name
var detail := talk.need_text
if talk.quest_available:
detail = "Help +%.0f Standing" % talk.quest_reward
return PlayerInteractionResult.new(
PlayerInteractionResult.KIND_TALK,
SimulationIds.ACTION_IDLE,
SimulationIds.npc_inventory_id(npc.id),
npc.npc_name,
prompt,
detail,
null
)
func get_nearby_villager_inspection() -> VillagerInspectionResult:
if (
simulation_manager == null
@@ -437,6 +483,38 @@ func _execute_deposit_interaction(context: PlayerInteractionResult) -> void:
)
func _execute_talk_interaction(context: PlayerInteractionResult) -> void:
var npc_id := _talk_npc_id_from_target(context.target_id)
if npc_id < 0:
return
var quest: PlayerQuestRecord = simulation_manager.accept_villager_request(npc_id)
if quest == null:
interaction_feedback.emit(
"%s declines" % context.display_name,
"They have no request you can accept right now.",
false
)
return
interaction_feedback.emit(
"Request accepted",
(
"%s asked you for help · +%.0f Standing on completion"
% [context.display_name, quest.get_standing_reward()]
),
true
)
func _talk_npc_id_from_target(target_id: StringName) -> int:
var prefix := "npc_"
var suffix := "_inventory"
var raw := String(target_id)
if not raw.begins_with(prefix) or not raw.ends_with(suffix):
return -1
var id_text := raw.trim_prefix(prefix).trim_suffix(suffix)
return id_text.to_int()
func _execute_pantry_interaction(context: PlayerInteractionResult) -> void:
if not context.is_available():
interaction_feedback.emit("The pantry is empty", context.blocked_reason, false)