feat: let trusted villagers ask the player directly as standing grows
This commit is contained in:
@@ -28,6 +28,7 @@ signal player_quest_opened(quest: PlayerQuestRecord)
|
|||||||
signal player_quest_completed(quest: PlayerQuestRecord)
|
signal player_quest_completed(quest: PlayerQuestRecord)
|
||||||
signal player_quest_expired(quest: PlayerQuestRecord)
|
signal player_quest_expired(quest: PlayerQuestRecord)
|
||||||
signal player_standing_changed(standing: PlayerStandingRecord)
|
signal player_standing_changed(standing: PlayerStandingRecord)
|
||||||
|
signal player_tier_advanced(tier: int, tier_name: String)
|
||||||
|
|
||||||
var village := SimVillage.new()
|
var village := SimVillage.new()
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ var relationship_system := RelationshipSystemScript.new()
|
|||||||
var event_knowledge_system := EventKnowledgeSystemScript.new()
|
var event_knowledge_system := EventKnowledgeSystemScript.new()
|
||||||
var opportunity_system := VillageOpportunitySystem.new()
|
var opportunity_system := VillageOpportunitySystem.new()
|
||||||
var player_quest_system := PlayerQuestSystem.new()
|
var player_quest_system := PlayerQuestSystem.new()
|
||||||
|
var _last_player_tier := PlayerStandingRecord.TIER_STRANGER
|
||||||
var storage_states: Dictionary:
|
var storage_states: Dictionary:
|
||||||
get:
|
get:
|
||||||
return economy.storage_states
|
return economy.storage_states
|
||||||
@@ -578,19 +580,30 @@ func _on_economic_event_recorded(event: EconomicEventRecord) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _resolve_animal_care_quest(event: EconomicEventRecord) -> void:
|
func _resolve_animal_care_quest(event: EconomicEventRecord) -> void:
|
||||||
var animal_id := StringName(event.data["destination_id"])
|
var animal_state := animal_care.get_state(StringName(event.data["destination_id"]))
|
||||||
var animal_state := animal_care.get_state(animal_id)
|
|
||||||
var resolution := player_quest_system.on_animal_fed(animal_state, event, tick_count)
|
var resolution := player_quest_system.on_animal_fed(animal_state, event, tick_count)
|
||||||
|
_finish_quest_resolution(resolution)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish_quest_resolution(resolution: Dictionary) -> void:
|
||||||
if not resolution.has("quest"):
|
if not resolution.has("quest"):
|
||||||
return
|
return
|
||||||
var quest: PlayerQuestRecord = resolution["quest"]
|
var quest: PlayerQuestRecord = resolution["quest"]
|
||||||
if resolution["completed"]:
|
if resolution["completed"]:
|
||||||
player_quest_completed.emit(quest)
|
player_quest_completed.emit(quest)
|
||||||
player_standing_changed.emit(player_quest_system.standing)
|
_emit_standing_change_and_tier()
|
||||||
else:
|
else:
|
||||||
player_quest_expired.emit(quest)
|
player_quest_expired.emit(quest)
|
||||||
|
|
||||||
|
|
||||||
|
func _emit_standing_change_and_tier() -> void:
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
||||||
func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bool:
|
func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bool:
|
||||||
var speaker := _find_npc_by_id(speaker_id)
|
var speaker := _find_npc_by_id(speaker_id)
|
||||||
var listener := _find_npc_by_id(listener_id)
|
var listener := _find_npc_by_id(listener_id)
|
||||||
@@ -674,20 +687,18 @@ func _update_opportunity_from_event(event: EconomicEventRecord) -> void:
|
|||||||
if changed.get_status() == OpportunityStateRecord.STATUS_OPEN:
|
if changed.get_status() == OpportunityStateRecord.STATUS_OPEN:
|
||||||
opportunity_opened.emit(changed)
|
opportunity_opened.emit(changed)
|
||||||
var quest := player_quest_system.consider_opportunity_opened(
|
var quest := player_quest_system.consider_opportunity_opened(
|
||||||
changed, get_active_opportunity_player_response(), tick_count
|
changed,
|
||||||
|
get_active_opportunity_player_response(),
|
||||||
|
tick_count,
|
||||||
|
player_quest_system.can_request_personally(changed)
|
||||||
)
|
)
|
||||||
if quest != null:
|
if quest != null:
|
||||||
player_quest_opened.emit(quest)
|
player_quest_opened.emit(quest)
|
||||||
return
|
return
|
||||||
opportunity_resolved.emit(changed, event)
|
opportunity_resolved.emit(changed, event)
|
||||||
var resolution := player_quest_system.on_opportunity_resolved(changed, event, tick_count)
|
_finish_quest_resolution(
|
||||||
if resolution.has("quest"):
|
player_quest_system.on_opportunity_resolved(changed, event, tick_count)
|
||||||
var quest: PlayerQuestRecord = resolution["quest"]
|
)
|
||||||
if resolution["completed"]:
|
|
||||||
player_quest_completed.emit(quest)
|
|
||||||
player_standing_changed.emit(player_quest_system.standing)
|
|
||||||
else:
|
|
||||||
player_quest_expired.emit(quest)
|
|
||||||
_maintain_event_knowledge(false)
|
_maintain_event_knowledge(false)
|
||||||
|
|
||||||
|
|
||||||
@@ -780,22 +791,20 @@ func get_active_player_quest() -> PlayerQuestRecord:
|
|||||||
func _consider_animal_care_quests() -> void:
|
func _consider_animal_care_quests() -> void:
|
||||||
var pantry: StorageStateRecord = get_pantry()
|
var pantry: StorageStateRecord = get_pantry()
|
||||||
for animal_state in animal_care.get_all_states():
|
for animal_state in animal_care.get_all_states():
|
||||||
var requester := _find_animal_requester(animal_state)
|
|
||||||
var quest := player_quest_system.consider_animal_care(
|
var quest := player_quest_system.consider_animal_care(
|
||||||
animal_state, requester, tick_count, pantry
|
animal_state, _nearest_npc_id(animal_state.get_position()), tick_count, pantry
|
||||||
)
|
)
|
||||||
if quest != null:
|
if quest != null:
|
||||||
player_quest_opened.emit(quest)
|
player_quest_opened.emit(quest)
|
||||||
|
|
||||||
|
|
||||||
func _find_animal_requester(animal_state: AnimalStateRecord) -> int:
|
func _nearest_npc_id(from_position: Vector3) -> int:
|
||||||
var animal_position := animal_state.get_position()
|
|
||||||
var nearest_id := -1
|
var nearest_id := -1
|
||||||
var nearest_distance := INF
|
var nearest_distance := INF
|
||||||
for npc in npcs:
|
for npc in npcs:
|
||||||
if npc.is_dead:
|
if npc.is_dead:
|
||||||
continue
|
continue
|
||||||
var distance := npc.position.distance_squared_to(animal_position)
|
var distance := npc.position.distance_squared_to(from_position)
|
||||||
if distance < nearest_distance:
|
if distance < nearest_distance:
|
||||||
nearest_distance = distance
|
nearest_distance = distance
|
||||||
nearest_id = npc.id
|
nearest_id = npc.id
|
||||||
@@ -1172,6 +1181,7 @@ func restore_state(record: SimulationStateRecord) -> bool:
|
|||||||
player_quest_system.restore(
|
player_quest_system.restore(
|
||||||
record.player_quests, int(record.simulation.get("next_quest_id", 0)), record.player_standing
|
record.player_quests, int(record.simulation.get("next_quest_id", 0)), record.player_standing
|
||||||
)
|
)
|
||||||
|
_last_player_tier = player_quest_system.standing.get_tier()
|
||||||
_maintain_event_knowledge(tick_count % get_knowledge_review_interval() == 0)
|
_maintain_event_knowledge(tick_count % get_knowledge_review_interval() == 0)
|
||||||
wander_random_sources.clear()
|
wander_random_sources.clear()
|
||||||
var wander_streams: Array = record.simulation["wander_random_streams"]
|
var wander_streams: Array = record.simulation["wander_random_streams"]
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ extends RefCounted
|
|||||||
const PANTRY_STANDING_REWARD := 8.0
|
const PANTRY_STANDING_REWARD := 8.0
|
||||||
const WOOD_STANDING_REWARD := 8.0
|
const WOOD_STANDING_REWARD := 8.0
|
||||||
const ANIMAL_CARE_STANDING_REWARD := 6.0
|
const ANIMAL_CARE_STANDING_REWARD := 6.0
|
||||||
|
const PERSONAL_TRUST_THRESHOLD := 0.5
|
||||||
|
const MIN_PERSONAL_REQUEST_TIER := PlayerStandingRecord.TIER_KNOWN_HAND
|
||||||
|
|
||||||
var quests: Array[PlayerQuestRecord] = []
|
var quests: Array[PlayerQuestRecord] = []
|
||||||
var next_quest_id := 0
|
var next_quest_id := 0
|
||||||
@@ -77,14 +79,12 @@ func on_animal_fed(
|
|||||||
func consider_opportunity_opened(
|
func consider_opportunity_opened(
|
||||||
opportunity: OpportunityStateRecord,
|
opportunity: OpportunityStateRecord,
|
||||||
player_response: OpportunityPlayerResponseResult,
|
player_response: OpportunityPlayerResponseResult,
|
||||||
current_tick: int
|
current_tick: int,
|
||||||
|
personal_request_eligible := false
|
||||||
) -> PlayerQuestRecord:
|
) -> PlayerQuestRecord:
|
||||||
if (
|
if opportunity == null or not opportunity.is_open() or current_tick < 0:
|
||||||
opportunity == null
|
return null
|
||||||
or not opportunity.is_open()
|
if player_response == null and not personal_request_eligible:
|
||||||
or player_response == null
|
|
||||||
or current_tick < 0
|
|
||||||
):
|
|
||||||
return null
|
return null
|
||||||
if _find_open_quest_for_opportunity(opportunity.get_opportunity_id()) != null:
|
if _find_open_quest_for_opportunity(opportunity.get_opportunity_id()) != null:
|
||||||
return null
|
return null
|
||||||
@@ -107,6 +107,16 @@ func consider_opportunity_opened(
|
|||||||
return quest
|
return quest
|
||||||
|
|
||||||
|
|
||||||
|
func can_request_personally(opportunity: OpportunityStateRecord) -> bool:
|
||||||
|
if (
|
||||||
|
opportunity == null
|
||||||
|
or not opportunity.is_open()
|
||||||
|
or standing.get_tier() < MIN_PERSONAL_REQUEST_TIER
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
return standing.get_gratitude(opportunity.get_interested_npc_id()) >= PERSONAL_TRUST_THRESHOLD
|
||||||
|
|
||||||
|
|
||||||
func on_opportunity_resolved(
|
func on_opportunity_resolved(
|
||||||
opportunity: OpportunityStateRecord, resolution_event: EconomicEventRecord, current_tick: int
|
opportunity: OpportunityStateRecord, resolution_event: EconomicEventRecord, current_tick: int
|
||||||
) -> Dictionary:
|
) -> Dictionary:
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
extends GutTest
|
||||||
|
|
||||||
|
const PlayerQuestSystemScript := preload("res://simulation/quests/PlayerQuestSystem.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func test_personal_request_needs_standing_tier_and_gratitude() -> void:
|
||||||
|
var system := PlayerQuestSystemScript.new()
|
||||||
|
var opportunity := _open_opportunity()
|
||||||
|
var pantry := _pantry()
|
||||||
|
|
||||||
|
assert_false(system.can_request_personally(opportunity))
|
||||||
|
for _grant in 6:
|
||||||
|
system.standing.grant_standing(3.0, opportunity.get_interested_npc_id())
|
||||||
|
assert_true(
|
||||||
|
system.can_request_personally(opportunity),
|
||||||
|
"Known Hand standing plus personal gratitude should make the NPC ask you directly"
|
||||||
|
)
|
||||||
|
var quest := system.consider_opportunity_opened(opportunity, null, 10, true)
|
||||||
|
assert_not_null(quest)
|
||||||
|
assert_eq(quest.get_requester_npc_id(), opportunity.get_interested_npc_id())
|
||||||
|
|
||||||
|
|
||||||
|
func test_no_personal_request_below_trust_threshold() -> void:
|
||||||
|
var system := PlayerQuestSystemScript.new()
|
||||||
|
var opportunity := _open_opportunity()
|
||||||
|
system.standing.grant_standing(16.0, opportunity.get_interested_npc_id())
|
||||||
|
system.standing.data["gratitude"][opportunity.get_interested_npc_id()] = 0.4
|
||||||
|
|
||||||
|
assert_false(system.can_request_personally(opportunity))
|
||||||
|
|
||||||
|
|
||||||
|
func test_standing_tier_progression() -> void:
|
||||||
|
var standing := PlayerStandingRecord.create()
|
||||||
|
assert_eq(standing.get_tier(), PlayerStandingRecord.TIER_STRANGER)
|
||||||
|
standing.grant_standing(14.0, 3)
|
||||||
|
assert_eq(standing.get_tier(), PlayerStandingRecord.TIER_STRANGER)
|
||||||
|
standing.grant_standing(2.0, 3)
|
||||||
|
assert_eq(standing.get_tier(), PlayerStandingRecord.TIER_KNOWN_HAND)
|
||||||
|
assert_eq(standing.get_tier_name(), "Known Hand")
|
||||||
|
standing.grant_standing(20.0, 3)
|
||||||
|
assert_eq(standing.get_tier(), PlayerStandingRecord.TIER_TRUSTED)
|
||||||
|
|
||||||
|
|
||||||
|
func test_standing_serialization_round_trip() -> void:
|
||||||
|
var standing := PlayerStandingRecord.create()
|
||||||
|
standing.grant_standing(35.0, 3)
|
||||||
|
var restored := PlayerStandingRecord.from_dictionary(standing.to_dictionary())
|
||||||
|
assert_not_null(restored)
|
||||||
|
assert_eq(restored.get_standing(), 35.0)
|
||||||
|
assert_eq(restored.get_tier(), PlayerStandingRecord.TIER_TRUSTED)
|
||||||
|
assert_eq(restored.get_gratitude(3), 0.1)
|
||||||
|
|
||||||
|
|
||||||
|
func _open_opportunity() -> OpportunityStateRecord:
|
||||||
|
return OpportunityStateRecord.create(
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
5,
|
||||||
|
3,
|
||||||
|
1.0,
|
||||||
|
SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY,
|
||||||
|
SimulationIds.STORAGE_VILLAGE_PANTRY,
|
||||||
|
SimulationIds.RESOURCE_FOOD
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _pantry() -> StorageStateRecord:
|
||||||
|
return StorageStateRecord.create(
|
||||||
|
SimulationIds.STORAGE_VILLAGE_PANTRY, {SimulationIds.RESOURCE_FOOD: 0.0}, 100.0
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b2jmmtiyvaqll
|
||||||
@@ -34,6 +34,8 @@ func _ready() -> void:
|
|||||||
simulation_manager.player_quest_expired.connect(_on_quest_expired)
|
simulation_manager.player_quest_expired.connect(_on_quest_expired)
|
||||||
if simulation_manager.has_signal("player_standing_changed"):
|
if simulation_manager.has_signal("player_standing_changed"):
|
||||||
simulation_manager.player_standing_changed.connect(_on_standing_changed)
|
simulation_manager.player_standing_changed.connect(_on_standing_changed)
|
||||||
|
if simulation_manager.has_signal("player_tier_advanced"):
|
||||||
|
simulation_manager.player_tier_advanced.connect(_on_tier_advanced)
|
||||||
if simulation_manager.has_signal("opportunity_resolved"):
|
if simulation_manager.has_signal("opportunity_resolved"):
|
||||||
simulation_manager.opportunity_resolved.connect(_on_opportunity_resolved)
|
simulation_manager.opportunity_resolved.connect(_on_opportunity_resolved)
|
||||||
if simulation_manager.has_signal("state_restored"):
|
if simulation_manager.has_signal("state_restored"):
|
||||||
@@ -53,6 +55,11 @@ func _on_standing_changed(_standing: PlayerStandingRecord) -> void:
|
|||||||
_refresh()
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_tier_advanced(_tier: int, tier_name: String) -> void:
|
||||||
|
_refresh()
|
||||||
|
_show_thanks("Standing grows", "The village now calls you a %s." % tier_name)
|
||||||
|
|
||||||
|
|
||||||
func _on_opportunity_resolved(
|
func _on_opportunity_resolved(
|
||||||
_opportunity: OpportunityStateRecord, _cause_event: EconomicEventRecord
|
_opportunity: OpportunityStateRecord, _cause_event: EconomicEventRecord
|
||||||
) -> void:
|
) -> void:
|
||||||
|
|||||||
Reference in New Issue
Block a user