123 lines
3.9 KiB
Rust
123 lines
3.9 KiB
Rust
use serde_json::Value;
|
|
|
|
use shared_contracts::runtime_story::{RuntimeStoryOptionInteraction, RuntimeStoryOptionView};
|
|
|
|
use crate::{read_bool_field, read_field, read_optional_string_field, read_required_string_field};
|
|
|
|
/// 这批 helper 只负责 runtime story option 的纯 DTO 编译,不触碰 HTTP / AppState。
|
|
pub fn infer_option_scope(function_id: &str) -> &'static str {
|
|
if function_id.starts_with("battle_") || function_id == "inventory_use" {
|
|
"combat"
|
|
} else if function_id.starts_with("npc_") {
|
|
"npc"
|
|
} else {
|
|
"story"
|
|
}
|
|
}
|
|
|
|
pub fn build_static_runtime_story_option(
|
|
function_id: &str,
|
|
action_text: &str,
|
|
scope: &str,
|
|
) -> RuntimeStoryOptionView {
|
|
RuntimeStoryOptionView {
|
|
function_id: function_id.to_string(),
|
|
action_text: action_text.to_string(),
|
|
detail_text: None,
|
|
scope: scope.to_string(),
|
|
interaction: None,
|
|
payload: None,
|
|
disabled: None,
|
|
reason: None,
|
|
}
|
|
}
|
|
|
|
pub fn build_runtime_story_option_with_payload(
|
|
function_id: &str,
|
|
action_text: &str,
|
|
scope: &str,
|
|
detail_text: Option<String>,
|
|
payload: Value,
|
|
) -> RuntimeStoryOptionView {
|
|
RuntimeStoryOptionView {
|
|
detail_text,
|
|
payload: Some(payload),
|
|
..build_static_runtime_story_option(function_id, action_text, scope)
|
|
}
|
|
}
|
|
|
|
pub fn build_disabled_runtime_story_option(
|
|
function_id: &str,
|
|
action_text: &str,
|
|
scope: &str,
|
|
detail_text: Option<String>,
|
|
reason: &str,
|
|
payload: Option<Value>,
|
|
) -> RuntimeStoryOptionView {
|
|
RuntimeStoryOptionView {
|
|
detail_text,
|
|
payload,
|
|
disabled: Some(true),
|
|
reason: Some(reason.to_string()),
|
|
..build_static_runtime_story_option(function_id, action_text, scope)
|
|
}
|
|
}
|
|
|
|
pub fn build_runtime_story_option_from_story_option(
|
|
value: &Value,
|
|
) -> Option<RuntimeStoryOptionView> {
|
|
let function_id = read_required_string_field(value, "functionId")?;
|
|
let action_text = read_required_string_field(value, "actionText")
|
|
.or_else(|| read_required_string_field(value, "text"))
|
|
.unwrap_or_else(|| function_id.clone());
|
|
|
|
Some(RuntimeStoryOptionView {
|
|
scope: infer_option_scope(function_id.as_str()).to_string(),
|
|
detail_text: read_optional_string_field(value, "detailText"),
|
|
interaction: build_runtime_story_option_interaction(read_field(value, "interaction")),
|
|
payload: read_field(value, "runtimePayload")
|
|
.or_else(|| read_field(value, "payload"))
|
|
.cloned(),
|
|
disabled: read_bool_field(value, "disabled"),
|
|
reason: read_optional_string_field(value, "disabledReason")
|
|
.or_else(|| read_optional_string_field(value, "reason")),
|
|
function_id,
|
|
action_text,
|
|
})
|
|
}
|
|
|
|
pub fn build_runtime_story_option_interaction(
|
|
value: Option<&Value>,
|
|
) -> Option<RuntimeStoryOptionInteraction> {
|
|
let interaction = value?;
|
|
match read_required_string_field(interaction, "kind")?.as_str() {
|
|
"npc" => Some(RuntimeStoryOptionInteraction::Npc {
|
|
npc_id: read_required_string_field(interaction, "npcId")?,
|
|
action: read_required_string_field(interaction, "action")?,
|
|
quest_id: read_optional_string_field(interaction, "questId"),
|
|
}),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn build_story_option_from_runtime_option(option: &RuntimeStoryOptionView) -> Value {
|
|
serde_json::json!({
|
|
"functionId": option.function_id,
|
|
"actionText": option.action_text,
|
|
"text": option.action_text,
|
|
"detailText": option.detail_text,
|
|
"visuals": {
|
|
"playerAnimation": "idle",
|
|
"playerMoveMeters": 0,
|
|
"playerOffsetY": 0,
|
|
"playerFacing": "right",
|
|
"scrollWorld": false,
|
|
"monsterChanges": []
|
|
},
|
|
"interaction": option.interaction,
|
|
"runtimePayload": option.payload,
|
|
"disabled": option.disabled,
|
|
"disabledReason": option.reason,
|
|
})
|
|
}
|