use super::*; impl From for BattleStateInput { fn from(input: DomainBattleStateInput) -> Self { Self { battle_state_id: input.battle_state_id, story_session_id: input.story_session_id, runtime_session_id: input.runtime_session_id, actor_user_id: input.actor_user_id, chapter_id: input.chapter_id, target_npc_id: input.target_npc_id, target_name: input.target_name, battle_mode: map_battle_mode(input.battle_mode), player_hp: input.player_hp, player_max_hp: input.player_max_hp, player_mana: input.player_mana, player_max_mana: input.player_max_mana, target_hp: input.target_hp, target_max_hp: input.target_max_hp, experience_reward: input.experience_reward, reward_items: input .reward_items .into_iter() .map(map_runtime_item_reward_item_snapshot) .collect(), created_at_micros: input.created_at_micros, } } } pub(crate) fn map_npc_battle_interaction_procedure_result( result: NpcBattleInteractionProcedureResult, ) -> Result { if !result.ok { return Err(SpacetimeClientError::procedure_failed(result.error_message)); } let interaction_result = result .result .ok_or_else(|| SpacetimeClientError::missing_snapshot("NPC 开战结果"))?; Ok(build_npc_battle_interaction_record( map_npc_battle_interaction_result(interaction_result), )) } pub(crate) fn map_battle_state_snapshot( snapshot: BattleStateSnapshot, ) -> DomainBattleStateSnapshot { DomainBattleStateSnapshot { battle_state_id: snapshot.battle_state_id, story_session_id: snapshot.story_session_id, runtime_session_id: snapshot.runtime_session_id, actor_user_id: snapshot.actor_user_id, chapter_id: snapshot.chapter_id, target_npc_id: snapshot.target_npc_id, target_name: snapshot.target_name, battle_mode: map_battle_mode_back(snapshot.battle_mode), status: map_battle_status(snapshot.status), player_hp: snapshot.player_hp, player_max_hp: snapshot.player_max_hp, player_mana: snapshot.player_mana, player_max_mana: snapshot.player_max_mana, target_hp: snapshot.target_hp, target_max_hp: snapshot.target_max_hp, experience_reward: snapshot.experience_reward, reward_items: snapshot .reward_items .into_iter() .map(map_runtime_item_reward_item_snapshot_back) .collect(), turn_index: snapshot.turn_index, last_action_function_id: snapshot.last_action_function_id, last_action_text: snapshot.last_action_text, last_result_text: snapshot.last_result_text, last_damage_dealt: snapshot.last_damage_dealt, last_damage_taken: snapshot.last_damage_taken, last_outcome: map_combat_outcome(snapshot.last_outcome), version: snapshot.version, created_at_micros: snapshot.created_at_micros, updated_at_micros: snapshot.updated_at_micros, } } pub(crate) fn map_npc_battle_interaction_result( result: NpcBattleInteractionResult, ) -> NpcBattleInteractionSnapshot { NpcBattleInteractionSnapshot { interaction: map_npc_interaction_result(result.interaction), battle_state: map_battle_state_snapshot(result.battle_state), } } pub(crate) fn map_npc_interaction_result( result: NpcInteractionResult, ) -> DomainNpcInteractionResult { DomainNpcInteractionResult { npc_state: map_npc_state_snapshot(result.npc_state), interaction_status: map_npc_interaction_status(result.interaction_status), action_text: result.action_text, result_text: result.result_text, story_text: result.story_text, battle_mode: result.battle_mode.map(map_npc_interaction_battle_mode), encounter_closed: result.encounter_closed, affinity_changed: result.affinity_changed, previous_affinity: result.previous_affinity, next_affinity: result.next_affinity, } } pub(crate) fn map_npc_state_snapshot(snapshot: NpcStateSnapshot) -> DomainNpcStateSnapshot { DomainNpcStateSnapshot { npc_state_id: snapshot.npc_state_id, runtime_session_id: snapshot.runtime_session_id, npc_id: snapshot.npc_id, npc_name: snapshot.npc_name, affinity: snapshot.affinity, relation_state: map_npc_relation_state(snapshot.relation_state), help_used: snapshot.help_used, chatted_count: snapshot.chatted_count, gifts_given: snapshot.gifts_given, recruited: snapshot.recruited, trade_stock_signature: snapshot.trade_stock_signature, revealed_facts: snapshot.revealed_facts, known_attribute_rumors: snapshot.known_attribute_rumors, first_meaningful_contact_resolved: snapshot.first_meaningful_contact_resolved, seen_backstory_chapter_ids: snapshot.seen_backstory_chapter_ids, stance_profile: map_npc_stance_profile(snapshot.stance_profile), created_at_micros: snapshot.created_at_micros, updated_at_micros: snapshot.updated_at_micros, } } pub(crate) fn map_npc_relation_state(value: NpcRelationState) -> DomainNpcRelationState { DomainNpcRelationState { affinity: value.affinity, stance: map_npc_relation_stance(value.stance), } } pub(crate) fn map_npc_stance_profile(value: NpcStanceProfile) -> DomainNpcStanceProfile { DomainNpcStanceProfile { trust: value.trust, warmth: value.warmth, ideological_fit: value.ideological_fit, fear_or_guard: value.fear_or_guard, loyalty: value.loyalty, current_conflict_tag: value.current_conflict_tag, recent_approvals: value.recent_approvals, recent_disapprovals: value.recent_disapprovals, } } pub(crate) fn map_npc_interaction_status( value: NpcInteractionStatus, ) -> DomainNpcInteractionStatus { match value { NpcInteractionStatus::Previewed => DomainNpcInteractionStatus::Previewed, NpcInteractionStatus::Dialogue => DomainNpcInteractionStatus::Dialogue, NpcInteractionStatus::Resolved => DomainNpcInteractionStatus::Resolved, NpcInteractionStatus::Recruited => DomainNpcInteractionStatus::Recruited, NpcInteractionStatus::BattlePending => DomainNpcInteractionStatus::BattlePending, NpcInteractionStatus::Left => DomainNpcInteractionStatus::Left, } } pub(crate) fn map_npc_interaction_battle_mode( value: NpcInteractionBattleMode, ) -> DomainNpcInteractionBattleMode { match value { NpcInteractionBattleMode::Fight => DomainNpcInteractionBattleMode::Fight, NpcInteractionBattleMode::Spar => DomainNpcInteractionBattleMode::Spar, } } pub(crate) fn map_npc_relation_stance(value: NpcRelationStance) -> DomainNpcRelationStance { match value { NpcRelationStance::Hostile => DomainNpcRelationStance::Hostile, NpcRelationStance::Guarded => DomainNpcRelationStance::Guarded, NpcRelationStance::Neutral => DomainNpcRelationStance::Neutral, NpcRelationStance::Cooperative => DomainNpcRelationStance::Cooperative, NpcRelationStance::Bonded => DomainNpcRelationStance::Bonded, } } pub(crate) fn map_ai_task_kind(value: DomainAiTaskKind) -> AiTaskKind { match value { DomainAiTaskKind::StoryGeneration => AiTaskKind::StoryGeneration, DomainAiTaskKind::CharacterChat => AiTaskKind::CharacterChat, DomainAiTaskKind::NpcChat => AiTaskKind::NpcChat, DomainAiTaskKind::CustomWorldGeneration => AiTaskKind::CustomWorldGeneration, DomainAiTaskKind::QuestIntent => AiTaskKind::QuestIntent, DomainAiTaskKind::RuntimeItemIntent => AiTaskKind::RuntimeItemIntent, } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct BattleStateRecord { pub battle_state_id: String, pub story_session_id: String, pub runtime_session_id: String, pub actor_user_id: String, pub chapter_id: Option, pub target_npc_id: String, pub target_name: String, pub battle_mode: String, pub status: String, 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, pub turn_index: u32, pub last_action_function_id: Option, pub last_action_text: Option, pub last_result_text: Option, pub last_damage_dealt: i32, pub last_damage_taken: i32, pub last_outcome: String, pub version: u32, pub created_at: String, pub updated_at: String, } #[derive(Clone, Debug, PartialEq)] pub struct CustomWorldLibraryEntryRecord { pub owner_user_id: String, pub profile_id: String, pub public_work_code: Option, pub author_public_user_code: Option, pub profile: serde_json::Value, pub visibility: String, pub published_at: Option, pub updated_at: String, pub author_display_name: String, pub world_name: String, pub subtitle: String, pub summary_text: String, pub cover_image_src: Option, pub theme_mode: String, pub playable_npc_count: u32, pub landmark_count: u32, pub play_count: u32, pub remix_count: u32, pub like_count: u32, pub recent_play_count_7d: u32, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct CustomWorldGalleryEntryRecord { pub owner_user_id: String, pub profile_id: String, pub public_work_code: String, pub author_public_user_code: String, pub visibility: String, pub published_at: Option, pub updated_at: String, pub author_display_name: String, pub world_name: String, pub subtitle: String, pub summary_text: String, pub cover_image_src: Option, pub theme_mode: String, pub playable_npc_count: u32, pub landmark_count: u32, pub play_count: u32, pub remix_count: u32, pub like_count: u32, pub recent_play_count_7d: u32, } #[derive(Clone, Debug, PartialEq)] pub struct CustomWorldPublishedProfileCompileRecord { pub profile_id: String, pub owner_user_id: String, pub world_name: String, pub subtitle: String, pub summary_text: String, pub theme_mode: String, pub cover_image_src: Option, pub playable_npc_count: u32, pub landmark_count: u32, pub author_display_name: String, pub compiled_profile: serde_json::Value, pub updated_at: String, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct CustomWorldWorkSummaryRecord { pub work_id: String, pub source_type: String, pub status: String, pub title: String, pub subtitle: String, pub summary: String, pub cover_image_src: Option, pub cover_render_mode: Option, pub cover_character_image_srcs: Vec, pub updated_at: String, pub published_at: Option, pub stage: Option, pub stage_label: Option, pub playable_npc_count: u32, pub landmark_count: u32, pub role_visual_ready_count: Option, pub role_animation_ready_count: Option, pub role_asset_summary_label: Option, pub session_id: Option, pub profile_id: Option, pub can_resume: bool, pub can_enter_world: bool, pub blocker_count: u32, pub publish_ready: bool, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct CustomWorldProfileUpsertRecordInput { pub profile_id: String, pub owner_user_id: String, pub public_work_code: Option, pub author_public_user_code: Option, pub source_agent_session_id: Option, pub world_name: String, pub subtitle: String, pub summary_text: String, pub theme_mode: DomainCustomWorldThemeMode, pub cover_image_src: Option, pub profile_payload_json: String, pub playable_npc_count: u32, pub landmark_count: u32, pub author_display_name: String, pub updated_at_micros: i64, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResolveNpcBattleInteractionInput { pub npc_interaction: DomainResolveNpcInteractionInput, pub story_session_id: String, pub actor_user_id: String, pub battle_state_id: Option, 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, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct NpcStateRecord { pub npc_state_id: String, pub runtime_session_id: String, pub npc_id: String, pub npc_name: String, pub affinity: i32, pub relation_stance: String, pub help_used: bool, pub chatted_count: u32, pub gifts_given: u32, pub recruited: bool, pub trade_stock_signature: Option, pub revealed_facts: Vec, pub known_attribute_rumors: Vec, pub first_meaningful_contact_resolved: bool, pub seen_backstory_chapter_ids: Vec, pub trust: u8, pub warmth: u8, pub ideological_fit: u8, pub fear_or_guard: u8, pub loyalty: u8, pub current_conflict_tag: Option, pub recent_approvals: Vec, pub recent_disapprovals: Vec, pub created_at: String, pub updated_at: String, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct NpcInteractionRecord { pub npc_state: NpcStateRecord, pub interaction_status: String, pub action_text: String, pub result_text: String, pub story_text: Option, pub battle_mode: Option, pub encounter_closed: bool, pub affinity_changed: bool, pub previous_affinity: i32, pub next_affinity: i32, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct NpcBattleInteractionRecord { pub npc_interaction: NpcInteractionRecord, pub battle_state: BattleStateRecord, } #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct NpcBattleInteractionSnapshot { interaction: DomainNpcInteractionResult, battle_state: DomainBattleStateSnapshot, } pub(crate) fn build_battle_state_record(snapshot: DomainBattleStateSnapshot) -> BattleStateRecord { BattleStateRecord { battle_state_id: snapshot.battle_state_id, story_session_id: snapshot.story_session_id, runtime_session_id: snapshot.runtime_session_id, actor_user_id: snapshot.actor_user_id, chapter_id: snapshot.chapter_id, target_npc_id: snapshot.target_npc_id, target_name: snapshot.target_name, battle_mode: snapshot.battle_mode.as_str().to_string(), status: snapshot.status.as_str().to_string(), player_hp: snapshot.player_hp, player_max_hp: snapshot.player_max_hp, player_mana: snapshot.player_mana, player_max_mana: snapshot.player_max_mana, target_hp: snapshot.target_hp, target_max_hp: snapshot.target_max_hp, experience_reward: snapshot.experience_reward, reward_items: snapshot.reward_items, turn_index: snapshot.turn_index, last_action_function_id: snapshot.last_action_function_id, last_action_text: snapshot.last_action_text, last_result_text: snapshot.last_result_text, last_damage_dealt: snapshot.last_damage_dealt, last_damage_taken: snapshot.last_damage_taken, last_outcome: snapshot.last_outcome.as_str().to_string(), version: snapshot.version, created_at: format_timestamp_micros(snapshot.created_at_micros), updated_at: format_timestamp_micros(snapshot.updated_at_micros), } } impl From for crate::module_bindings::ResolveNpcBattleInteractionInput { fn from(input: ResolveNpcBattleInteractionInput) -> Self { Self { npc_interaction: crate::module_bindings::ResolveNpcInteractionInput { runtime_session_id: input.npc_interaction.runtime_session_id, npc_id: input.npc_interaction.npc_id, npc_name: input.npc_interaction.npc_name, interaction_function_id: input.npc_interaction.interaction_function_id, release_npc_id: input.npc_interaction.release_npc_id, updated_at_micros: input.npc_interaction.updated_at_micros, }, story_session_id: input.story_session_id, actor_user_id: input.actor_user_id, battle_state_id: input.battle_state_id, player_hp: input.player_hp, player_max_hp: input.player_max_hp, player_mana: input.player_mana, player_max_mana: input.player_max_mana, target_hp: input.target_hp, target_max_hp: input.target_max_hp, experience_reward: input.experience_reward, reward_items: input .reward_items .into_iter() .map(map_runtime_item_reward_item_snapshot) .collect(), } } } pub(crate) fn validate_npc_battle_interaction_input( input: &ResolveNpcBattleInteractionInput, ) -> Result<(), SpacetimeClientError> { let battle_state_input = DomainBattleStateInput { battle_state_id: input .battle_state_id .clone() .unwrap_or_else(|| "battle_preview".to_string()), story_session_id: input.story_session_id.clone(), runtime_session_id: input.npc_interaction.runtime_session_id.clone(), actor_user_id: input.actor_user_id.clone(), chapter_id: None, target_npc_id: input.npc_interaction.npc_id.clone(), target_name: input.npc_interaction.npc_name.clone(), battle_mode: DomainBattleMode::Fight, player_hp: input.player_hp, player_max_hp: input.player_max_hp, player_mana: input.player_mana, player_max_mana: input.player_max_mana, target_hp: input.target_hp, target_max_hp: input.target_max_hp, experience_reward: input.experience_reward, reward_items: input.reward_items.clone(), created_at_micros: input.npc_interaction.updated_at_micros, }; validate_battle_state_input(&battle_state_input) .map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?; for reward_item in input.reward_items.iter().cloned() { normalize_reward_item_snapshot(reward_item) .map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?; } Ok(()) } pub(crate) fn build_npc_state_record(snapshot: DomainNpcStateSnapshot) -> NpcStateRecord { NpcStateRecord { npc_state_id: snapshot.npc_state_id, runtime_session_id: snapshot.runtime_session_id, npc_id: snapshot.npc_id, npc_name: snapshot.npc_name, affinity: snapshot.affinity, relation_stance: format_npc_relation_stance(snapshot.relation_state.stance).to_string(), help_used: snapshot.help_used, chatted_count: snapshot.chatted_count, gifts_given: snapshot.gifts_given, recruited: snapshot.recruited, trade_stock_signature: snapshot.trade_stock_signature, revealed_facts: snapshot.revealed_facts, known_attribute_rumors: snapshot.known_attribute_rumors, first_meaningful_contact_resolved: snapshot.first_meaningful_contact_resolved, seen_backstory_chapter_ids: snapshot.seen_backstory_chapter_ids, trust: snapshot.stance_profile.trust, warmth: snapshot.stance_profile.warmth, ideological_fit: snapshot.stance_profile.ideological_fit, fear_or_guard: snapshot.stance_profile.fear_or_guard, loyalty: snapshot.stance_profile.loyalty, current_conflict_tag: snapshot.stance_profile.current_conflict_tag, recent_approvals: snapshot.stance_profile.recent_approvals, recent_disapprovals: snapshot.stance_profile.recent_disapprovals, created_at: format_timestamp_micros(snapshot.created_at_micros), updated_at: format_timestamp_micros(snapshot.updated_at_micros), } } pub(crate) fn build_npc_interaction_record( result: DomainNpcInteractionResult, ) -> NpcInteractionRecord { NpcInteractionRecord { npc_state: build_npc_state_record(result.npc_state), interaction_status: format_npc_interaction_status(result.interaction_status).to_string(), action_text: result.action_text, result_text: result.result_text, story_text: result.story_text, battle_mode: result .battle_mode .map(|mode| format_npc_interaction_battle_mode(mode).to_string()), encounter_closed: result.encounter_closed, affinity_changed: result.affinity_changed, previous_affinity: result.previous_affinity, next_affinity: result.next_affinity, } } pub(crate) fn build_npc_battle_interaction_record( result: NpcBattleInteractionSnapshot, ) -> NpcBattleInteractionRecord { NpcBattleInteractionRecord { npc_interaction: build_npc_interaction_record(result.interaction), battle_state: build_battle_state_record(result.battle_state), } } pub(crate) fn format_npc_relation_stance(value: DomainNpcRelationStance) -> &'static str { match value { DomainNpcRelationStance::Hostile => "hostile", DomainNpcRelationStance::Guarded => "guarded", DomainNpcRelationStance::Neutral => "neutral", DomainNpcRelationStance::Cooperative => "cooperative", DomainNpcRelationStance::Bonded => "bonded", } } pub(crate) fn format_npc_interaction_status(value: DomainNpcInteractionStatus) -> &'static str { match value { DomainNpcInteractionStatus::Previewed => "previewed", DomainNpcInteractionStatus::Dialogue => "dialogue", DomainNpcInteractionStatus::Resolved => "resolved", DomainNpcInteractionStatus::Recruited => "recruited", DomainNpcInteractionStatus::BattlePending => "battle_pending", DomainNpcInteractionStatus::Left => "left", } } pub(crate) fn format_npc_interaction_battle_mode( value: DomainNpcInteractionBattleMode, ) -> &'static str { match value { DomainNpcInteractionBattleMode::Fight => "fight", DomainNpcInteractionBattleMode::Spar => "spar", } } pub(crate) fn map_inventory_item_source_kind( value: InventoryItemSourceKind, ) -> module_inventory::InventoryItemSourceKind { match value { InventoryItemSourceKind::StoryReward => { module_inventory::InventoryItemSourceKind::StoryReward } InventoryItemSourceKind::QuestReward => { module_inventory::InventoryItemSourceKind::QuestReward } InventoryItemSourceKind::TreasureReward => { module_inventory::InventoryItemSourceKind::TreasureReward } InventoryItemSourceKind::NpcGift => module_inventory::InventoryItemSourceKind::NpcGift, InventoryItemSourceKind::NpcTrade => module_inventory::InventoryItemSourceKind::NpcTrade, InventoryItemSourceKind::CombatDrop => { module_inventory::InventoryItemSourceKind::CombatDrop } InventoryItemSourceKind::ForgeCraft => { module_inventory::InventoryItemSourceKind::ForgeCraft } InventoryItemSourceKind::ForgeReforge => { module_inventory::InventoryItemSourceKind::ForgeReforge } InventoryItemSourceKind::ManualPatch => { module_inventory::InventoryItemSourceKind::ManualPatch } } }