feat: let the player talk to villagers and accept or decline personal requests
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://i7pjb5605cgr
|
||||
@@ -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)
|
||||
|
||||
@@ -56,6 +56,7 @@ var event_knowledge_system := EventKnowledgeSystemScript.new()
|
||||
var opportunity_system := VillageOpportunitySystem.new()
|
||||
var player_quest_system := PlayerQuestSystem.new()
|
||||
var player_needs := PlayerNeedsSystem.new()
|
||||
var player_negotiation := PlayerNegotiationSystem.new()
|
||||
var _last_player_tier := PlayerStandingRecord.TIER_STRANGER
|
||||
var storage_states: Dictionary:
|
||||
get:
|
||||
@@ -88,6 +89,15 @@ func _ready() -> void:
|
||||
event_recorder.set_npcs(npcs)
|
||||
player_needs.configure(economy, event_recorder)
|
||||
player_needs.set_resource_states(resource_states)
|
||||
player_negotiation.configure(
|
||||
player_quest_system,
|
||||
Callable(self, "_find_npc_by_id"),
|
||||
Callable(self, "get_active_opportunity"),
|
||||
Callable(self, "get_active_opportunity_player_response"),
|
||||
player_quest_opened,
|
||||
player_quest_expired,
|
||||
player_standing_changed
|
||||
)
|
||||
economy.inventory_changed.connect(_on_economy_inventory_changed)
|
||||
economy.economic_event_requested.connect(event_recorder.record_economic)
|
||||
economy.narrative_event_requested.connect(event_recorder.record_narrative)
|
||||
@@ -181,7 +191,10 @@ func simulate_tick() -> void:
|
||||
if debug_logs:
|
||||
print("--- Tick ", tick_count, " ---")
|
||||
animal_care.advance(tick_count)
|
||||
_consider_animal_care_quests()
|
||||
for quest in player_quest_system.consider_animal_care_quests(
|
||||
animal_care, npcs, get_pantry(), tick_count
|
||||
):
|
||||
player_quest_opened.emit(quest)
|
||||
_advance_player_needs()
|
||||
var village_was_changed := false
|
||||
_population_view.rebuild(npcs)
|
||||
@@ -594,15 +607,15 @@ func _finish_quest_resolution(resolution: Dictionary) -> void:
|
||||
if not resolution.has("quest"):
|
||||
return
|
||||
var quest: PlayerQuestRecord = resolution["quest"]
|
||||
if not resolution["completed"]:
|
||||
player_quest_expired.emit(quest)
|
||||
if resolution["completed"]:
|
||||
player_quest_completed.emit(quest)
|
||||
var standing := player_quest_system.standing
|
||||
player_standing_changed.emit(standing)
|
||||
if standing.get_tier() > _last_player_tier:
|
||||
_last_player_tier = standing.get_tier()
|
||||
player_tier_advanced.emit(standing.get_tier(), standing.get_tier_name())
|
||||
return
|
||||
player_quest_completed.emit(quest)
|
||||
var standing := player_quest_system.standing
|
||||
player_standing_changed.emit(standing)
|
||||
if standing.get_tier() > _last_player_tier:
|
||||
_last_player_tier = standing.get_tier()
|
||||
player_tier_advanced.emit(standing.get_tier(), standing.get_tier_name())
|
||||
player_quest_expired.emit(quest)
|
||||
|
||||
|
||||
func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bool:
|
||||
@@ -764,10 +777,8 @@ func get_player_state() -> PlayerStateRecord:
|
||||
|
||||
|
||||
func player_eat(amount: float) -> float:
|
||||
var eaten := player_needs.eat(amount)
|
||||
if eaten > 0.0:
|
||||
player_needs_changed.emit(player_needs.state)
|
||||
return eaten
|
||||
player_needs_changed.emit(player_needs.state)
|
||||
return player_needs.eat(amount)
|
||||
|
||||
|
||||
func player_gather(node: ResourceNode) -> float:
|
||||
@@ -785,33 +796,22 @@ func player_deposit(resource_id: StringName) -> float:
|
||||
return deposited
|
||||
|
||||
|
||||
func _consider_animal_care_quests() -> void:
|
||||
var pantry: StorageStateRecord = get_pantry()
|
||||
for animal_state in animal_care.get_all_states():
|
||||
var quest := player_quest_system.consider_animal_care(
|
||||
animal_state, _nearest_npc_id(animal_state.get_position()), tick_count, pantry
|
||||
)
|
||||
if quest != null:
|
||||
player_quest_opened.emit(quest)
|
||||
|
||||
|
||||
func _nearest_npc_id(from_position: Vector3) -> int:
|
||||
var nearest_id := -1
|
||||
var nearest_distance := INF
|
||||
for npc in npcs:
|
||||
if npc.is_dead:
|
||||
continue
|
||||
var distance := npc.position.distance_squared_to(from_position)
|
||||
if distance < nearest_distance:
|
||||
nearest_distance = distance
|
||||
nearest_id = npc.id
|
||||
return nearest_id
|
||||
|
||||
|
||||
func get_player_quests() -> Array[PlayerQuestRecord]:
|
||||
return player_quest_system.get_all_sorted()
|
||||
|
||||
|
||||
func get_villager_talk(npc_id: int) -> PlayerTalkResult:
|
||||
return player_negotiation.get_talk(npc_id)
|
||||
|
||||
|
||||
func accept_villager_request(npc_id: int) -> PlayerQuestRecord:
|
||||
return player_negotiation.accept_request(npc_id, tick_count)
|
||||
|
||||
|
||||
func decline_villager_request(npc_id: int) -> PlayerQuestRecord:
|
||||
return player_negotiation.decline_request(npc_id, tick_count)
|
||||
|
||||
|
||||
func get_latest_player_quest_for_requester(requester_npc_id: int) -> PlayerQuestRecord:
|
||||
return player_quest_system.get_latest_for_requester(requester_npc_id)
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
class_name PlayerNegotiationSystem
|
||||
extends RefCounted
|
||||
|
||||
var quest_system: PlayerQuestSystem
|
||||
var npc_lookup: Callable
|
||||
var opportunity_provider: Callable
|
||||
var player_response_provider: Callable
|
||||
var quest_opened_signal: Signal
|
||||
var quest_expired_signal: Signal
|
||||
var standing_changed_signal: Signal
|
||||
|
||||
|
||||
func configure(
|
||||
quests: PlayerQuestSystem,
|
||||
lookup: Callable,
|
||||
opportunity_source: Callable,
|
||||
response_source: Callable,
|
||||
opened: Signal,
|
||||
expired: Signal,
|
||||
standing: Signal
|
||||
) -> void:
|
||||
quest_system = quests
|
||||
npc_lookup = lookup
|
||||
opportunity_provider = opportunity_source
|
||||
player_response_provider = response_source
|
||||
quest_opened_signal = opened
|
||||
quest_expired_signal = expired
|
||||
standing_changed_signal = standing
|
||||
|
||||
|
||||
func get_talk(npc_id: int) -> PlayerTalkResult:
|
||||
var npc := npc_lookup.call(npc_id) as SimNPC
|
||||
if npc == null or npc.is_dead:
|
||||
return null
|
||||
return quest_system.get_talk_for_requester(npc.id, npc.npc_name, opportunity_provider.call())
|
||||
|
||||
|
||||
func accept_request(npc_id: int, current_tick: int) -> PlayerQuestRecord:
|
||||
var npc := npc_lookup.call(npc_id) as SimNPC
|
||||
if npc == null:
|
||||
return null
|
||||
var opportunity: OpportunityStateRecord = opportunity_provider.call()
|
||||
if opportunity == null or opportunity.get_interested_npc_id() != npc.id:
|
||||
return null
|
||||
var result: Dictionary = quest_system.accept_opportunity_quest(
|
||||
opportunity, player_response_provider.call(), current_tick
|
||||
)
|
||||
var quest := result.get("quest") as PlayerQuestRecord
|
||||
if quest != null and result.get("created", false) and not quest_opened_signal.is_null():
|
||||
quest_opened_signal.emit(quest)
|
||||
return quest
|
||||
|
||||
|
||||
func decline_request(npc_id: int, current_tick: int) -> PlayerQuestRecord:
|
||||
var npc := npc_lookup.call(npc_id) as SimNPC
|
||||
if npc == null:
|
||||
return null
|
||||
var opportunity: OpportunityStateRecord = opportunity_provider.call()
|
||||
if opportunity == null or opportunity.get_interested_npc_id() != npc.id:
|
||||
return null
|
||||
var quest := quest_system.decline_opportunity_quest(opportunity, current_tick)
|
||||
if quest != null:
|
||||
if not quest_expired_signal.is_null():
|
||||
quest_expired_signal.emit(quest)
|
||||
if not standing_changed_signal.is_null():
|
||||
standing_changed_signal.emit(quest_system.standing)
|
||||
return quest
|
||||
@@ -0,0 +1 @@
|
||||
uid://7oayv5ga327o
|
||||
@@ -135,6 +135,58 @@ func on_opportunity_resolved(
|
||||
return {}
|
||||
|
||||
|
||||
func get_talk_for_requester(
|
||||
requester_id: int, requester_name: String, opportunity: OpportunityStateRecord
|
||||
) -> PlayerTalkResult:
|
||||
var has_need := opportunity != null and opportunity.is_open()
|
||||
var need_text := ""
|
||||
if has_need:
|
||||
need_text = "I'm worried about the %s." % String(opportunity.get_resource_id()).capitalize()
|
||||
var quest := get_latest_for_requester(requester_id)
|
||||
var quest_available := quest != null and quest.is_open()
|
||||
var reward := quest.get_standing_reward() if quest_available else 0.0
|
||||
var can_accept := has_need and (quest_available or can_request_personally(opportunity))
|
||||
return PlayerTalkResult.new(
|
||||
requester_id,
|
||||
requester_name,
|
||||
"%s greets you." % requester_name,
|
||||
has_need,
|
||||
need_text,
|
||||
quest_available,
|
||||
reward,
|
||||
can_accept,
|
||||
has_need
|
||||
)
|
||||
|
||||
|
||||
func accept_opportunity_quest(
|
||||
opportunity: OpportunityStateRecord,
|
||||
player_response: OpportunityPlayerResponseResult,
|
||||
current_tick: int
|
||||
) -> Dictionary:
|
||||
var existing := _find_open_quest_for_opportunity(opportunity.get_opportunity_id())
|
||||
if existing != null:
|
||||
return {"quest": existing, "created": false}
|
||||
var created := consider_opportunity_opened(
|
||||
opportunity, player_response, current_tick, can_request_personally(opportunity)
|
||||
)
|
||||
return {"quest": created, "created": created != null}
|
||||
|
||||
|
||||
func decline_opportunity_quest(
|
||||
opportunity: OpportunityStateRecord, current_tick: int
|
||||
) -> PlayerQuestRecord:
|
||||
var quest := _find_open_quest_for_opportunity(opportunity.get_opportunity_id())
|
||||
if quest == null:
|
||||
return null
|
||||
if not quest.expire(current_tick):
|
||||
return null
|
||||
var requester_id := quest.get_requester_npc_id()
|
||||
var gratitude := standing.get_gratitude(requester_id)
|
||||
standing.data["gratitude"][requester_id] = maxf(gratitude - 0.1, 0.0)
|
||||
return quest
|
||||
|
||||
|
||||
func on_opportunity_invalidated(
|
||||
opportunity: OpportunityStateRecord, current_tick: int
|
||||
) -> PlayerQuestRecord:
|
||||
@@ -146,6 +198,34 @@ func on_opportunity_invalidated(
|
||||
return null
|
||||
|
||||
|
||||
func consider_animal_care_quests(
|
||||
animal_care: AnimalCareSystem,
|
||||
npcs: Array[SimNPC],
|
||||
pantry: StorageStateRecord,
|
||||
current_tick: int
|
||||
) -> Array[PlayerQuestRecord]:
|
||||
var opened: Array[PlayerQuestRecord] = []
|
||||
for animal_state in animal_care.get_all_states():
|
||||
var requester := _nearest_npc_id(animal_state.get_position(), npcs)
|
||||
var quest := consider_animal_care(animal_state, requester, current_tick, pantry)
|
||||
if quest != null:
|
||||
opened.append(quest)
|
||||
return opened
|
||||
|
||||
|
||||
static func _nearest_npc_id(from_position: Vector3, npcs: Array[SimNPC]) -> int:
|
||||
var nearest_id := -1
|
||||
var nearest_distance := INF
|
||||
for npc in npcs:
|
||||
if npc.is_dead:
|
||||
continue
|
||||
var distance := npc.position.distance_squared_to(from_position)
|
||||
if distance < nearest_distance:
|
||||
nearest_distance = distance
|
||||
nearest_id = npc.id
|
||||
return nearest_id
|
||||
|
||||
|
||||
func get_active_quest() -> PlayerQuestRecord:
|
||||
for quest in quests:
|
||||
if quest.is_open():
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
extends SceneTree
|
||||
|
||||
var failures: Array[String] = []
|
||||
var opened_quest_ids: Array[int] = []
|
||||
var expired_quest_ids: Array[int] = []
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var manager := _create_manager()
|
||||
manager.player_quest_opened.connect(_on_quest_opened)
|
||||
manager.player_quest_expired.connect(_on_quest_expired)
|
||||
var berry := _register_resource(manager, &"talk_berry_bush")
|
||||
var actor: SimNPC = manager.npcs[0]
|
||||
var interested: SimNPC = manager.npcs[2]
|
||||
_set_pantry_amount(manager, 1.0)
|
||||
for npc in manager.npcs:
|
||||
npc.position = Vector3(40.0 + npc.id * 10.0, 0.0, 0.0)
|
||||
actor.position = Vector3.ZERO
|
||||
interested.position = Vector3(4.0, 0.0, 0.0)
|
||||
actor.hunger = 60.0
|
||||
interested.hunger = 90.0
|
||||
manager.economy.withdraw_to_inventory(actor, SimulationIds.RESOURCE_FOOD, 1.0)
|
||||
var opportunity: OpportunityStateRecord = manager.get_active_opportunity()
|
||||
_check(
|
||||
opportunity != null and opportunity.get_interested_npc_id() == interested.id,
|
||||
"The talk proof needs an open need owned by the inspected villager",
|
||||
)
|
||||
if opportunity == null:
|
||||
berry.free()
|
||||
manager.free()
|
||||
_finish()
|
||||
return
|
||||
|
||||
var talk: PlayerTalkResult = manager.get_villager_talk(interested.id)
|
||||
_check(
|
||||
(
|
||||
talk != null
|
||||
and talk.npc_id == interested.id
|
||||
and talk.npc_name == interested.npc_name
|
||||
and talk.has_need
|
||||
and talk.can_accept
|
||||
and interested.npc_name in talk.greeting
|
||||
),
|
||||
"Talking should greet the villager and surface their real need",
|
||||
)
|
||||
|
||||
var checksum_before: String = manager.get_state_checksum()
|
||||
var accepted: PlayerQuestRecord = manager.accept_villager_request(interested.id)
|
||||
print(
|
||||
" debug accepted=",
|
||||
accepted,
|
||||
" opened=",
|
||||
opened_quest_ids,
|
||||
" active=",
|
||||
manager.get_active_player_quest(),
|
||||
" checksum_same=",
|
||||
manager.get_state_checksum() == checksum_before,
|
||||
" resolved=",
|
||||
manager.get_player_standing().get_resolved_needs(),
|
||||
)
|
||||
_check(
|
||||
(
|
||||
accepted != null
|
||||
and accepted.is_open()
|
||||
and accepted.get_requester_npc_id() == interested.id
|
||||
and opened_quest_ids == [accepted.get_quest_id()]
|
||||
and manager.get_state_checksum() == checksum_before
|
||||
and manager.get_player_standing().get_resolved_needs() == 0
|
||||
),
|
||||
"Accepting a request should surface the same open quest without granting standing yet",
|
||||
)
|
||||
var accepted_again: PlayerQuestRecord = manager.accept_villager_request(interested.id)
|
||||
_check(
|
||||
accepted_again != null and accepted_again.get_quest_id() == accepted.get_quest_id(),
|
||||
"Re-accepting should not duplicate the open quest",
|
||||
)
|
||||
|
||||
var carry_state: PlayerStateRecord = manager.get_player_state()
|
||||
carry_state.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
|
||||
var deposited: float = manager.player_deposit(SimulationIds.RESOURCE_FOOD)
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(deposited, 1.0)
|
||||
and manager.get_active_player_quest() == null
|
||||
and manager.get_player_standing().get_resolved_needs() == 1
|
||||
),
|
||||
"Completing the accepted request through a real deposit should resolve it",
|
||||
)
|
||||
|
||||
var declined := _create_manager(904)
|
||||
declined.player_quest_opened.connect(_on_quest_opened)
|
||||
declined.player_quest_expired.connect(_on_quest_expired)
|
||||
var berry2 := _register_resource(declined, &"talk_berry_bush_2")
|
||||
var actor2: SimNPC = declined.npcs[0]
|
||||
var interested2: SimNPC = declined.npcs[2]
|
||||
_set_pantry_amount(declined, 1.0)
|
||||
for npc in declined.npcs:
|
||||
npc.position = Vector3(40.0 + npc.id * 10.0, 0.0, 0.0)
|
||||
actor2.position = Vector3.ZERO
|
||||
interested2.position = Vector3(4.0, 0.0, 0.0)
|
||||
actor2.hunger = 60.0
|
||||
interested2.hunger = 90.0
|
||||
declined.economy.withdraw_to_inventory(actor2, SimulationIds.RESOURCE_FOOD, 1.0)
|
||||
var declined_quest: PlayerQuestRecord = declined.get_active_player_quest()
|
||||
_check(declined_quest != null, "The decline branch needs an open quest")
|
||||
if declined_quest == null:
|
||||
berry.free()
|
||||
berry2.free()
|
||||
manager.free()
|
||||
declined.free()
|
||||
_finish()
|
||||
return
|
||||
var gratitude_before: float = declined.get_player_standing().get_gratitude(interested2.id)
|
||||
var refused: PlayerQuestRecord = declined.decline_villager_request(interested2.id)
|
||||
_check(
|
||||
(
|
||||
refused != null
|
||||
and refused.get_quest_id() == declined_quest.get_quest_id()
|
||||
and expired_quest_ids.has(declined_quest.get_quest_id())
|
||||
and declined.get_active_player_quest() == null
|
||||
and declined.get_player_standing().get_gratitude(interested2.id) <= gratitude_before
|
||||
),
|
||||
"Declining a personal request should close the quest without granting gratitude",
|
||||
)
|
||||
|
||||
berry.free()
|
||||
berry2.free()
|
||||
manager.free()
|
||||
declined.free()
|
||||
_finish()
|
||||
|
||||
|
||||
func _register_resource(manager: Node, node_id: StringName) -> ResourceNode:
|
||||
var node := ResourceNode.new()
|
||||
node.name = "TalkResource"
|
||||
node.node_id = node_id
|
||||
node.action_id = SimulationIds.ACTION_GATHER_FOOD
|
||||
node.resource_id = SimulationIds.RESOURCE_FOOD
|
||||
node.initial_amount = 2.0
|
||||
node.yield_per_action = 2.0
|
||||
node.debug_label_enabled = false
|
||||
var interaction_point := Marker3D.new()
|
||||
interaction_point.name = "InteractionPoint"
|
||||
node.add_child(interaction_point)
|
||||
root.add_child(node)
|
||||
_check(
|
||||
manager.register_resource_node(node),
|
||||
"The talk proof should bind a real finite food ResourceNode"
|
||||
)
|
||||
return node
|
||||
|
||||
|
||||
func _create_manager(seed_value: int = 901) -> Node:
|
||||
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||
manager.simulation_seed = seed_value
|
||||
manager.debug_logs = false
|
||||
var home_positions: Array[Vector3] = [
|
||||
Vector3(0.0, 0.0, 0.0),
|
||||
Vector3(2.0, 0.0, 0.0),
|
||||
Vector3(10.0, 0.0, 0.0),
|
||||
Vector3(12.0, 0.0, 0.0),
|
||||
Vector3(30.0, 0.0, 30.0),
|
||||
Vector3(40.0, 0.0, 40.0),
|
||||
]
|
||||
manager.home_positions = home_positions
|
||||
root.add_child(manager)
|
||||
manager.set_process(false)
|
||||
return manager
|
||||
|
||||
|
||||
func _set_pantry_amount(manager: Node, amount: float) -> void:
|
||||
var pantry: StorageStateRecord = manager.get_pantry()
|
||||
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
|
||||
pantry.deposit(SimulationIds.RESOURCE_FOOD, amount)
|
||||
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
||||
|
||||
|
||||
func _on_quest_opened(quest: PlayerQuestRecord) -> void:
|
||||
opened_quest_ids.append(quest.get_quest_id())
|
||||
|
||||
|
||||
func _on_quest_expired(quest: PlayerQuestRecord) -> void:
|
||||
expired_quest_ids.append(quest.get_quest_id())
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if failures.is_empty():
|
||||
print("[TEST] Player talk and personal negotiation passed")
|
||||
quit(0)
|
||||
return
|
||||
for failure in failures:
|
||||
push_error("[TEST] " + failure)
|
||||
quit(1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdh2d6y6jvajo
|
||||
Reference in New Issue
Block a user