fix: preserve rpg custom world detail profiles

This commit is contained in:
kdletters
2026-05-22 03:14:11 +08:00
parent a9d23a8a44
commit d74457faa2
19 changed files with 2726 additions and 109 deletions

View File

@@ -14,16 +14,18 @@ use crate::{
build_current_build_toast, build_npc_gift_result_text,
build_runtime_story_option_from_story_option, build_runtime_story_view_model,
build_static_runtime_story_option, build_status_patch, build_story_option_from_runtime_option,
clear_encounter_state, clone_inventory_item_with_quantity, current_encounter_name,
ensure_json_object, find_player_inventory_entry, normalize_equipment_slot_id,
normalize_required_string, npc_buyback_price, npc_purchase_price,
clear_encounter_state, clear_post_battle_state, clone_inventory_item_with_quantity,
current_encounter_name, ensure_json_object, ensure_scene_act_state,
ensure_scene_encounter_preview, finalize_post_battle_resolution, find_player_inventory_entry,
normalize_equipment_slot_id, normalize_required_string, npc_buyback_price, npc_purchase_price,
project_story_engine_after_action, read_array_field, read_bool_field, read_field,
read_i32_field, read_inventory_item_name, read_object_field, read_optional_string_field,
read_player_equipment_item, read_player_inventory_values, read_runtime_session_id,
read_u32_field, recruit_companion_to_party, remove_inventory_item_from_list,
resolve_action_text, resolve_battle_action, resolve_equipment_slot_for_item,
resolve_forge_craft_action, resolve_forge_dismantle_action, resolve_forge_reforge_action,
resolve_npc_gift_affinity_gain, restore_player_resource, simple_story_resolution,
resolve_forward_scene_id, resolve_npc_gift_affinity_gain, resolve_post_battle_story_options,
resolve_runtime_scene_preset, restore_player_resource, simple_story_resolution,
write_bool_field, write_i32_field, write_null_field, write_player_equipment_item,
write_player_inventory_values, write_runtime_npc_interaction_view, write_string_field,
write_u32_field,
@@ -97,23 +99,7 @@ pub fn resolve_story_runtime_action(
requested_runtime_session_id.as_str(),
);
let mut options = resolution
.presentation_options
.take()
.unwrap_or_else(|| build_fallback_runtime_story_options(&game_state));
if options.is_empty() {
options = build_fallback_runtime_story_options(&game_state);
}
let story_text = resolution
.story_text
.clone()
.unwrap_or_else(|| resolution.result_text.clone());
let history_result_text = resolution.result_text.clone();
let saved_current_story = resolution
.saved_current_story
.take()
.unwrap_or_else(|| build_current_story(story_text.as_str(), &options));
append_story_history(
&mut game_state,
@@ -132,6 +118,37 @@ pub fn resolve_story_runtime_action(
.and_then(|battle| battle.outcome.as_deref()),
);
if let Some(post_battle) = finalize_post_battle_resolution(
&mut game_state,
history_result_text.as_str(),
resolution
.battle
.as_ref()
.and_then(|battle| battle.outcome.as_deref()),
Vec::new(),
) {
resolution.story_text = Some(post_battle.story_text);
resolution.presentation_options = Some(post_battle.presentation_options);
resolution.saved_current_story = Some(post_battle.saved_current_story);
}
let mut options = resolution
.presentation_options
.take()
.unwrap_or_else(|| build_fallback_runtime_story_options(&game_state));
if options.is_empty() {
options = build_fallback_runtime_story_options(&game_state);
}
let story_text = resolution
.story_text
.clone()
.unwrap_or_else(|| resolution.result_text.clone());
let saved_current_story = resolution
.saved_current_story
.take()
.unwrap_or_else(|| build_current_story(story_text.as_str(), &options));
let mut patches = vec![RuntimeStoryPatch::StoryHistoryAppend {
action_text: resolution.action_text.clone(),
result_text: history_result_text.clone(),
@@ -212,11 +229,10 @@ fn resolve_runtime_story_choice_action(
resolve_action_text("主动出声试探", request),
"你的喊话打破了当前静场,周围潜着的动静也更难继续藏住。",
)),
"idle_explore_forward" => Ok(simple_story_resolution(
game_state,
resolve_action_text("继续向前探索", request),
"你没有停在原地,而是继续向前压,把下一段遭遇主动推到自己面前。",
)),
"idle_explore_forward" => resolve_idle_explore_forward_action(game_state, request),
"idle_travel_next_scene" | "camp_travel_home_scene" => {
resolve_idle_travel_next_scene_action(game_state, request)
}
"idle_observe_signs" => Ok(simple_story_resolution(
game_state,
resolve_action_text("观察周围迹象", request),
@@ -309,6 +325,62 @@ fn resolve_continue_adventure_action(
})
}
fn resolve_idle_explore_forward_action(
game_state: &mut Value,
request: &RuntimeStoryActionRequest,
) -> Result<StoryResolution, String> {
// 中文注释:探索前进是战后继续链路的一环,必须在后端清掉战斗态并生成下一段遭遇预览。
// 前端只播放表现动画,不能只靠本地状态把同一组 idle 选项重新展示一遍。
clear_post_battle_state(game_state);
ensure_scene_encounter_preview(game_state);
Ok(StoryResolution {
action_text: resolve_action_text("继续向前探索", request),
result_text: "你没有停在原地,而是继续向前压,把下一段遭遇主动推到自己面前。".to_string(),
story_text: None,
presentation_options: Some(resolve_post_battle_story_options(game_state)),
saved_current_story: None,
patches: vec![build_status_patch(game_state)],
battle: None,
toast: None,
})
}
fn resolve_idle_travel_next_scene_action(
game_state: &mut Value,
request: &RuntimeStoryActionRequest,
) -> Result<StoryResolution, String> {
// 中文注释:切场景会改变 currentScenePreset、章节 act 状态和运行统计,
// 这些都是 runtime 快照真相,不能只在前端播放退场/进场动画。
let payload = request.action.payload.as_ref();
let target_scene_id = payload
.and_then(|payload| read_optional_string_field(payload, "targetSceneId"))
.or_else(|| resolve_forward_scene_id(game_state))
.ok_or_else(|| "idle_travel_next_scene 缺少 targetSceneId".to_string())?;
let next_scene = resolve_runtime_scene_preset(game_state, target_scene_id.as_str())
.ok_or_else(|| format!("未找到目标场景:{target_scene_id}"))?;
let next_scene_name =
read_optional_string_field(&next_scene, "name").unwrap_or_else(|| target_scene_id.clone());
clear_post_battle_state(game_state);
ensure_json_object(game_state).insert("currentScenePreset".to_string(), next_scene);
write_i32_field(game_state, "playerX", 0);
write_string_field(game_state, "playerFacing", "right");
ensure_scene_act_state(game_state);
ensure_scene_encounter_preview(game_state);
increment_runtime_stat_local(game_state, "scenesTraveled", 1);
Ok(StoryResolution {
action_text: resolve_action_text(&format!("前往{next_scene_name}"), request),
result_text: format!("你离开当前区域,抵达了{next_scene_name}。"),
story_text: None,
presentation_options: Some(resolve_post_battle_story_options(game_state)),
saved_current_story: None,
patches: vec![build_status_patch(game_state)],
battle: None,
toast: None,
})
}
fn resolve_npc_preview_talk_action(
game_state: &mut Value,
request: &RuntimeStoryActionRequest,