592 lines
21 KiB
Rust
592 lines
21 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BeginStorySessionRequest {
|
|
pub runtime_session_id: String,
|
|
pub world_profile_id: String,
|
|
pub initial_prompt: String,
|
|
#[serde(default)]
|
|
pub opening_summary: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ContinueStoryRequest {
|
|
pub story_session_id: String,
|
|
pub narrative_text: String,
|
|
#[serde(default)]
|
|
pub choice_function_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StorySessionPayload {
|
|
pub story_session_id: String,
|
|
pub runtime_session_id: String,
|
|
pub actor_user_id: String,
|
|
pub world_profile_id: String,
|
|
pub initial_prompt: String,
|
|
#[serde(default)]
|
|
pub opening_summary: Option<String>,
|
|
pub latest_narrative_text: String,
|
|
#[serde(default)]
|
|
pub latest_choice_function_id: Option<String>,
|
|
pub status: String,
|
|
pub version: u32,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryEventPayload {
|
|
pub event_id: String,
|
|
pub story_session_id: String,
|
|
pub event_kind: String,
|
|
pub narrative_text: String,
|
|
#[serde(default)]
|
|
pub choice_function_id: Option<String>,
|
|
pub created_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StorySessionMutationResponse {
|
|
pub story_session: StorySessionPayload,
|
|
pub story_event: StoryEventPayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StorySessionStateResponse {
|
|
pub story_session: StorySessionPayload,
|
|
pub story_events: Vec<StoryEventPayload>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryRuntimeProjectionRequest {
|
|
pub story_session_id: String,
|
|
#[serde(default)]
|
|
pub client_version: Option<u32>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryRuntimeActorProjection {
|
|
pub hp: i32,
|
|
pub max_hp: i32,
|
|
pub mana: i32,
|
|
pub max_mana: i32,
|
|
pub currency: i32,
|
|
pub currency_text: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryRuntimeInventoryProjection {
|
|
pub backpack_items: Vec<Value>,
|
|
pub equipment_slots: Vec<Value>,
|
|
pub forge_recipes: Vec<Value>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryRuntimeOptionProjection {
|
|
pub function_id: String,
|
|
pub action_text: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub detail_text: Option<String>,
|
|
pub scope: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub payload: Option<Value>,
|
|
pub enabled: bool,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub reason: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryRuntimeStatusProjection {
|
|
pub in_battle: bool,
|
|
pub npc_interaction_active: bool,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub current_encounter_id: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub current_npc_battle_mode: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub current_npc_battle_outcome: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryRuntimeProjectionResponse {
|
|
pub story_session: StorySessionPayload,
|
|
pub story_events: Vec<StoryEventPayload>,
|
|
pub server_version: u32,
|
|
pub actor: StoryRuntimeActorProjection,
|
|
pub inventory: StoryRuntimeInventoryProjection,
|
|
pub options: Vec<StoryRuntimeOptionProjection>,
|
|
pub status: StoryRuntimeStatusProjection,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub current_narrative_text: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub action_result_text: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub toast: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryBattleRewardItemRequest {
|
|
pub item_id: String,
|
|
pub category: String,
|
|
pub item_name: String,
|
|
#[serde(default)]
|
|
pub description: Option<String>,
|
|
pub quantity: u32,
|
|
pub rarity: String,
|
|
#[serde(default)]
|
|
pub tags: Vec<String>,
|
|
pub stackable: bool,
|
|
#[serde(default)]
|
|
pub stack_key: String,
|
|
#[serde(default)]
|
|
pub equipment_slot_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CreateStoryBattleRequest {
|
|
pub story_session_id: String,
|
|
pub runtime_session_id: String,
|
|
#[serde(default)]
|
|
pub chapter_id: Option<String>,
|
|
pub target_npc_id: String,
|
|
pub target_name: String,
|
|
pub battle_mode: 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,
|
|
#[serde(default)]
|
|
pub experience_reward: u32,
|
|
#[serde(default)]
|
|
pub reward_items: Vec<StoryBattleRewardItemRequest>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CreateStoryNpcBattleRequest {
|
|
pub story_session_id: String,
|
|
pub runtime_session_id: String,
|
|
pub npc_id: String,
|
|
pub npc_name: String,
|
|
pub interaction_function_id: String,
|
|
#[serde(default)]
|
|
pub release_npc_id: Option<String>,
|
|
#[serde(default)]
|
|
pub battle_state_id: Option<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,
|
|
#[serde(default)]
|
|
pub experience_reward: u32,
|
|
#[serde(default)]
|
|
pub reward_items: Vec<StoryBattleRewardItemRequest>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ResolveStoryBattleRequest {
|
|
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,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryBattleRewardItemPayload {
|
|
pub item_id: String,
|
|
pub category: String,
|
|
pub item_name: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
pub quantity: u32,
|
|
pub rarity: String,
|
|
pub tags: Vec<String>,
|
|
pub stackable: bool,
|
|
pub stack_key: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub equipment_slot_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryBattleStatePayload {
|
|
pub battle_state_id: String,
|
|
pub story_session_id: String,
|
|
pub runtime_session_id: String,
|
|
pub actor_user_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub chapter_id: Option<String>,
|
|
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<StoryBattleRewardItemPayload>,
|
|
pub turn_index: u32,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub last_action_function_id: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub last_action_text: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub last_result_text: Option<String>,
|
|
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, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryBattleStateResponse {
|
|
pub battle_state: StoryBattleStatePayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryCombatActionPayload {
|
|
pub damage_dealt: i32,
|
|
pub damage_taken: i32,
|
|
pub outcome: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ResolveStoryBattleResponse {
|
|
pub battle_state: StoryBattleStatePayload,
|
|
pub combat: StoryCombatActionPayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryNpcStanceProfilePayload {
|
|
pub trust: u8,
|
|
pub warmth: u8,
|
|
pub ideological_fit: u8,
|
|
pub fear_or_guard: u8,
|
|
pub loyalty: u8,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub current_conflict_tag: Option<String>,
|
|
pub recent_approvals: Vec<String>,
|
|
pub recent_disapprovals: Vec<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryNpcStatePayload {
|
|
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,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub trade_stock_signature: Option<String>,
|
|
pub revealed_facts: Vec<String>,
|
|
pub known_attribute_rumors: Vec<String>,
|
|
pub first_meaningful_contact_resolved: bool,
|
|
pub seen_backstory_chapter_ids: Vec<String>,
|
|
pub stance_profile: StoryNpcStanceProfilePayload,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StoryNpcInteractionPayload {
|
|
pub npc_state: StoryNpcStatePayload,
|
|
pub interaction_status: String,
|
|
pub action_text: String,
|
|
pub result_text: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub story_text: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub battle_mode: Option<String>,
|
|
pub encounter_closed: bool,
|
|
pub affinity_changed: bool,
|
|
pub previous_affinity: i32,
|
|
pub next_affinity: i32,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CreateStoryNpcBattleResponse {
|
|
pub npc_interaction: StoryNpcInteractionPayload,
|
|
pub battle_state: StoryBattleStatePayload,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn continue_story_request_uses_camel_case_fields() {
|
|
let payload = serde_json::to_value(ContinueStoryRequest {
|
|
story_session_id: "storysess_1".to_string(),
|
|
narrative_text: "继续前进".to_string(),
|
|
choice_function_id: Some("npc_chat".to_string()),
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(
|
|
payload,
|
|
json!({
|
|
"storySessionId": "storysess_1",
|
|
"narrativeText": "继续前进",
|
|
"choiceFunctionId": "npc_chat"
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn story_session_mutation_response_uses_camel_case_fields() {
|
|
let payload = serde_json::to_value(StorySessionMutationResponse {
|
|
story_session: StorySessionPayload {
|
|
story_session_id: "storysess_1".to_string(),
|
|
runtime_session_id: "runtime_1".to_string(),
|
|
actor_user_id: "user_1".to_string(),
|
|
world_profile_id: "profile_1".to_string(),
|
|
initial_prompt: "进入营地".to_string(),
|
|
opening_summary: Some("营地开场".to_string()),
|
|
latest_narrative_text: "篝火正在燃烧。".to_string(),
|
|
latest_choice_function_id: Some("talk".to_string()),
|
|
status: "active".to_string(),
|
|
version: 1,
|
|
created_at: "1.000000Z".to_string(),
|
|
updated_at: "1.000000Z".to_string(),
|
|
},
|
|
story_event: StoryEventPayload {
|
|
event_id: "storyevt_1".to_string(),
|
|
story_session_id: "storysess_1".to_string(),
|
|
event_kind: "session_started".to_string(),
|
|
narrative_text: "篝火正在燃烧。".to_string(),
|
|
choice_function_id: Some("talk".to_string()),
|
|
created_at: "1.000000Z".to_string(),
|
|
},
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(
|
|
payload["storySession"]["storySessionId"],
|
|
json!("storysess_1")
|
|
);
|
|
assert_eq!(payload["storyEvent"]["eventKind"], json!("session_started"));
|
|
assert_eq!(payload["storyEvent"]["choiceFunctionId"], json!("talk"));
|
|
}
|
|
|
|
#[test]
|
|
fn story_session_state_response_uses_camel_case_fields() {
|
|
let payload = serde_json::to_value(StorySessionStateResponse {
|
|
story_session: StorySessionPayload {
|
|
story_session_id: "storysess_1".to_string(),
|
|
runtime_session_id: "runtime_1".to_string(),
|
|
actor_user_id: "user_1".to_string(),
|
|
world_profile_id: "profile_1".to_string(),
|
|
initial_prompt: "进入营地".to_string(),
|
|
opening_summary: Some("营地开场".to_string()),
|
|
latest_narrative_text: "你看见篝火边有人招手。".to_string(),
|
|
latest_choice_function_id: Some("talk_to_npc".to_string()),
|
|
status: "active".to_string(),
|
|
version: 2,
|
|
created_at: "1.000000Z".to_string(),
|
|
updated_at: "2.000000Z".to_string(),
|
|
},
|
|
story_events: vec![StoryEventPayload {
|
|
event_id: "storyevt_2".to_string(),
|
|
story_session_id: "storysess_1".to_string(),
|
|
event_kind: "story_continued".to_string(),
|
|
narrative_text: "你看见篝火边有人招手。".to_string(),
|
|
choice_function_id: Some("talk_to_npc".to_string()),
|
|
created_at: "2.000000Z".to_string(),
|
|
}],
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(
|
|
payload["storySession"]["latestChoiceFunctionId"],
|
|
json!("talk_to_npc")
|
|
);
|
|
assert_eq!(
|
|
payload["storyEvents"][0]["eventKind"],
|
|
json!("story_continued")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn story_runtime_projection_response_uses_new_story_runtime_contract() {
|
|
let payload = serde_json::to_value(StoryRuntimeProjectionResponse {
|
|
story_session: StorySessionPayload {
|
|
story_session_id: "storysess_1".to_string(),
|
|
runtime_session_id: "runtime_1".to_string(),
|
|
actor_user_id: "user_1".to_string(),
|
|
world_profile_id: "profile_1".to_string(),
|
|
initial_prompt: "进入营地".to_string(),
|
|
opening_summary: Some("营地开场".to_string()),
|
|
latest_narrative_text: "你看见篝火边有人招手。".to_string(),
|
|
latest_choice_function_id: Some("talk_to_npc".to_string()),
|
|
status: "active".to_string(),
|
|
version: 2,
|
|
created_at: "1.000000Z".to_string(),
|
|
updated_at: "2.000000Z".to_string(),
|
|
},
|
|
story_events: vec![StoryEventPayload {
|
|
event_id: "storyevt_2".to_string(),
|
|
story_session_id: "storysess_1".to_string(),
|
|
event_kind: "story_continued".to_string(),
|
|
narrative_text: "你看见篝火边有人招手。".to_string(),
|
|
choice_function_id: Some("talk_to_npc".to_string()),
|
|
created_at: "2.000000Z".to_string(),
|
|
}],
|
|
server_version: 2,
|
|
actor: StoryRuntimeActorProjection {
|
|
hp: 32,
|
|
max_hp: 40,
|
|
mana: 18,
|
|
max_mana: 20,
|
|
currency: 80,
|
|
currency_text: "80 铜钱".to_string(),
|
|
},
|
|
inventory: StoryRuntimeInventoryProjection {
|
|
backpack_items: vec![json!({ "id": "potion-1", "name": "疗伤药" })],
|
|
equipment_slots: vec![json!({ "slotId": "weapon", "label": "武器" })],
|
|
forge_recipes: Vec::new(),
|
|
},
|
|
options: vec![StoryRuntimeOptionProjection {
|
|
function_id: "npc_chat".to_string(),
|
|
action_text: "继续交谈".to_string(),
|
|
detail_text: Some("围绕当前话题继续推进关系判断。".to_string()),
|
|
scope: "npc".to_string(),
|
|
payload: Some(json!({ "npcId": "npc_camp_firekeeper" })),
|
|
enabled: true,
|
|
reason: None,
|
|
}],
|
|
status: StoryRuntimeStatusProjection {
|
|
in_battle: false,
|
|
npc_interaction_active: true,
|
|
current_encounter_id: Some("npc_camp_firekeeper".to_string()),
|
|
current_npc_battle_mode: None,
|
|
current_npc_battle_outcome: None,
|
|
},
|
|
current_narrative_text: Some("守火人示意你继续说。".to_string()),
|
|
action_result_text: None,
|
|
toast: None,
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(
|
|
payload["storySession"]["storySessionId"],
|
|
json!("storysess_1")
|
|
);
|
|
assert_eq!(payload["serverVersion"], json!(2));
|
|
assert_eq!(payload["actor"]["maxHp"], json!(40));
|
|
assert_eq!(
|
|
payload["inventory"]["backpackItems"][0]["name"],
|
|
json!("疗伤药")
|
|
);
|
|
assert_eq!(payload["options"][0]["functionId"], json!("npc_chat"));
|
|
assert!(payload.get("snapshot").is_none());
|
|
assert!(payload.get("viewModel").is_none());
|
|
assert!(payload.get("presentation").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn story_battle_responses_use_story_contract_shape() {
|
|
let battle_state = StoryBattleStatePayload {
|
|
battle_state_id: "battle_1".to_string(),
|
|
story_session_id: "storysess_1".to_string(),
|
|
runtime_session_id: "runtime_1".to_string(),
|
|
actor_user_id: "user_1".to_string(),
|
|
chapter_id: None,
|
|
target_npc_id: "npc_wolf".to_string(),
|
|
target_name: "黑爪狼".to_string(),
|
|
battle_mode: "fight".to_string(),
|
|
status: "active".to_string(),
|
|
player_hp: 28,
|
|
player_max_hp: 40,
|
|
player_mana: 12,
|
|
player_max_mana: 20,
|
|
target_hp: 18,
|
|
target_max_hp: 30,
|
|
experience_reward: 12,
|
|
reward_items: vec![StoryBattleRewardItemPayload {
|
|
item_id: "wolf-fang".to_string(),
|
|
category: "material".to_string(),
|
|
item_name: "狼牙".to_string(),
|
|
description: None,
|
|
quantity: 1,
|
|
rarity: "common".to_string(),
|
|
tags: vec!["beast".to_string()],
|
|
stackable: true,
|
|
stack_key: "wolf-fang".to_string(),
|
|
equipment_slot_id: None,
|
|
}],
|
|
turn_index: 1,
|
|
last_action_function_id: Some("battle_attack_basic".to_string()),
|
|
last_action_text: Some("普通攻击".to_string()),
|
|
last_result_text: Some("你击中了黑爪狼。".to_string()),
|
|
last_damage_dealt: 10,
|
|
last_damage_taken: 3,
|
|
last_outcome: "ongoing".to_string(),
|
|
version: 2,
|
|
created_at: "1.000000Z".to_string(),
|
|
updated_at: "2.000000Z".to_string(),
|
|
};
|
|
|
|
let payload = serde_json::to_value(ResolveStoryBattleResponse {
|
|
battle_state,
|
|
combat: StoryCombatActionPayload {
|
|
damage_dealt: 10,
|
|
damage_taken: 3,
|
|
outcome: "ongoing".to_string(),
|
|
},
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(payload["battleState"]["battleStateId"], json!("battle_1"));
|
|
assert_eq!(
|
|
payload["battleState"]["rewardItems"][0]["itemName"],
|
|
json!("狼牙")
|
|
);
|
|
assert_eq!(payload["combat"]["damageDealt"], json!(10));
|
|
assert!(payload["battleState"].get("chapterId").is_none());
|
|
}
|
|
}
|