260 lines
10 KiB
Rust
260 lines
10 KiB
Rust
mod application;
|
|
mod commands;
|
|
mod domain;
|
|
mod errors;
|
|
mod events;
|
|
|
|
pub use application::*;
|
|
pub use commands::*;
|
|
pub use domain::*;
|
|
pub use errors::*;
|
|
pub use events::*;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn build_test_reward() -> QuestRewardSnapshot {
|
|
QuestRewardSnapshot {
|
|
affinity_bonus: 12,
|
|
currency: 72,
|
|
experience: Some(35),
|
|
items: vec![QuestRewardItem {
|
|
item_id: "reward_item_01".to_string(),
|
|
category: "补给".to_string(),
|
|
name: "常备药包".to_string(),
|
|
description: Some("带着草药苦味的便携补给。".to_string()),
|
|
quantity: 1,
|
|
rarity: QuestRewardItemRarity::Rare,
|
|
tags: vec!["healing".to_string()],
|
|
stackable: true,
|
|
stack_key: "reward_item_01".to_string(),
|
|
equipment_slot_id: None,
|
|
}],
|
|
intel: Some(QuestRewardIntel {
|
|
rumor_text: "旧桥下面还埋着另一条线索。".to_string(),
|
|
unlocked_scene_id: Some("scene_old_bridge".to_string()),
|
|
}),
|
|
story_hint: Some("委托人的态度明显缓和了下来。".to_string()),
|
|
}
|
|
}
|
|
|
|
fn build_test_binding() -> QuestNarrativeBindingSnapshot {
|
|
QuestNarrativeBindingSnapshot {
|
|
origin: QuestNarrativeOrigin::FallbackBuilder,
|
|
narrative_type: QuestNarrativeType::Investigation,
|
|
dramatic_need: "委托人需要先确认遗迹异动是真是假。".to_string(),
|
|
issuer_goal: "摸清遗迹周边的情况。".to_string(),
|
|
player_hook: "玩家正好就在现场。".to_string(),
|
|
world_reason: "最近的异常都收束到了这片区域。".to_string(),
|
|
followup_hooks: vec!["遗迹后方还有更深的入口".to_string()],
|
|
}
|
|
}
|
|
|
|
fn build_test_steps() -> Vec<QuestStepSnapshot> {
|
|
vec![
|
|
QuestStepSnapshot {
|
|
step_id: "step_investigate".to_string(),
|
|
kind: QuestObjectiveKind::InspectTreasure,
|
|
target_hostile_npc_id: None,
|
|
target_npc_id: None,
|
|
target_scene_id: Some("scene_ruins".to_string()),
|
|
target_item_id: None,
|
|
required_count: 1,
|
|
progress: 0,
|
|
title: "调查遗迹".to_string(),
|
|
reveal_text: "先去把遗迹边缘的异动看清楚。".to_string(),
|
|
complete_text: "遗迹调查已经完成。".to_string(),
|
|
},
|
|
QuestStepSnapshot {
|
|
step_id: "step_report_back".to_string(),
|
|
kind: QuestObjectiveKind::TalkToNpc,
|
|
target_hostile_npc_id: None,
|
|
target_npc_id: Some("npc_scholar_lin".to_string()),
|
|
target_scene_id: None,
|
|
target_item_id: None,
|
|
required_count: 1,
|
|
progress: 0,
|
|
title: "回去汇报".to_string(),
|
|
reveal_text: "回去把结果告诉林朔。".to_string(),
|
|
complete_text: "林朔已经收到了你的回报。".to_string(),
|
|
},
|
|
]
|
|
}
|
|
|
|
fn build_test_record() -> QuestRecordSnapshot {
|
|
build_quest_record_snapshot(QuestRecordInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
runtime_session_id: "runtime_001".to_string(),
|
|
story_session_id: Some("storysess_001".to_string()),
|
|
actor_user_id: "user_001".to_string(),
|
|
issuer_npc_id: "npc_scholar_lin".to_string(),
|
|
issuer_npc_name: "林朔".to_string(),
|
|
scene_id: Some("scene_ruins".to_string()),
|
|
chapter_id: Some("chapter_01".to_string()),
|
|
act_id: Some("act_01".to_string()),
|
|
thread_id: Some("thread_ruins".to_string()),
|
|
contract_id: Some("contract_01".to_string()),
|
|
title: "遗迹异动".to_string(),
|
|
description: "林朔希望你先去确认遗迹外缘的异常。".to_string(),
|
|
summary: "调查遗迹异动,再回去汇报".to_string(),
|
|
status: QuestStatus::Active,
|
|
completion_notified: false,
|
|
reward: build_test_reward(),
|
|
reward_text: "完成后可获得赏金、补给和线索。".to_string(),
|
|
narrative_binding: build_test_binding(),
|
|
steps: build_test_steps(),
|
|
active_step_id: Some("step_investigate".to_string()),
|
|
visible_stage: 0,
|
|
hidden_flags: vec!["ruins".to_string()],
|
|
discovered_fact_ids: vec![],
|
|
related_carrier_ids: vec![],
|
|
consequence_ids: vec![],
|
|
created_at_micros: 1_713_680_000_000_000,
|
|
})
|
|
.expect("test quest record should build")
|
|
}
|
|
|
|
#[test]
|
|
fn build_quest_record_snapshot_rejects_empty_steps() {
|
|
let error = build_quest_record_snapshot(QuestRecordInput {
|
|
quest_id: "quest_001".to_string(),
|
|
runtime_session_id: "runtime_001".to_string(),
|
|
story_session_id: None,
|
|
actor_user_id: "user_001".to_string(),
|
|
issuer_npc_id: "npc_001".to_string(),
|
|
issuer_npc_name: "林朔".to_string(),
|
|
scene_id: None,
|
|
chapter_id: None,
|
|
act_id: None,
|
|
thread_id: None,
|
|
contract_id: None,
|
|
title: "遗迹异动".to_string(),
|
|
description: "测试".to_string(),
|
|
summary: String::new(),
|
|
status: QuestStatus::Active,
|
|
completion_notified: false,
|
|
reward: build_test_reward(),
|
|
reward_text: "完成后可获得赏金。".to_string(),
|
|
narrative_binding: build_test_binding(),
|
|
steps: vec![],
|
|
active_step_id: None,
|
|
visible_stage: 0,
|
|
hidden_flags: vec![],
|
|
discovered_fact_ids: vec![],
|
|
related_carrier_ids: vec![],
|
|
consequence_ids: vec![],
|
|
created_at_micros: 1,
|
|
})
|
|
.expect_err("empty steps should fail");
|
|
|
|
assert_eq!(error, QuestRecordFieldError::EmptySteps);
|
|
}
|
|
|
|
#[test]
|
|
fn apply_quest_signal_advances_only_current_active_step() {
|
|
let current = build_test_record();
|
|
let outcome = apply_quest_signal(
|
|
current,
|
|
QuestSignalApplyInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
signal: QuestProgressSignal::TreasureInspected(QuestTreasureInspectedSignal {
|
|
scene_id: Some("scene_ruins".to_string()),
|
|
}),
|
|
updated_at_micros: 1_713_680_000_100_000,
|
|
},
|
|
)
|
|
.expect("signal apply should succeed");
|
|
|
|
assert!(outcome.changed);
|
|
assert!(!outcome.completed_now);
|
|
assert_eq!(outcome.next_record.status, QuestStatus::Active);
|
|
assert_eq!(
|
|
outcome.next_record.active_step_id.as_deref(),
|
|
Some("step_report_back")
|
|
);
|
|
assert_eq!(outcome.changed_step_id.as_deref(), Some("step_investigate"));
|
|
assert_eq!(outcome.changed_step_progress, Some(1));
|
|
}
|
|
|
|
#[test]
|
|
fn apply_quest_signal_marks_completed_when_last_step_finishes() {
|
|
let current = apply_quest_signal(
|
|
build_test_record(),
|
|
QuestSignalApplyInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
signal: QuestProgressSignal::TreasureInspected(QuestTreasureInspectedSignal {
|
|
scene_id: Some("scene_ruins".to_string()),
|
|
}),
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect("first step should succeed")
|
|
.next_record;
|
|
|
|
let outcome = apply_quest_signal(
|
|
current,
|
|
QuestSignalApplyInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
signal: QuestProgressSignal::NpcTalkCompleted(QuestNpcTalkCompletedSignal {
|
|
npc_id: "npc_scholar_lin".to_string(),
|
|
}),
|
|
updated_at_micros: 30,
|
|
},
|
|
)
|
|
.expect("last step should complete");
|
|
|
|
assert!(outcome.changed);
|
|
assert!(outcome.completed_now);
|
|
assert_eq!(outcome.next_record.status, QuestStatus::Completed);
|
|
assert_eq!(outcome.next_record.active_step_id, None);
|
|
assert_eq!(outcome.next_record.completed_at_micros, Some(30));
|
|
}
|
|
|
|
#[test]
|
|
fn turn_in_quest_record_moves_status_to_turned_in() {
|
|
let completed = apply_quest_signal(
|
|
apply_quest_signal(
|
|
build_test_record(),
|
|
QuestSignalApplyInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
signal: QuestProgressSignal::TreasureInspected(QuestTreasureInspectedSignal {
|
|
scene_id: Some("scene_ruins".to_string()),
|
|
}),
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect("first step should succeed")
|
|
.next_record,
|
|
QuestSignalApplyInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
signal: QuestProgressSignal::NpcTalkCompleted(QuestNpcTalkCompletedSignal {
|
|
npc_id: "npc_scholar_lin".to_string(),
|
|
}),
|
|
updated_at_micros: 30,
|
|
},
|
|
)
|
|
.expect("second step should succeed")
|
|
.next_record;
|
|
|
|
let turned_in = turn_in_quest_record(
|
|
completed,
|
|
QuestTurnInInput {
|
|
quest_id: "quest:npc_scholar_lin:inspect_treasure:scene_ruins".to_string(),
|
|
turned_in_at_micros: 40,
|
|
},
|
|
)
|
|
.expect("completed quest should turn in");
|
|
|
|
assert_eq!(turned_in.status, QuestStatus::TurnedIn);
|
|
assert_eq!(turned_in.turned_in_at_micros, Some(40));
|
|
assert!(turned_in.completion_notified);
|
|
assert!(
|
|
turned_in
|
|
.steps
|
|
.iter()
|
|
.all(|step| step.progress == step.required_count)
|
|
);
|
|
}
|
|
}
|