Files
Genarrative/server-rs/crates/module-runtime-story/src/options.rs

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,
})
}