Close DDD refactor and remove generated asset proxy

This commit is contained in:
kdletters
2026-05-02 00:27:22 +08:00
parent fd08262bf0
commit 9d9913095d
605 changed files with 11811 additions and 10106 deletions

View File

@@ -7,6 +7,22 @@ use crate::commands::{StoryContinueInput, StorySessionInput, normalize_optional_
use crate::domain::{INITIAL_STORY_SESSION_VERSION, StorySessionSnapshot, StorySessionStatus};
use crate::errors::StorySessionFieldError;
use crate::events::{StoryEventKind, StoryEventSnapshot};
use module_combat::{BattleStateSnapshot, CombatOutcome};
use module_inventory::{
GrantInventoryItemInput, InventoryEquipmentSlot, InventoryItemRarity, InventoryItemSnapshot,
InventoryItemSourceKind, InventoryMutation, InventoryMutationInput,
generate_inventory_mutation_id, generate_inventory_slot_id,
};
use module_progression::{
ChapterProgressionLedgerInput, PlayerProgressionGrantInput, PlayerProgressionGrantSource,
};
use module_quest::{
QuestRecordSnapshot, QuestRewardEquipmentSlot, QuestRewardItem, QuestRewardItemRarity,
};
use module_runtime_item::{
RuntimeItemEquipmentSlot, RuntimeItemRewardItemRarity, RuntimeItemRewardItemSnapshot,
TreasureRecordSnapshot, build_inventory_item_snapshot_from_reward_item,
};
use serde::{Deserialize, Serialize};
use shared_kernel::format_timestamp_micros;
#[cfg(feature = "spacetime-types")]
@@ -68,6 +84,17 @@ pub struct StorySessionStateRecord {
pub events: Vec<StoryEventRecord>,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RpgGameplaySettlementPlan {
/// 背包写入计划。adapter 只负责读取当前槽位并执行 mutation不再拼奖励物品字段。
pub inventory_mutations: Vec<InventoryMutationInput>,
/// 玩家经验入账计划。章节账本需要依赖执行后的等级结果,所以单独保留。
pub progression_grant: Option<PlayerProgressionGrantInput>,
/// 章节实际收益账本计划。若章节预算尚未初始化adapter 会跳过而不阻断主链。
pub chapter_ledger: Option<ChapterProgressionLedgerInput>,
}
pub fn build_story_session_snapshot(input: StorySessionInput) -> StorySessionSnapshot {
StorySessionSnapshot {
story_session_id: input.story_session_id,
@@ -165,3 +192,272 @@ pub fn build_story_session_state_record(
.collect::<Vec<_>>(),
}
}
pub fn build_combat_victory_settlement_plan(
snapshot: &BattleStateSnapshot,
) -> RpgGameplaySettlementPlan {
// 非胜利结果不触发战利品、敌对经验或章节 hostile 账本,避免逃脱/切磋混入击杀收益。
if snapshot.last_outcome != CombatOutcome::Victory {
return empty_settlement_plan();
}
let inventory_mutations = snapshot
.reward_items
.iter()
.cloned()
.enumerate()
.map(|(index, reward_item)| {
let seed = build_reward_seed(snapshot.updated_at_micros, index);
InventoryMutationInput {
mutation_id: generate_inventory_mutation_id(seed),
runtime_session_id: snapshot.runtime_session_id.clone(),
story_session_id: Some(snapshot.story_session_id.clone()),
actor_user_id: snapshot.actor_user_id.clone(),
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput {
slot_id: generate_inventory_slot_id(seed),
item: build_inventory_item_snapshot_from_battle_reward_item(
&snapshot.battle_state_id,
reward_item,
),
}),
updated_at_micros: snapshot.updated_at_micros,
}
})
.collect::<Vec<_>>();
let progression_grant = (snapshot.experience_reward > 0).then(|| PlayerProgressionGrantInput {
user_id: snapshot.actor_user_id.clone(),
amount: snapshot.experience_reward,
source: PlayerProgressionGrantSource::HostileNpc,
updated_at_micros: snapshot.updated_at_micros,
});
let chapter_ledger =
progression_grant
.as_ref()
.and_then(|_| match snapshot.chapter_id.as_deref() {
Some(chapter_id) if !chapter_id.trim().is_empty() => {
Some(ChapterProgressionLedgerInput {
user_id: snapshot.actor_user_id.clone(),
chapter_id: chapter_id.trim().to_string(),
granted_quest_xp: 0,
granted_hostile_xp: snapshot.experience_reward,
hostile_defeat_increment: 1,
level_at_exit: None,
updated_at_micros: snapshot.updated_at_micros,
})
}
_ => None,
});
RpgGameplaySettlementPlan {
inventory_mutations,
progression_grant,
chapter_ledger,
}
}
/// 任务交付后只生成结算计划,不在领域层直接写背包、成长或章节表。
pub fn build_quest_turn_in_settlement_plan(
snapshot: &QuestRecordSnapshot,
) -> RpgGameplaySettlementPlan {
let inventory_mutations = snapshot
.reward
.items
.iter()
.cloned()
.enumerate()
.map(|(index, reward_item)| {
let seed = build_reward_seed(snapshot.updated_at_micros, index);
InventoryMutationInput {
mutation_id: generate_inventory_mutation_id(seed),
runtime_session_id: snapshot.runtime_session_id.clone(),
story_session_id: snapshot.story_session_id.clone(),
actor_user_id: snapshot.actor_user_id.clone(),
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput {
slot_id: generate_inventory_slot_id(seed),
item: build_inventory_item_snapshot_from_quest_reward_item(
&snapshot.quest_id,
reward_item,
),
}),
updated_at_micros: snapshot.updated_at_micros,
}
})
.collect::<Vec<_>>();
let reward_experience = snapshot.reward.experience.unwrap_or(0);
let progression_grant = (reward_experience > 0).then(|| PlayerProgressionGrantInput {
user_id: snapshot.actor_user_id.clone(),
amount: reward_experience,
source: PlayerProgressionGrantSource::Quest,
updated_at_micros: snapshot.updated_at_micros,
});
let chapter_ledger =
progression_grant
.as_ref()
.and_then(|_| match snapshot.chapter_id.as_deref() {
Some(chapter_id) if !chapter_id.trim().is_empty() => {
Some(ChapterProgressionLedgerInput {
user_id: snapshot.actor_user_id.clone(),
chapter_id: chapter_id.trim().to_string(),
granted_quest_xp: reward_experience,
granted_hostile_xp: 0,
hostile_defeat_increment: 0,
level_at_exit: None,
updated_at_micros: snapshot.updated_at_micros,
})
}
_ => None,
});
RpgGameplaySettlementPlan {
inventory_mutations,
progression_grant,
chapter_ledger,
}
}
/// 宝箱记录由 `module-runtime-item` 建模,这里只把奖励转成 story gameplay 的背包写入计划。
pub fn build_treasure_settlement_plan(
snapshot: &TreasureRecordSnapshot,
) -> Result<RpgGameplaySettlementPlan, StorySessionFieldError> {
let inventory_mutations = snapshot
.reward_items
.iter()
.cloned()
.enumerate()
.map(
|(index, reward_item)| -> Result<InventoryMutationInput, StorySessionFieldError> {
Ok(InventoryMutationInput {
mutation_id: build_treasure_inventory_mutation_id(
&snapshot.treasure_record_id,
index,
),
runtime_session_id: snapshot.runtime_session_id.clone(),
story_session_id: Some(snapshot.story_session_id.clone()),
actor_user_id: snapshot.actor_user_id.clone(),
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput {
slot_id: build_treasure_inventory_slot_id(
&snapshot.treasure_record_id,
index,
),
item: build_inventory_item_snapshot_from_reward_item(
&snapshot.treasure_record_id,
reward_item,
)
.map_err(|_| StorySessionFieldError::InvalidGameplayReward)?,
}),
updated_at_micros: snapshot.updated_at_micros,
})
},
)
.collect::<Result<Vec<_>, _>>()?;
Ok(RpgGameplaySettlementPlan {
inventory_mutations,
progression_grant: None,
chapter_ledger: None,
})
}
fn empty_settlement_plan() -> RpgGameplaySettlementPlan {
RpgGameplaySettlementPlan {
inventory_mutations: Vec::new(),
progression_grant: None,
chapter_ledger: None,
}
}
fn build_inventory_item_snapshot_from_battle_reward_item(
battle_state_id: &str,
reward_item: RuntimeItemRewardItemSnapshot,
) -> InventoryItemSnapshot {
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_runtime_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_runtime_reward_equipment_slot),
source_kind: InventoryItemSourceKind::CombatDrop,
source_reference_id: Some(battle_state_id.to_string()),
}
}
fn build_inventory_item_snapshot_from_quest_reward_item(
quest_id: &str,
reward_item: QuestRewardItem,
) -> InventoryItemSnapshot {
InventoryItemSnapshot {
item_id: reward_item.item_id,
category: reward_item.category,
name: reward_item.name,
description: reward_item.description,
quantity: reward_item.quantity,
rarity: map_quest_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_quest_reward_equipment_slot),
source_kind: InventoryItemSourceKind::QuestReward,
source_reference_id: Some(quest_id.to_string()),
}
}
fn map_quest_reward_item_rarity(rarity: QuestRewardItemRarity) -> InventoryItemRarity {
match rarity {
QuestRewardItemRarity::Common => InventoryItemRarity::Common,
QuestRewardItemRarity::Uncommon => InventoryItemRarity::Uncommon,
QuestRewardItemRarity::Rare => InventoryItemRarity::Rare,
QuestRewardItemRarity::Epic => InventoryItemRarity::Epic,
QuestRewardItemRarity::Legendary => InventoryItemRarity::Legendary,
}
}
fn map_runtime_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_quest_reward_equipment_slot(slot: QuestRewardEquipmentSlot) -> InventoryEquipmentSlot {
match slot {
QuestRewardEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
QuestRewardEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
QuestRewardEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
}
}
fn map_runtime_reward_equipment_slot(slot: RuntimeItemEquipmentSlot) -> InventoryEquipmentSlot {
match slot {
RuntimeItemEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
RuntimeItemEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
RuntimeItemEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
}
}
fn build_reward_seed(updated_at_micros: i64, index: usize) -> i64 {
updated_at_micros.saturating_add(index as i64 + 1)
}
fn build_treasure_inventory_slot_id(treasure_record_id: &str, reward_index: usize) -> String {
format!("invslot_{}_{}", treasure_record_id, reward_index)
}
fn build_treasure_inventory_mutation_id(treasure_record_id: &str, reward_index: usize) -> String {
format!("invmut_{}_{}", treasure_record_id, reward_index)
}