use serde_json::Value; use shared_contracts::runtime_story::RuntimeStoryActionRequest; use crate::{ StoryResolution, add_inventory_items_to_list, build_current_build_toast, current_world_type, ensure_inventory_action_available, find_player_inventory_entry, read_i32_field, read_inventory_item_name, read_optional_string_field, read_player_inventory_values, remove_inventory_item_from_list, resolve_action_text, resolve_equipment_slot_for_item, write_i32_field, write_player_inventory_values, }; use super::forge::{ apply_forge_requirements_if_possible, build_dismantle_outputs, build_forge_recipe_result_item, build_forge_success_text, build_reforged_item, forge_recipe_definition, format_currency_text, reforge_cost_definition, }; /// 锻造动作编排已经不再依赖 `api-server` 的 HTTP 边界。 /// /// 这里继续沿用 compat 快照态结算,后续可直接被 `api-server` 外壳或真相态桥接层复用。 pub fn resolve_forge_craft_action( game_state: &mut Value, request: &RuntimeStoryActionRequest, ) -> Result { ensure_inventory_action_available( game_state, "缺少玩家角色,无法执行锻造配方。", "战斗中无法使用工坊。", )?; let recipe_id = request .action .payload .as_ref() .and_then(|payload| read_optional_string_field(payload, "recipeId")) .or_else(|| request.action.target_id.clone()) .ok_or_else(|| "forge_craft 缺少 recipeId".to_string())?; let recipe = forge_recipe_definition(recipe_id.as_str()) .ok_or_else(|| "未找到目标锻造配方。".to_string())?; let player_currency = read_i32_field(game_state, "playerCurrency").unwrap_or(0); if player_currency < recipe.currency_cost { return Err(format!("{} 当前材料或货币不足。", recipe.name)); } let current_inventory = read_player_inventory_values(game_state); let consumed_inventory = apply_forge_requirements_if_possible( current_inventory.as_slice(), recipe.requirements.as_slice(), ) .ok_or_else(|| format!("{} 当前材料或货币不足。", recipe.name))?; let created_item = build_forge_recipe_result_item( game_state, recipe.id, current_world_type(game_state).as_deref(), ); let next_inventory = add_inventory_items_to_list(consumed_inventory, vec![created_item.clone()]); write_i32_field( game_state, "playerCurrency", player_currency.saturating_sub(recipe.currency_cost), ); write_player_inventory_values(game_state, next_inventory); Ok(StoryResolution { action_text: resolve_action_text( &format!("制作{}", read_inventory_item_name(&created_item)), request, ), result_text: build_forge_success_text( "craft", Some(recipe.name), None, Some(read_inventory_item_name(&created_item).as_str()), &[], Some(format_currency_text( recipe.currency_cost, current_world_type(game_state).as_deref(), )), ), story_text: None, presentation_options: None, saved_current_story: None, patches: Vec::new(), battle: None, toast: Some(build_current_build_toast(game_state)), }) } pub fn resolve_forge_dismantle_action( game_state: &mut Value, request: &RuntimeStoryActionRequest, ) -> Result { ensure_inventory_action_available( game_state, "缺少玩家角色,无法执行拆解。", "战斗中无法执行拆解。", )?; let item_id = request .action .payload .as_ref() .and_then(|payload| read_optional_string_field(payload, "itemId")) .or_else(|| request.action.target_id.clone()) .ok_or_else(|| "forge_dismantle 缺少 itemId".to_string())?; let item = find_player_inventory_entry(game_state, item_id.as_str()) .cloned() .ok_or_else(|| "未找到可拆解的物品。".to_string())?; if read_i32_field(&item, "quantity").unwrap_or(0) <= 0 { return Err("未找到可拆解的物品。".to_string()); } let outputs = build_dismantle_outputs(game_state, &item) .ok_or_else(|| format!("{} 当前不支持拆解。", read_inventory_item_name(&item)))?; let mut next_inventory = read_player_inventory_values(game_state); next_inventory = remove_inventory_item_from_list(next_inventory, item_id.as_str(), 1); next_inventory = add_inventory_items_to_list(next_inventory, outputs.clone()); write_player_inventory_values(game_state, next_inventory); let output_names = outputs .iter() .map(read_inventory_item_name) .collect::>(); Ok(StoryResolution { action_text: resolve_action_text( &format!("拆解{}", read_inventory_item_name(&item)), request, ), result_text: build_forge_success_text( "dismantle", None, Some(read_inventory_item_name(&item).as_str()), None, output_names.as_slice(), None, ), story_text: None, presentation_options: None, saved_current_story: None, patches: Vec::new(), battle: None, toast: Some(build_current_build_toast(game_state)), }) } pub fn resolve_forge_reforge_action( game_state: &mut Value, request: &RuntimeStoryActionRequest, ) -> Result { ensure_inventory_action_available( game_state, "缺少玩家角色,无法执行重铸。", "战斗中无法执行重铸。", )?; let item_id = request .action .payload .as_ref() .and_then(|payload| read_optional_string_field(payload, "itemId")) .or_else(|| request.action.target_id.clone()) .ok_or_else(|| "forge_reforge 缺少 itemId".to_string())?; let item = find_player_inventory_entry(game_state, item_id.as_str()) .cloned() .ok_or_else(|| "未找到可重铸的物品。".to_string())?; if read_i32_field(&item, "quantity").unwrap_or(0) <= 0 { return Err("未找到可重铸的物品。".to_string()); } let slot_id = resolve_equipment_slot_for_item(&item); let reforge_cost = reforge_cost_definition(slot_id); let player_currency = read_i32_field(game_state, "playerCurrency").unwrap_or(0); if player_currency < reforge_cost.currency_cost { return Err(format!( "{} 当前不满足重铸条件。", read_inventory_item_name(&item) )); } let reforged_item = build_reforged_item(game_state, &item) .ok_or_else(|| format!("{} 当前不满足重铸条件。", read_inventory_item_name(&item)))?; let base_inventory = remove_inventory_item_from_list( read_player_inventory_values(game_state), item_id.as_str(), 1, ); let consumed_inventory = apply_forge_requirements_if_possible( base_inventory.as_slice(), reforge_cost.requirements.as_slice(), ) .ok_or_else(|| format!("{} 当前不满足重铸条件。", read_inventory_item_name(&item)))?; let next_inventory = add_inventory_items_to_list(consumed_inventory, vec![reforged_item.clone()]); write_player_inventory_values(game_state, next_inventory); write_i32_field( game_state, "playerCurrency", player_currency.saturating_sub(reforge_cost.currency_cost), ); Ok(StoryResolution { action_text: resolve_action_text( &format!("重铸{}", read_inventory_item_name(&item)), request, ), result_text: build_forge_success_text( "reforge", None, Some(read_inventory_item_name(&item).as_str()), Some(read_inventory_item_name(&reforged_item).as_str()), &[], Some(format_currency_text( reforge_cost.currency_cost, current_world_type(game_state).as_deref(), )), ), story_text: None, presentation_options: None, saved_current_story: None, patches: Vec::new(), battle: None, toast: Some(build_current_build_toast(game_state)), }) }