262 lines
9.1 KiB
Rust
262 lines
9.1 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::*;
|
|
use module_runtime_item::RuntimeItemRewardItemSnapshot;
|
|
|
|
fn build_fight_snapshot() -> BattleStateSnapshot {
|
|
build_battle_state_snapshot(BattleStateInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
story_session_id: "storysess_001".to_string(),
|
|
runtime_session_id: "runtime_001".to_string(),
|
|
actor_user_id: "user_001".to_string(),
|
|
chapter_id: Some("chapter_001".to_string()),
|
|
target_npc_id: "npc_001".to_string(),
|
|
target_name: "黑爪狼".to_string(),
|
|
battle_mode: BattleMode::Fight,
|
|
player_hp: 60,
|
|
player_max_hp: 60,
|
|
player_mana: 20,
|
|
player_max_mana: 20,
|
|
target_hp: 30,
|
|
target_max_hp: 30,
|
|
experience_reward: 18,
|
|
reward_items: vec![],
|
|
created_at_micros: 10,
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn validate_battle_state_input_accepts_minimal_contract() {
|
|
let result = validate_battle_state_input(&BattleStateInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
story_session_id: "storysess_001".to_string(),
|
|
runtime_session_id: "runtime_001".to_string(),
|
|
actor_user_id: "user_001".to_string(),
|
|
chapter_id: Some("chapter_001".to_string()),
|
|
target_npc_id: "npc_001".to_string(),
|
|
target_name: "黑爪狼".to_string(),
|
|
battle_mode: BattleMode::Fight,
|
|
player_hp: 50,
|
|
player_max_hp: 60,
|
|
player_mana: 10,
|
|
player_max_mana: 20,
|
|
target_hp: 30,
|
|
target_max_hp: 30,
|
|
experience_reward: 12,
|
|
reward_items: vec![],
|
|
created_at_micros: 1,
|
|
});
|
|
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn validate_battle_state_input_rejects_invalid_reward_items() {
|
|
let error = validate_battle_state_input(&BattleStateInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
story_session_id: "storysess_001".to_string(),
|
|
runtime_session_id: "runtime_001".to_string(),
|
|
actor_user_id: "user_001".to_string(),
|
|
chapter_id: Some("chapter_001".to_string()),
|
|
target_npc_id: "npc_001".to_string(),
|
|
target_name: "黑爪狼".to_string(),
|
|
battle_mode: BattleMode::Fight,
|
|
player_hp: 50,
|
|
player_max_hp: 60,
|
|
player_mana: 10,
|
|
player_max_mana: 20,
|
|
target_hp: 30,
|
|
target_max_hp: 30,
|
|
experience_reward: 12,
|
|
reward_items: vec![RuntimeItemRewardItemSnapshot {
|
|
item_id: String::new(),
|
|
category: "遗物".to_string(),
|
|
item_name: "铜钥残片".to_string(),
|
|
description: None,
|
|
quantity: 1,
|
|
rarity: module_runtime_item::RuntimeItemRewardItemRarity::Rare,
|
|
tags: vec![],
|
|
stackable: false,
|
|
stack_key: String::new(),
|
|
equipment_slot_id: None,
|
|
}],
|
|
created_at_micros: 1,
|
|
})
|
|
.expect_err("invalid reward item should be rejected");
|
|
|
|
assert_eq!(
|
|
error,
|
|
CombatFieldError::InvalidRewardItem(
|
|
"battle_state.reward_items[].item_id 不能为空".to_string()
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn build_battle_state_query_input_trims_and_validates_id() {
|
|
let input = build_battle_state_query_input(" battle_001 ".to_string())
|
|
.expect("query input should build");
|
|
|
|
assert_eq!(input.battle_state_id, "battle_001");
|
|
}
|
|
|
|
#[test]
|
|
fn build_battle_state_query_input_rejects_empty_id() {
|
|
let error =
|
|
build_battle_state_query_input(" ".to_string()).expect_err("empty id should fail");
|
|
|
|
assert_eq!(error, CombatFieldError::MissingBattleStateId);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_basic_attack_advances_turn_and_applies_counter_damage() {
|
|
let result = resolve_combat_action(
|
|
build_fight_snapshot(),
|
|
ResolveCombatActionInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
function_id: "battle_attack_basic".to_string(),
|
|
action_text: "普通攻击".to_string(),
|
|
base_damage: 10,
|
|
mana_cost: 0,
|
|
heal: 0,
|
|
mana_restore: 0,
|
|
counter_multiplier_basis_points: 10_000,
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect("basic attack should succeed");
|
|
|
|
assert_eq!(result.snapshot.turn_index, 1);
|
|
assert_eq!(result.snapshot.target_hp, 20);
|
|
assert_eq!(result.snapshot.player_hp, 56);
|
|
assert_eq!(result.snapshot.last_damage_dealt, 10);
|
|
assert_eq!(result.snapshot.last_damage_taken, 4);
|
|
assert_eq!(result.outcome, CombatOutcome::Ongoing);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_escape_marks_battle_resolved() {
|
|
let result = resolve_combat_action(
|
|
build_fight_snapshot(),
|
|
ResolveCombatActionInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
function_id: "battle_escape_breakout".to_string(),
|
|
action_text: "逃跑".to_string(),
|
|
base_damage: 0,
|
|
mana_cost: 0,
|
|
heal: 0,
|
|
mana_restore: 0,
|
|
counter_multiplier_basis_points: 0,
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect("escape should succeed");
|
|
|
|
assert_eq!(result.snapshot.status, BattleStatus::Resolved);
|
|
assert_eq!(result.snapshot.last_outcome, CombatOutcome::Escaped);
|
|
assert_eq!(result.damage_dealt, 0);
|
|
assert_eq!(result.damage_taken, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_skill_can_finish_fight() {
|
|
let result = resolve_combat_action(
|
|
build_fight_snapshot(),
|
|
ResolveCombatActionInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
function_id: "battle_use_skill".to_string(),
|
|
action_text: "试锋斩".to_string(),
|
|
base_damage: 35,
|
|
mana_cost: 8,
|
|
heal: 0,
|
|
mana_restore: 0,
|
|
counter_multiplier_basis_points: 9_500,
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect("skill should succeed");
|
|
|
|
assert_eq!(result.snapshot.status, BattleStatus::Resolved);
|
|
assert_eq!(result.snapshot.target_hp, 0);
|
|
assert_eq!(result.snapshot.player_mana, 12);
|
|
assert_eq!(result.outcome, CombatOutcome::Victory);
|
|
assert_eq!(result.damage_taken, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn spar_mode_keeps_hp_floor_at_one() {
|
|
let snapshot = build_battle_state_snapshot(BattleStateInput {
|
|
battle_state_id: "battle_002".to_string(),
|
|
story_session_id: "storysess_001".to_string(),
|
|
runtime_session_id: "runtime_001".to_string(),
|
|
actor_user_id: "user_001".to_string(),
|
|
chapter_id: Some("chapter_spar".to_string()),
|
|
target_npc_id: "npc_002".to_string(),
|
|
target_name: "卫队长".to_string(),
|
|
battle_mode: BattleMode::Spar,
|
|
player_hp: 5,
|
|
player_max_hp: 5,
|
|
player_mana: 10,
|
|
player_max_mana: 10,
|
|
target_hp: 3,
|
|
target_max_hp: 3,
|
|
experience_reward: 0,
|
|
reward_items: vec![],
|
|
created_at_micros: 10,
|
|
});
|
|
|
|
let result = resolve_combat_action(
|
|
snapshot,
|
|
ResolveCombatActionInput {
|
|
battle_state_id: "battle_002".to_string(),
|
|
function_id: "battle_attack_basic".to_string(),
|
|
action_text: "普通攻击".to_string(),
|
|
base_damage: 5,
|
|
mana_cost: 0,
|
|
heal: 0,
|
|
mana_restore: 0,
|
|
counter_multiplier_basis_points: 10_000,
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect("spar attack should succeed");
|
|
|
|
assert_eq!(result.snapshot.target_hp, 1);
|
|
assert_eq!(result.snapshot.status, BattleStatus::Resolved);
|
|
assert_eq!(result.outcome, CombatOutcome::SparComplete);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_rejects_unsupported_function() {
|
|
let error = resolve_combat_action(
|
|
build_fight_snapshot(),
|
|
ResolveCombatActionInput {
|
|
battle_state_id: "battle_001".to_string(),
|
|
function_id: "inventory_use".to_string(),
|
|
action_text: "使用物品".to_string(),
|
|
base_damage: 0,
|
|
mana_cost: 0,
|
|
heal: 0,
|
|
mana_restore: 0,
|
|
counter_multiplier_basis_points: 7_200,
|
|
updated_at_micros: 20,
|
|
},
|
|
)
|
|
.expect_err("inventory_use should be deferred for now");
|
|
|
|
assert_eq!(error, CombatFieldError::UnsupportedFunctionId);
|
|
}
|
|
}
|