Files
Genarrative/server-rs/crates/module-combat/src/lib.rs

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);
}
}