178 lines
6.6 KiB
Rust
178 lines
6.6 KiB
Rust
//! 运行时物品应用编排。
|
|
//!
|
|
//! 这里只返回奖励结果、记录快照和待写入背包事件。
|
|
|
|
use crate::commands::TreasureResolveInput;
|
|
use crate::domain::{
|
|
RuntimeItemEquipmentSlot, RuntimeItemRewardItemRarity, RuntimeItemRewardItemSnapshot,
|
|
TreasureRecordSnapshot,
|
|
};
|
|
use crate::errors::TreasureFieldError;
|
|
use module_inventory::{
|
|
InventoryEquipmentSlot, InventoryItemRarity, InventoryItemSnapshot, InventoryItemSourceKind,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use shared_kernel::{
|
|
normalize_optional_string as normalize_shared_optional_string, normalize_required_string,
|
|
normalize_string_list as normalize_shared_string_list,
|
|
};
|
|
#[cfg(feature = "spacetime-types")]
|
|
use spacetimedb::SpacetimeType;
|
|
|
|
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct TreasureRecordProcedureResult {
|
|
pub ok: bool,
|
|
pub record: Option<TreasureRecordSnapshot>,
|
|
pub error_message: Option<String>,
|
|
}
|
|
|
|
pub fn build_treasure_record_snapshot(
|
|
input: TreasureResolveInput,
|
|
) -> Result<TreasureRecordSnapshot, TreasureFieldError> {
|
|
validate_treasure_input(&input)?;
|
|
|
|
Ok(TreasureRecordSnapshot {
|
|
treasure_record_id: input.treasure_record_id,
|
|
runtime_session_id: input.runtime_session_id,
|
|
story_session_id: input.story_session_id,
|
|
actor_user_id: input.actor_user_id,
|
|
encounter_id: input.encounter_id,
|
|
encounter_name: input.encounter_name,
|
|
scene_id: normalize_optional_value(input.scene_id),
|
|
scene_name: normalize_optional_value(input.scene_name),
|
|
action: input.action,
|
|
reward_items: input
|
|
.reward_items
|
|
.into_iter()
|
|
.map(normalize_reward_item)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
reward_hp: input.reward_hp,
|
|
reward_mana: input.reward_mana,
|
|
reward_currency: input.reward_currency,
|
|
story_hint: normalize_optional_value(input.story_hint),
|
|
created_at_micros: input.created_at_micros,
|
|
updated_at_micros: input.updated_at_micros,
|
|
})
|
|
}
|
|
|
|
pub fn build_inventory_item_snapshot_from_reward_item(
|
|
treasure_record_id: &str,
|
|
reward_item: RuntimeItemRewardItemSnapshot,
|
|
) -> Result<InventoryItemSnapshot, TreasureFieldError> {
|
|
let treasure_record_id = normalize_required_value(
|
|
treasure_record_id.to_string(),
|
|
TreasureFieldError::MissingTreasureRecordId,
|
|
)?;
|
|
let reward_item = normalize_reward_item(reward_item)?;
|
|
|
|
Ok(InventoryItemSnapshot {
|
|
item_id: reward_item.item_id,
|
|
category: reward_item.category,
|
|
name: reward_item.item_name,
|
|
description: reward_item.description,
|
|
quantity: reward_item.quantity,
|
|
rarity: map_reward_item_rarity(reward_item.rarity),
|
|
tags: reward_item.tags,
|
|
stackable: reward_item.stackable,
|
|
stack_key: reward_item.stack_key,
|
|
equipment_slot_id: reward_item
|
|
.equipment_slot_id
|
|
.map(map_reward_item_equipment_slot),
|
|
source_kind: InventoryItemSourceKind::TreasureReward,
|
|
source_reference_id: Some(treasure_record_id),
|
|
})
|
|
}
|
|
|
|
pub fn normalize_reward_item_snapshot(
|
|
reward_item: RuntimeItemRewardItemSnapshot,
|
|
) -> Result<RuntimeItemRewardItemSnapshot, TreasureFieldError> {
|
|
normalize_reward_item(reward_item)
|
|
}
|
|
|
|
fn validate_treasure_input(input: &TreasureResolveInput) -> Result<(), TreasureFieldError> {
|
|
if input.treasure_record_id.trim().is_empty() {
|
|
return Err(TreasureFieldError::MissingTreasureRecordId);
|
|
}
|
|
if input.runtime_session_id.trim().is_empty() {
|
|
return Err(TreasureFieldError::MissingRuntimeSessionId);
|
|
}
|
|
if input.story_session_id.trim().is_empty() {
|
|
return Err(TreasureFieldError::MissingStorySessionId);
|
|
}
|
|
if input.actor_user_id.trim().is_empty() {
|
|
return Err(TreasureFieldError::MissingActorUserId);
|
|
}
|
|
if input.encounter_id.trim().is_empty() {
|
|
return Err(TreasureFieldError::MissingEncounterId);
|
|
}
|
|
if input.encounter_name.trim().is_empty() {
|
|
return Err(TreasureFieldError::MissingEncounterName);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn normalize_optional_value(value: Option<String>) -> Option<String> {
|
|
normalize_shared_optional_string(value)
|
|
}
|
|
|
|
fn normalize_reward_item(
|
|
mut item: RuntimeItemRewardItemSnapshot,
|
|
) -> Result<RuntimeItemRewardItemSnapshot, TreasureFieldError> {
|
|
item.item_id = normalize_required_value(item.item_id, TreasureFieldError::MissingRewardItemId)?;
|
|
item.category =
|
|
normalize_required_value(item.category, TreasureFieldError::MissingRewardItemCategory)?;
|
|
item.item_name =
|
|
normalize_required_value(item.item_name, TreasureFieldError::MissingRewardItemName)?;
|
|
item.description = normalize_optional_value(item.description);
|
|
if item.quantity == 0 {
|
|
return Err(TreasureFieldError::InvalidRewardItemQuantity);
|
|
}
|
|
if !item.stackable && item.quantity != 1 {
|
|
return Err(TreasureFieldError::RewardNonStackableItemMustStaySingleQuantity);
|
|
}
|
|
if item.equipment_slot_id.is_some() && item.stackable {
|
|
return Err(TreasureFieldError::RewardEquipmentItemCannotStack);
|
|
}
|
|
item.tags = normalize_string_list(item.tags);
|
|
item.stack_key = if item.stackable {
|
|
normalize_required_value(
|
|
item.stack_key,
|
|
TreasureFieldError::MissingRewardItemStackKey,
|
|
)?
|
|
} else {
|
|
normalize_optional_value(Some(item.stack_key)).unwrap_or_else(|| item.item_id.clone())
|
|
};
|
|
Ok(item)
|
|
}
|
|
|
|
fn normalize_required_value(
|
|
value: String,
|
|
error: TreasureFieldError,
|
|
) -> Result<String, TreasureFieldError> {
|
|
normalize_required_string(value).ok_or(error)
|
|
}
|
|
|
|
fn normalize_string_list(values: Vec<String>) -> Vec<String> {
|
|
normalize_shared_string_list(values)
|
|
}
|
|
|
|
fn map_reward_item_rarity(rarity: RuntimeItemRewardItemRarity) -> InventoryItemRarity {
|
|
match rarity {
|
|
RuntimeItemRewardItemRarity::Common => InventoryItemRarity::Common,
|
|
RuntimeItemRewardItemRarity::Uncommon => InventoryItemRarity::Uncommon,
|
|
RuntimeItemRewardItemRarity::Rare => InventoryItemRarity::Rare,
|
|
RuntimeItemRewardItemRarity::Epic => InventoryItemRarity::Epic,
|
|
RuntimeItemRewardItemRarity::Legendary => InventoryItemRarity::Legendary,
|
|
}
|
|
}
|
|
|
|
fn map_reward_item_equipment_slot(slot: RuntimeItemEquipmentSlot) -> InventoryEquipmentSlot {
|
|
match slot {
|
|
RuntimeItemEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
|
|
RuntimeItemEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
|
|
RuntimeItemEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
|
|
}
|
|
}
|