merge: integrate remote player quest slices

This commit is contained in:
Rijad Zuzo
2026-08-16 16:53:30 +02:00
55 changed files with 3640 additions and 54 deletions
+70
View File
@@ -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
@@ -0,0 +1,168 @@
extends GutTest
const VillagerOpportunityNoteScript := preload("res://player/VillagerOpportunityNote.gd")
func test_no_opportunity_produces_no_need() -> void:
var note := VillagerOpportunityNote.derive(null, null, null, "", null, "", "")
assert_not_null(note)
assert_false(note.has_need)
assert_eq(note.help_kind, &"")
func test_player_route_wins_over_helper_when_derivable() -> void:
var opportunity := _open_pantry_opportunity()
var response := _player_response()
var helper := _helper(7)
var pantry := _pantry(0.0, 100.0)
var note := VillagerOpportunityNote.derive(
opportunity, response, helper, "Tarik", pantry, "Village Pantry", "food"
)
assert_true(note.has_need)
assert_eq(note.help_kind, VillagerOpportunityNote.HELP_PLAYER_ROUTE)
assert_eq(note.need_text, "The Village Pantry needs food")
assert_true(note.help_text.contains("You can help"))
assert_true(note.help_text.contains("Village Pantry"))
func test_helper_is_named_when_no_player_route() -> void:
var opportunity := _open_pantry_opportunity()
var pantry := _pantry(0.0, 100.0)
var note := VillagerOpportunityNote.derive(
opportunity, null, _helper(7), "Tarik", pantry, "Village Pantry", "food"
)
assert_true(note.has_need)
assert_eq(note.help_kind, VillagerOpportunityNote.HELP_HELPER)
assert_true(note.help_text.contains("Tarik"))
assert_true(note.help_text.contains("food"))
func test_unavailable_reports_no_room_when_storage_is_full() -> void:
var opportunity := _open_pantry_opportunity()
var pantry := _pantry(0.0, 0.5)
var note := VillagerOpportunityNote.derive(
opportunity, null, null, "", pantry, "Village Pantry", "food"
)
assert_true(note.has_need)
assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE)
assert_true(note.help_text.contains("no room"))
func test_unavailable_reports_need_already_met() -> void:
var opportunity := _open_pantry_opportunity()
var pantry := _pantry(1.0, 100.0)
var note := VillagerOpportunityNote.derive(
opportunity, null, null, "", pantry, "Village Pantry", "food"
)
assert_true(note.has_need)
assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE)
assert_true(note.help_text.contains("already has enough"))
func test_unavailable_reports_no_stocked_source_when_storage_has_room() -> void:
var opportunity := _open_pantry_opportunity()
var pantry := _pantry(0.0, 100.0)
var note := VillagerOpportunityNote.derive(
opportunity, null, null, "", pantry, "Village Pantry", "food"
)
assert_true(note.has_need)
assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE)
assert_true(note.help_text.contains("No stocked source"))
func test_unavailable_reports_missing_storage() -> void:
var opportunity := _open_pantry_opportunity()
var note := VillagerOpportunityNote.derive(
opportunity, null, null, "", null, "Village Pantry", "food"
)
assert_true(note.has_need)
assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE)
assert_true(note.help_text.contains("No storage"))
func test_cache_key_uses_need_and_help_state() -> void:
var opportunity := _open_pantry_opportunity()
var pantry := _pantry(0.0, 100.0)
var helper_note := VillagerOpportunityNote.derive(
opportunity, null, _helper(7), "Tarik", pantry, "Village Pantry", "food"
)
var unavailable_note := VillagerOpportunityNote.derive(
opportunity, null, null, "", pantry, "Village Pantry", "food"
)
var no_need := VillagerOpportunityNote.derive(null, null, null, "", null, "", "")
assert_ne(helper_note.cache_key(), unavailable_note.cache_key())
assert_ne(no_need.cache_key(), helper_note.cache_key())
func _open_pantry_opportunity() -> OpportunityStateRecord:
return OpportunityStateRecord.create(
0,
10,
5,
3,
1.0,
SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY,
SimulationIds.STORAGE_VILLAGE_PANTRY,
SimulationIds.RESOURCE_FOOD
)
func _player_response() -> OpportunityPlayerResponseResult:
return (
OpportunityPlayerResponseResult
. new(
{
"opportunity_id": 0,
"trigger_event_id": 5,
"action_id": SimulationIds.ACTION_GATHER_FOOD,
"resource_id": SimulationIds.RESOURCE_FOOD,
"target_id": SimulationIds.STORAGE_VILLAGE_PANTRY,
"available_source_count": 2,
"reason":
"No capable helper; 2 player-usable finite Food sources can supply village_pantry",
}
)
)
func _helper(helper_id: int) -> OpportunityHelperResult:
return (
OpportunityHelperResult
. new(
{
"opportunity_id": 0,
"helper_npc_id": helper_id,
"action_id": SimulationIds.ACTION_GATHER_FOOD,
"source_id": "",
"resource_id": SimulationIds.RESOURCE_FOOD,
"trigger_event_id": 5,
"trust": 0.8,
"familiarity": 0.5,
"uses_inventory": false,
"available_source_count": 1,
"profession_match": true,
"reason":
"Knows the need; trust 0.80 toward Amina; has 1 available finite Food source",
}
)
)
func _pantry(amount: float, capacity: float) -> StorageStateRecord:
return StorageStateRecord.create(
SimulationIds.STORAGE_VILLAGE_PANTRY, {SimulationIds.RESOURCE_FOOD: amount}, capacity
)
@@ -0,0 +1 @@
uid://1chfro4iw1o5