1
This commit is contained in:
@@ -350,6 +350,8 @@ pub struct CharacterWorkflowCachePayload {
|
||||
pub cache_scope_id: Option<String>,
|
||||
pub visual_prompt_text: String,
|
||||
pub animation_prompt_text: String,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub animation_prompt_text_by_key: BTreeMap<String, String>,
|
||||
pub visual_drafts: Vec<CharacterVisualDraftPayload>,
|
||||
pub selected_visual_draft_id: String,
|
||||
pub selected_animation: String,
|
||||
@@ -376,6 +378,8 @@ pub struct CharacterWorkflowCacheSaveRequest {
|
||||
#[serde(default)]
|
||||
pub animation_prompt_text: Option<String>,
|
||||
#[serde(default)]
|
||||
pub animation_prompt_text_by_key: BTreeMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub visual_drafts: Vec<CharacterVisualDraftPayload>,
|
||||
#[serde(default)]
|
||||
pub selected_visual_draft_id: Option<String>,
|
||||
@@ -398,6 +402,91 @@ pub struct CharacterWorkflowCacheGetResponse {
|
||||
pub cache: Option<CharacterWorkflowCachePayload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CharacterAssetRolePromptInput {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub title: String,
|
||||
#[serde(default)]
|
||||
pub role: String,
|
||||
#[serde(default)]
|
||||
pub visual_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub action_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub scene_visual_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub backstory: Option<String>,
|
||||
#[serde(default)]
|
||||
pub personality: Option<String>,
|
||||
#[serde(default)]
|
||||
pub motivation: Option<String>,
|
||||
#[serde(default)]
|
||||
pub combat_style: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub image_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub generated_visual_asset_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub generated_animation_set_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub animation_map: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CharacterRolePromptBundlePayload {
|
||||
pub visual_prompt_text: String,
|
||||
pub animation_prompt_text: String,
|
||||
pub scene_prompt_text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CharacterRoleAssetWorkflowPayload {
|
||||
pub role: CharacterAssetRolePromptInput,
|
||||
pub default_prompt_bundle: CharacterRolePromptBundlePayload,
|
||||
pub visual_prompt_text: String,
|
||||
pub animation_prompt_text: String,
|
||||
pub animation_prompt_text_by_key: BTreeMap<String, String>,
|
||||
pub visual_drafts: Vec<CharacterVisualDraftPayload>,
|
||||
pub selected_visual_draft_id: String,
|
||||
pub selected_animation: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_src: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub generated_visual_asset_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub generated_animation_set_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub animation_map: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CharacterRoleAssetWorkflowResolveRequest {
|
||||
#[serde(default)]
|
||||
pub cache_scope_id: Option<String>,
|
||||
pub role: CharacterAssetRolePromptInput,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CharacterRoleAssetWorkflowResponse {
|
||||
pub ok: bool,
|
||||
pub cache: Option<CharacterWorkflowCachePayload>,
|
||||
pub workflow: CharacterRoleAssetWorkflowPayload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CharacterWorkflowCacheSaveResponse {
|
||||
@@ -767,6 +856,10 @@ mod tests {
|
||||
cache_scope_id: Some("world-01".to_string()),
|
||||
visual_prompt_text: "主形象".to_string(),
|
||||
animation_prompt_text: "待机".to_string(),
|
||||
animation_prompt_text_by_key: BTreeMap::from([(
|
||||
"idle".to_string(),
|
||||
"待机".to_string(),
|
||||
)]),
|
||||
visual_drafts: vec![CharacterVisualDraftPayload {
|
||||
id: "draft-1".to_string(),
|
||||
label: "候选 1".to_string(),
|
||||
@@ -790,6 +883,10 @@ mod tests {
|
||||
assert_eq!(payload["ok"], json!(true));
|
||||
assert_eq!(payload["cache"]["characterId"], json!("hero"));
|
||||
assert_eq!(payload["cache"]["cacheScopeId"], json!("world-01"));
|
||||
assert_eq!(
|
||||
payload["cache"]["animationPromptTextByKey"]["idle"],
|
||||
json!("待机")
|
||||
);
|
||||
assert_eq!(
|
||||
payload["cache"]["visualDrafts"][0]["imageSrc"],
|
||||
json!("/generated-character-drafts/hero/visual/job/candidate.svg")
|
||||
|
||||
@@ -50,9 +50,13 @@ pub struct BigFishLevelBlueprintResponse {
|
||||
pub level: u32,
|
||||
pub name: String,
|
||||
pub one_line_fantasy: String,
|
||||
pub text_description: String,
|
||||
pub silhouette_direction: String,
|
||||
pub size_ratio: f32,
|
||||
pub visual_description: String,
|
||||
pub visual_prompt_seed: String,
|
||||
pub idle_motion_description: String,
|
||||
pub move_motion_description: String,
|
||||
pub motion_prompt_seed: String,
|
||||
pub merge_source_level: Option<u32>,
|
||||
pub prey_window: Vec<u32>,
|
||||
|
||||
@@ -55,6 +55,15 @@ pub struct PutSavedGameSnapshotRequest {
|
||||
pub saved_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct PutRuntimeSaveCheckpointRequest {
|
||||
pub session_id: String,
|
||||
pub bottom_tab: String,
|
||||
#[serde(default)]
|
||||
pub saved_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BasicOkResponse {
|
||||
@@ -345,6 +354,16 @@ pub struct CustomWorldProfileUpsertRequest {
|
||||
pub source_agent_session_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateCustomWorldProfileRequest {
|
||||
pub setting_text: String,
|
||||
#[serde(default)]
|
||||
pub creator_intent: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub generation_mode: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CustomWorldLibraryEntryResponse {
|
||||
@@ -573,6 +592,24 @@ pub struct CustomWorldPublishGateResponse {
|
||||
pub can_enter_world: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CustomWorldCreationResultViewResponse {
|
||||
pub session: CustomWorldAgentSessionSnapshotResponse,
|
||||
pub profile: Option<serde_json::Value>,
|
||||
pub profile_source: String,
|
||||
pub target_stage: String,
|
||||
pub generation_view_source: Option<String>,
|
||||
pub result_view_source: Option<String>,
|
||||
pub can_autosave_library: bool,
|
||||
pub can_sync_result_profile: bool,
|
||||
pub publish_ready: bool,
|
||||
pub can_enter_world: bool,
|
||||
pub blocker_count: u32,
|
||||
pub recovery_action: String,
|
||||
pub recovery_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CustomWorldAgentSessionSnapshotResponse {
|
||||
|
||||
@@ -22,6 +22,27 @@ pub struct RuntimeStoryStateResolveRequest {
|
||||
pub snapshot: Option<RuntimeStorySnapshotPayload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryBootstrapRequest {
|
||||
pub world_type: String,
|
||||
#[serde(default)]
|
||||
pub custom_world_profile: Option<Value>,
|
||||
pub character: Value,
|
||||
#[serde(default)]
|
||||
pub runtime_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
pub disable_persistence: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryBootstrapResponse {
|
||||
pub session_id: String,
|
||||
pub server_version: u32,
|
||||
pub snapshot: RuntimeStorySnapshotPayload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryChoiceAction {
|
||||
@@ -66,7 +87,13 @@ impl Default for RuntimeStoryAiRequestOptions {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryAiRequest {
|
||||
#[serde(default)]
|
||||
pub session_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub client_version: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub world_type: String,
|
||||
#[serde(default)]
|
||||
pub character: Value,
|
||||
#[serde(default)]
|
||||
pub monsters: Vec<Value>,
|
||||
@@ -74,9 +101,16 @@ pub struct RuntimeStoryAiRequest {
|
||||
pub history: Vec<Value>,
|
||||
#[serde(default)]
|
||||
pub choice: String,
|
||||
#[serde(default)]
|
||||
pub context: Value,
|
||||
#[serde(default)]
|
||||
pub request_options: RuntimeStoryAiRequestOptions,
|
||||
#[serde(default)]
|
||||
pub last_function_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub observe_signs_requested: bool,
|
||||
#[serde(default)]
|
||||
pub recent_action_result: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -163,6 +197,130 @@ pub struct RuntimeStoryStatusViewModel {
|
||||
pub current_npc_battle_outcome: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryInventoryActionView {
|
||||
pub function_id: String,
|
||||
pub action_text: 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 RuntimeStoryInventoryItemActionsView {
|
||||
#[serde(rename = "use")]
|
||||
pub use_item: RuntimeStoryInventoryActionView,
|
||||
pub equip: RuntimeStoryInventoryActionView,
|
||||
pub dismantle: RuntimeStoryInventoryActionView,
|
||||
pub reforge: RuntimeStoryInventoryActionView,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryInventoryItemView {
|
||||
pub item: Value,
|
||||
pub actions: RuntimeStoryInventoryItemActionsView,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryEquipmentSlotView {
|
||||
pub slot_id: String,
|
||||
pub label: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub item: Option<Value>,
|
||||
pub unequip: RuntimeStoryInventoryActionView,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryForgeRequirementView {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub quantity: i32,
|
||||
pub owned: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryForgeRecipeView {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub kind: String,
|
||||
pub description: String,
|
||||
pub result_label: String,
|
||||
pub currency_cost: i32,
|
||||
pub currency_text: String,
|
||||
pub requirements: Vec<RuntimeStoryForgeRequirementView>,
|
||||
pub can_craft: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub disabled_reason: Option<String>,
|
||||
pub action: RuntimeStoryInventoryActionView,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryInventoryViewModel {
|
||||
pub player_currency: i32,
|
||||
pub currency_text: String,
|
||||
pub in_battle: bool,
|
||||
pub backpack_items: Vec<RuntimeStoryInventoryItemView>,
|
||||
pub equipment_slots: Vec<RuntimeStoryEquipmentSlotView>,
|
||||
pub forge_recipes: Vec<RuntimeStoryForgeRecipeView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeNpcTradeItemView {
|
||||
pub item_id: String,
|
||||
pub item: Value,
|
||||
pub mode: String,
|
||||
pub unit_price: i32,
|
||||
pub max_quantity: i32,
|
||||
pub can_submit: 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 RuntimeNpcGiftItemView {
|
||||
pub item_id: String,
|
||||
pub item: Value,
|
||||
pub affinity_gain: i32,
|
||||
pub can_submit: 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 RuntimeNpcTradeView {
|
||||
pub buy_items: Vec<RuntimeNpcTradeItemView>,
|
||||
pub sell_items: Vec<RuntimeNpcTradeItemView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeNpcGiftView {
|
||||
pub items: Vec<RuntimeNpcGiftItemView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeNpcInteractionView {
|
||||
pub npc_id: String,
|
||||
pub npc_name: String,
|
||||
pub player_currency: i32,
|
||||
pub currency_name: String,
|
||||
pub trade: RuntimeNpcTradeView,
|
||||
pub gift: RuntimeNpcGiftView,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeBattlePresentation {
|
||||
@@ -185,8 +343,11 @@ pub struct RuntimeStoryViewModel {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub encounter: Option<RuntimeStoryEncounterViewModel>,
|
||||
pub companions: Vec<RuntimeStoryCompanionViewModel>,
|
||||
pub inventory: RuntimeStoryInventoryViewModel,
|
||||
pub available_options: Vec<RuntimeStoryOptionView>,
|
||||
pub status: RuntimeStoryStatusViewModel,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub npc_interaction: Option<RuntimeNpcInteractionView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -311,6 +472,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_bootstrap_request_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(RuntimeStoryBootstrapRequest {
|
||||
world_type: "CUSTOM".to_string(),
|
||||
custom_world_profile: Some(json!({ "id": "profile-1" })),
|
||||
character: json!({ "id": "role-1", "name": "沈砺" }),
|
||||
runtime_mode: Some("play".to_string()),
|
||||
disable_persistence: Some(false),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(payload["worldType"], json!("CUSTOM"));
|
||||
assert_eq!(payload["customWorldProfile"]["id"], json!("profile-1"));
|
||||
assert_eq!(payload["runtimeMode"], json!("play"));
|
||||
assert_eq!(payload["disablePersistence"], json!(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_ai_request_defaults_optional_arrays() {
|
||||
let payload: RuntimeStoryAiRequest = serde_json::from_value(json!({
|
||||
@@ -326,6 +504,33 @@ mod tests {
|
||||
assert!(payload.request_options.available_options.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_ai_request_accepts_session_only_payload() {
|
||||
let payload: RuntimeStoryAiRequest = serde_json::from_value(json!({
|
||||
"sessionId": "runtime-main",
|
||||
"clientVersion": 3,
|
||||
"choice": "继续向前",
|
||||
"lastFunctionId": "idle_explore_forward",
|
||||
"requestOptions": {
|
||||
"optionCatalog": [{
|
||||
"functionId": "idle_observe_signs",
|
||||
"actionText": "观察周围迹象"
|
||||
}]
|
||||
}
|
||||
}))
|
||||
.expect("payload should deserialize");
|
||||
|
||||
assert_eq!(payload.session_id.as_deref(), Some("runtime-main"));
|
||||
assert_eq!(payload.client_version, Some(3));
|
||||
assert_eq!(payload.world_type, "");
|
||||
assert_eq!(payload.context, Value::Null);
|
||||
assert_eq!(
|
||||
payload.last_function_id.as_deref(),
|
||||
Some("idle_explore_forward")
|
||||
);
|
||||
assert_eq!(payload.request_options.option_catalog.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_action_response_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(RuntimeStoryActionResponse {
|
||||
@@ -353,6 +558,87 @@ mod tests {
|
||||
character_id: Some("char_companion_001".to_string()),
|
||||
joined_at_affinity: 64,
|
||||
}],
|
||||
inventory: RuntimeStoryInventoryViewModel {
|
||||
player_currency: 80,
|
||||
currency_text: "80 铜钱".to_string(),
|
||||
in_battle: false,
|
||||
backpack_items: vec![RuntimeStoryInventoryItemView {
|
||||
item: json!({
|
||||
"id": "potion-1",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
actions: RuntimeStoryInventoryItemActionsView {
|
||||
use_item: RuntimeStoryInventoryActionView {
|
||||
function_id: "inventory_use".to_string(),
|
||||
action_text: "使用疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: true,
|
||||
reason: None,
|
||||
},
|
||||
equip: RuntimeStoryInventoryActionView {
|
||||
function_id: "equipment_equip".to_string(),
|
||||
action_text: "装备疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能装备。".to_string()),
|
||||
},
|
||||
dismantle: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_dismantle".to_string(),
|
||||
action_text: "拆解疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能拆解。".to_string()),
|
||||
},
|
||||
reforge: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_reforge".to_string(),
|
||||
action_text: "重铸疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能重铸。".to_string()),
|
||||
},
|
||||
},
|
||||
}],
|
||||
equipment_slots: vec![RuntimeStoryEquipmentSlotView {
|
||||
slot_id: "weapon".to_string(),
|
||||
label: "武器".to_string(),
|
||||
item: None,
|
||||
unequip: RuntimeStoryInventoryActionView {
|
||||
function_id: "equipment_unequip".to_string(),
|
||||
action_text: "卸下武器".to_string(),
|
||||
payload: Some(json!({ "slotId": "weapon" })),
|
||||
enabled: false,
|
||||
reason: Some("武器位当前没有装备。".to_string()),
|
||||
},
|
||||
}],
|
||||
forge_recipes: vec![RuntimeStoryForgeRecipeView {
|
||||
id: "synthesis-refined-ingot".to_string(),
|
||||
name: "压炼锭材".to_string(),
|
||||
kind: "synthesis".to_string(),
|
||||
description: "把零散残片和基础材料压成稳定可用的金属锭材。".to_string(),
|
||||
result_label: "精炼锭材".to_string(),
|
||||
currency_cost: 18,
|
||||
currency_text: "18 铜钱".to_string(),
|
||||
requirements: vec![RuntimeStoryForgeRequirementView {
|
||||
id: "material:any".to_string(),
|
||||
label: "任意材料".to_string(),
|
||||
quantity: 3,
|
||||
owned: 0,
|
||||
}],
|
||||
can_craft: false,
|
||||
disabled_reason: Some("材料不足。".to_string()),
|
||||
action: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_craft".to_string(),
|
||||
action_text: "制作精炼锭材".to_string(),
|
||||
payload: Some(json!({ "recipeId": "synthesis-refined-ingot" })),
|
||||
enabled: false,
|
||||
reason: Some("材料不足。".to_string()),
|
||||
},
|
||||
}],
|
||||
},
|
||||
available_options: vec![RuntimeStoryOptionView {
|
||||
function_id: "npc_chat".to_string(),
|
||||
action_text: "继续交谈".to_string(),
|
||||
@@ -373,6 +659,47 @@ mod tests {
|
||||
current_npc_battle_mode: None,
|
||||
current_npc_battle_outcome: None,
|
||||
},
|
||||
npc_interaction: Some(RuntimeNpcInteractionView {
|
||||
npc_id: "npc_camp_firekeeper".to_string(),
|
||||
npc_name: "守火人".to_string(),
|
||||
player_currency: 80,
|
||||
currency_name: "铜钱".to_string(),
|
||||
trade: RuntimeNpcTradeView {
|
||||
buy_items: vec![RuntimeNpcTradeItemView {
|
||||
item_id: "npc-potion".to_string(),
|
||||
item: json!({
|
||||
"id": "npc-potion",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
mode: "buy".to_string(),
|
||||
unit_price: 20,
|
||||
max_quantity: 2,
|
||||
can_submit: true,
|
||||
reason: None,
|
||||
}],
|
||||
sell_items: Vec::new(),
|
||||
},
|
||||
gift: RuntimeNpcGiftView {
|
||||
items: vec![RuntimeNpcGiftItemView {
|
||||
item_id: "potion-1".to_string(),
|
||||
item: json!({
|
||||
"id": "potion-1",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
affinity_gain: 10,
|
||||
can_submit: true,
|
||||
reason: None,
|
||||
}],
|
||||
},
|
||||
}),
|
||||
},
|
||||
presentation: RuntimeStoryPresentation {
|
||||
action_text: "".to_string(),
|
||||
@@ -419,6 +746,14 @@ mod tests {
|
||||
payload["viewModel"]["availableOptions"][0]["interaction"]["npcId"],
|
||||
json!("npc_camp_firekeeper")
|
||||
);
|
||||
assert_eq!(
|
||||
payload["viewModel"]["inventory"]["backpackItems"][0]["actions"]["use"]["functionId"],
|
||||
json!("inventory_use")
|
||||
);
|
||||
assert_eq!(
|
||||
payload["viewModel"]["inventory"]["forgeRecipes"][0]["canCraft"],
|
||||
json!(false)
|
||||
);
|
||||
assert_eq!(
|
||||
payload["presentation"]["storyText"],
|
||||
json!("守火人抬眼看了你一瞬,示意你把想问的话继续说完。")
|
||||
|
||||
Reference in New Issue
Block a user