171 lines
6.0 KiB
Rust
171 lines
6.0 KiB
Rust
//! 战斗写入命令。
|
|
//!
|
|
//! 用于表达创建战斗、使用技能、逃离和结算等输入,不直接携带表行类型。
|
|
|
|
use crate::domain::{BattleMode, LEGACY_ATTACK_FUNCTION_IDS};
|
|
use crate::errors::CombatFieldError;
|
|
use module_runtime_item::{
|
|
RuntimeItemRewardItemSnapshot, TreasureFieldError, normalize_reward_item_snapshot,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use shared_kernel::normalize_required_string;
|
|
#[cfg(feature = "spacetime-types")]
|
|
use spacetimedb::SpacetimeType;
|
|
|
|
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct BattleStateInput {
|
|
pub battle_state_id: String,
|
|
pub story_session_id: String,
|
|
pub runtime_session_id: String,
|
|
pub actor_user_id: String,
|
|
pub chapter_id: Option<String>,
|
|
pub target_npc_id: String,
|
|
pub target_name: String,
|
|
pub battle_mode: BattleMode,
|
|
pub player_hp: i32,
|
|
pub player_max_hp: i32,
|
|
pub player_mana: i32,
|
|
pub player_max_mana: i32,
|
|
pub target_hp: i32,
|
|
pub target_max_hp: i32,
|
|
pub experience_reward: u32,
|
|
pub reward_items: Vec<RuntimeItemRewardItemSnapshot>,
|
|
pub created_at_micros: i64,
|
|
}
|
|
|
|
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct ResolveCombatActionInput {
|
|
pub battle_state_id: String,
|
|
pub function_id: String,
|
|
pub action_text: String,
|
|
pub base_damage: i32,
|
|
pub mana_cost: i32,
|
|
pub heal: i32,
|
|
pub mana_restore: i32,
|
|
pub counter_multiplier_basis_points: u32,
|
|
pub updated_at_micros: i64,
|
|
}
|
|
|
|
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct BattleStateQueryInput {
|
|
pub battle_state_id: String,
|
|
}
|
|
|
|
pub fn validate_battle_state_input(input: &BattleStateInput) -> Result<(), CombatFieldError> {
|
|
if normalize_required_string(&input.battle_state_id).is_none() {
|
|
return Err(CombatFieldError::MissingBattleStateId);
|
|
}
|
|
if normalize_required_string(&input.story_session_id).is_none() {
|
|
return Err(CombatFieldError::MissingStorySessionId);
|
|
}
|
|
if normalize_required_string(&input.runtime_session_id).is_none() {
|
|
return Err(CombatFieldError::MissingRuntimeSessionId);
|
|
}
|
|
if normalize_required_string(&input.actor_user_id).is_none() {
|
|
return Err(CombatFieldError::MissingActorUserId);
|
|
}
|
|
if normalize_required_string(&input.target_npc_id).is_none() {
|
|
return Err(CombatFieldError::MissingTargetNpcId);
|
|
}
|
|
if normalize_required_string(&input.target_name).is_none() {
|
|
return Err(CombatFieldError::MissingTargetName);
|
|
}
|
|
if input.player_max_hp <= 0 || input.player_hp <= 0 || input.player_hp > input.player_max_hp {
|
|
return Err(CombatFieldError::InvalidPlayerVitals);
|
|
}
|
|
if input.player_max_mana < 0
|
|
|| input.player_mana < 0
|
|
|| input.player_mana > input.player_max_mana
|
|
{
|
|
return Err(CombatFieldError::InvalidPlayerVitals);
|
|
}
|
|
if input.target_max_hp <= 0 || input.target_hp <= 0 || input.target_hp > input.target_max_hp {
|
|
return Err(CombatFieldError::InvalidTargetVitals);
|
|
}
|
|
for reward_item in input.reward_items.iter().cloned() {
|
|
normalize_reward_item_snapshot(reward_item).map_err(map_reward_item_field_error)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn validate_resolve_combat_action_input(
|
|
input: &ResolveCombatActionInput,
|
|
) -> Result<(), CombatFieldError> {
|
|
if normalize_required_string(&input.battle_state_id).is_none() {
|
|
return Err(CombatFieldError::MissingBattleStateId);
|
|
}
|
|
if normalize_required_string(&input.function_id).is_none() {
|
|
return Err(CombatFieldError::MissingFunctionId);
|
|
}
|
|
if !is_supported_combat_function_id(&input.function_id) {
|
|
return Err(CombatFieldError::UnsupportedFunctionId);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn build_battle_state_query_input(
|
|
battle_state_id: String,
|
|
) -> Result<BattleStateQueryInput, CombatFieldError> {
|
|
let input = BattleStateQueryInput {
|
|
battle_state_id: normalize_required_string(battle_state_id).unwrap_or_default(),
|
|
};
|
|
|
|
validate_battle_state_query_input(&input)?;
|
|
|
|
Ok(input)
|
|
}
|
|
|
|
pub fn validate_battle_state_query_input(
|
|
input: &BattleStateQueryInput,
|
|
) -> Result<(), CombatFieldError> {
|
|
if normalize_required_string(&input.battle_state_id).is_none() {
|
|
return Err(CombatFieldError::MissingBattleStateId);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn is_supported_combat_function_id(function_id: &str) -> bool {
|
|
matches!(
|
|
function_id,
|
|
"battle_attack_basic"
|
|
| "battle_recover_breath"
|
|
| "battle_use_skill"
|
|
| "battle_escape_breakout"
|
|
) || LEGACY_ATTACK_FUNCTION_IDS.contains(&function_id)
|
|
}
|
|
|
|
fn map_reward_item_field_error(error: TreasureFieldError) -> CombatFieldError {
|
|
let message = match error {
|
|
TreasureFieldError::MissingRewardItemId => {
|
|
"battle_state.reward_items[].item_id 不能为空".to_string()
|
|
}
|
|
TreasureFieldError::MissingRewardItemCategory => {
|
|
"battle_state.reward_items[].category 不能为空".to_string()
|
|
}
|
|
TreasureFieldError::MissingRewardItemName => {
|
|
"battle_state.reward_items[].item_name 不能为空".to_string()
|
|
}
|
|
TreasureFieldError::InvalidRewardItemQuantity => {
|
|
"battle_state.reward_items[].quantity 必须大于 0".to_string()
|
|
}
|
|
TreasureFieldError::MissingRewardItemStackKey => {
|
|
"battle_state.reward_items[].stack_key 不能为空".to_string()
|
|
}
|
|
TreasureFieldError::RewardEquipmentItemCannotStack => {
|
|
"battle_state.reward_items[] 可装备物品不能标记为 stackable".to_string()
|
|
}
|
|
TreasureFieldError::RewardNonStackableItemMustStaySingleQuantity => {
|
|
"battle_state.reward_items[] 不可堆叠物品必须固定为单槽位单数量".to_string()
|
|
}
|
|
other => other.to_string(),
|
|
};
|
|
|
|
CombatFieldError::InvalidRewardItem(message)
|
|
}
|