Close DDD refactor and remove generated asset proxy
This commit is contained in:
@@ -6,6 +6,10 @@ use module_quest::{
|
||||
acknowledge_quest_completion as acknowledge_quest_record_completion,
|
||||
apply_quest_signal as apply_quest_record_signal,
|
||||
};
|
||||
use module_story::{
|
||||
RpgGameplaySettlementPlan, build_combat_victory_settlement_plan,
|
||||
build_quest_turn_in_settlement_plan, build_treasure_settlement_plan,
|
||||
};
|
||||
|
||||
#[spacetimedb::table(accessor = player_progression)]
|
||||
pub struct PlayerProgression {
|
||||
@@ -619,37 +623,11 @@ fn resolve_battle_state_record(
|
||||
.battle_state()
|
||||
.insert(build_battle_state_row(result.snapshot.clone()));
|
||||
|
||||
if result.outcome == CombatOutcome::Victory {
|
||||
grant_battle_reward_items(ctx, &result.snapshot)?;
|
||||
|
||||
if result.snapshot.experience_reward > 0 {
|
||||
let updated_player = upsert_player_progression_after_grant_tx(
|
||||
ctx,
|
||||
PlayerProgressionGrantInput {
|
||||
user_id: result.snapshot.actor_user_id.clone(),
|
||||
amount: result.snapshot.experience_reward,
|
||||
source: PlayerProgressionGrantSource::HostileNpc,
|
||||
updated_at_micros: result.snapshot.updated_at_micros,
|
||||
},
|
||||
)?;
|
||||
|
||||
// 章节计划可能尚未初始化;此时不能阻断战斗胜利结算,只跳过章节账本写入。
|
||||
try_update_chapter_progression_ledger_tx(
|
||||
ctx,
|
||||
result.snapshot.actor_user_id.clone(),
|
||||
result.snapshot.chapter_id.clone(),
|
||||
ChapterProgressionLedgerInput {
|
||||
user_id: result.snapshot.actor_user_id.clone(),
|
||||
chapter_id: result.snapshot.chapter_id.clone().unwrap_or_default(),
|
||||
granted_quest_xp: 0,
|
||||
granted_hostile_xp: result.snapshot.experience_reward,
|
||||
hostile_defeat_increment: 1,
|
||||
level_at_exit: Some(updated_player.level),
|
||||
updated_at_micros: result.snapshot.updated_at_micros,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
apply_rpg_gameplay_settlement_plan(
|
||||
ctx,
|
||||
build_combat_victory_settlement_plan(&result.snapshot),
|
||||
Some(result.snapshot.battle_state_id.as_str()),
|
||||
)?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1094,35 +1072,11 @@ pub fn turn_in_quest(ctx: &ReducerContext, input: QuestTurnInInput) -> Result<()
|
||||
next.updated_at_micros,
|
||||
);
|
||||
|
||||
let reward_experience = next.reward.experience.unwrap_or(0);
|
||||
grant_quest_reward_items(ctx, &next)?;
|
||||
if reward_experience > 0 {
|
||||
let updated_player = upsert_player_progression_after_grant_tx(
|
||||
ctx,
|
||||
PlayerProgressionGrantInput {
|
||||
user_id: next.actor_user_id.clone(),
|
||||
amount: reward_experience,
|
||||
source: PlayerProgressionGrantSource::Quest,
|
||||
updated_at_micros: next.updated_at_micros,
|
||||
},
|
||||
)?;
|
||||
|
||||
// 章节计划缺失时先保持任务交付成功,避免成长联动反向阻断 quest 主链。
|
||||
try_update_chapter_progression_ledger_tx(
|
||||
ctx,
|
||||
next.actor_user_id.clone(),
|
||||
next.chapter_id.clone(),
|
||||
ChapterProgressionLedgerInput {
|
||||
user_id: next.actor_user_id.clone(),
|
||||
chapter_id: next.chapter_id.clone().unwrap_or_default(),
|
||||
granted_quest_xp: reward_experience,
|
||||
granted_hostile_xp: 0,
|
||||
hostile_defeat_increment: 0,
|
||||
level_at_exit: Some(updated_player.level),
|
||||
updated_at_micros: next.updated_at_micros,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
apply_rpg_gameplay_settlement_plan(
|
||||
ctx,
|
||||
build_quest_turn_in_settlement_plan(&next),
|
||||
Some(next.quest_id.as_str()),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1199,57 +1153,15 @@ fn upsert_treasure_record(
|
||||
.treasure_record()
|
||||
.insert(build_treasure_record_row(&snapshot, created_at, updated_at));
|
||||
|
||||
grant_treasure_reward_items_to_inventory(ctx, &snapshot)?;
|
||||
apply_rpg_gameplay_settlement_plan(
|
||||
ctx,
|
||||
build_treasure_settlement_plan(&snapshot).map_err(|error| error.to_string())?,
|
||||
Some(snapshot.treasure_record_id.as_str()),
|
||||
)?;
|
||||
|
||||
Ok(snapshot)
|
||||
}
|
||||
|
||||
fn grant_treasure_reward_items_to_inventory(
|
||||
ctx: &ReducerContext,
|
||||
snapshot: &TreasureRecordSnapshot,
|
||||
) -> Result<(), String> {
|
||||
for (index, reward_item) in snapshot.reward_items.iter().cloned().enumerate() {
|
||||
let inventory_item = build_inventory_item_snapshot_from_reward_item(
|
||||
&snapshot.treasure_record_id,
|
||||
reward_item,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let slot_id = build_treasure_inventory_slot_id(&snapshot.treasure_record_id, index);
|
||||
let mutation_id = build_treasure_inventory_mutation_id(&snapshot.treasure_record_id, index);
|
||||
|
||||
apply_inventory_mutation_tx(
|
||||
ctx,
|
||||
InventoryMutationInput {
|
||||
mutation_id,
|
||||
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(module_inventory::GrantInventoryItemInput {
|
||||
slot_id,
|
||||
item: inventory_item,
|
||||
}),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_treasure_inventory_slot_id(treasure_record_id: &str, reward_index: usize) -> String {
|
||||
format!(
|
||||
"{}{}_{}",
|
||||
INVENTORY_SLOT_ID_PREFIX, treasure_record_id, reward_index
|
||||
)
|
||||
}
|
||||
|
||||
fn build_treasure_inventory_mutation_id(treasure_record_id: &str, reward_index: usize) -> String {
|
||||
format!(
|
||||
"{}{}_{}",
|
||||
INVENTORY_MUTATION_ID_PREFIX, treasure_record_id, reward_index
|
||||
)
|
||||
}
|
||||
|
||||
fn build_treasure_record_row(
|
||||
snapshot: &TreasureRecordSnapshot,
|
||||
created_at: Timestamp,
|
||||
@@ -1496,214 +1408,69 @@ fn build_inventory_slot_snapshot_from_row(row: &InventorySlot) -> InventorySlotS
|
||||
}
|
||||
}
|
||||
|
||||
fn grant_quest_reward_items(
|
||||
fn apply_rpg_gameplay_settlement_plan(
|
||||
ctx: &ReducerContext,
|
||||
snapshot: &QuestRecordSnapshot,
|
||||
plan: RpgGameplaySettlementPlan,
|
||||
inventory_source_reference_id: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
if !ctx
|
||||
.db
|
||||
.inventory_slot()
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.runtime_session_id == snapshot.runtime_session_id
|
||||
&& row.actor_user_id == snapshot.actor_user_id
|
||||
})
|
||||
.all(|row| row.source_reference_id.as_deref() != Some(snapshot.quest_id.as_str()))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if !plan.inventory_mutations.is_empty() {
|
||||
if let Some(source_reference_id) = inventory_source_reference_id {
|
||||
if inventory_reward_source_already_granted(ctx, &plan, source_reference_id) {
|
||||
return apply_progression_and_chapter_settlement(ctx, plan);
|
||||
}
|
||||
}
|
||||
|
||||
for (index, reward_item) in snapshot.reward.items.clone().into_iter().enumerate() {
|
||||
let inventory_item =
|
||||
build_inventory_item_snapshot_from_quest_reward_item(&snapshot.quest_id, reward_item);
|
||||
grant_inventory_item_to_actor(
|
||||
ctx,
|
||||
&snapshot.runtime_session_id,
|
||||
snapshot.story_session_id.clone(),
|
||||
&snapshot.actor_user_id,
|
||||
inventory_item,
|
||||
build_reward_seed(snapshot.updated_at_micros, index),
|
||||
snapshot.updated_at_micros,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn grant_battle_reward_items(
|
||||
ctx: &ReducerContext,
|
||||
snapshot: &BattleStateSnapshot,
|
||||
) -> Result<(), String> {
|
||||
if snapshot.reward_items.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !ctx
|
||||
.db
|
||||
.inventory_slot()
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.runtime_session_id == snapshot.runtime_session_id
|
||||
&& row.actor_user_id == snapshot.actor_user_id
|
||||
})
|
||||
.all(|row| row.source_reference_id.as_deref() != Some(snapshot.battle_state_id.as_str()))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (index, reward_item) in snapshot.reward_items.clone().into_iter().enumerate() {
|
||||
let inventory_item = build_inventory_item_snapshot_from_battle_reward_item(
|
||||
&snapshot.battle_state_id,
|
||||
reward_item,
|
||||
);
|
||||
grant_inventory_item_to_actor(
|
||||
ctx,
|
||||
&snapshot.runtime_session_id,
|
||||
Some(snapshot.story_session_id.clone()),
|
||||
&snapshot.actor_user_id,
|
||||
inventory_item,
|
||||
build_reward_seed(snapshot.updated_at_micros, index),
|
||||
snapshot.updated_at_micros,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn grant_inventory_item_to_actor(
|
||||
ctx: &ReducerContext,
|
||||
runtime_session_id: &str,
|
||||
story_session_id: Option<String>,
|
||||
actor_user_id: &str,
|
||||
item: InventoryItemSnapshot,
|
||||
seed_micros: i64,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<(), String> {
|
||||
let current_slots = ctx
|
||||
.db
|
||||
.inventory_slot()
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.runtime_session_id == runtime_session_id && row.actor_user_id == actor_user_id
|
||||
})
|
||||
.map(|row| build_inventory_slot_snapshot_from_row(&row))
|
||||
.collect::<Vec<_>>();
|
||||
let slot_id = generate_inventory_slot_id(seed_micros);
|
||||
let mutation_id = generate_inventory_mutation_id(seed_micros);
|
||||
let outcome = apply_inventory_slot_mutation(
|
||||
current_slots,
|
||||
InventoryMutationInput {
|
||||
mutation_id,
|
||||
runtime_session_id: runtime_session_id.to_string(),
|
||||
story_session_id,
|
||||
actor_user_id: actor_user_id.to_string(),
|
||||
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput { slot_id, item }),
|
||||
updated_at_micros,
|
||||
},
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
for removed_slot_id in outcome.removed_slot_ids {
|
||||
ctx.db.inventory_slot().slot_id().delete(&removed_slot_id);
|
||||
}
|
||||
for slot in outcome.next_slots {
|
||||
ctx.db.inventory_slot().slot_id().delete(&slot.slot_id);
|
||||
ctx.db
|
||||
.inventory_slot()
|
||||
.insert(build_inventory_slot_row(slot));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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: module_runtime_item::RuntimeItemRewardItemRarity,
|
||||
) -> InventoryItemRarity {
|
||||
match rarity {
|
||||
module_runtime_item::RuntimeItemRewardItemRarity::Common => InventoryItemRarity::Common,
|
||||
module_runtime_item::RuntimeItemRewardItemRarity::Uncommon => InventoryItemRarity::Uncommon,
|
||||
module_runtime_item::RuntimeItemRewardItemRarity::Rare => InventoryItemRarity::Rare,
|
||||
module_runtime_item::RuntimeItemRewardItemRarity::Epic => InventoryItemRarity::Epic,
|
||||
module_runtime_item::RuntimeItemRewardItemRarity::Legendary => {
|
||||
InventoryItemRarity::Legendary
|
||||
for mutation in plan.inventory_mutations.clone() {
|
||||
apply_inventory_mutation_tx(ctx, mutation)?;
|
||||
}
|
||||
}
|
||||
|
||||
apply_progression_and_chapter_settlement(ctx, plan)
|
||||
}
|
||||
|
||||
fn map_quest_reward_equipment_slot(slot: QuestRewardEquipmentSlot) -> InventoryEquipmentSlot {
|
||||
match slot {
|
||||
QuestRewardEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
|
||||
QuestRewardEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
|
||||
QuestRewardEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
|
||||
fn inventory_reward_source_already_granted(
|
||||
ctx: &ReducerContext,
|
||||
plan: &RpgGameplaySettlementPlan,
|
||||
source_reference_id: &str,
|
||||
) -> bool {
|
||||
let Some(first_mutation) = plan.inventory_mutations.first() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
ctx.db
|
||||
.inventory_slot()
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.runtime_session_id == first_mutation.runtime_session_id
|
||||
&& row.actor_user_id == first_mutation.actor_user_id
|
||||
})
|
||||
.any(|row| row.source_reference_id.as_deref() == Some(source_reference_id))
|
||||
}
|
||||
|
||||
fn apply_progression_and_chapter_settlement(
|
||||
ctx: &ReducerContext,
|
||||
plan: RpgGameplaySettlementPlan,
|
||||
) -> Result<(), String> {
|
||||
let updated_player = match plan.progression_grant {
|
||||
Some(input) => Some(upsert_player_progression_after_grant_tx(ctx, input)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
if let Some(mut input) = plan.chapter_ledger {
|
||||
if let Some(player) = updated_player {
|
||||
input.level_at_exit = Some(player.level);
|
||||
}
|
||||
|
||||
// 章节计划可能尚未初始化;此时不能反向阻断战斗或任务主链。
|
||||
try_update_chapter_progression_ledger_tx(
|
||||
ctx,
|
||||
input.user_id.clone(),
|
||||
Some(input.chapter_id.clone()),
|
||||
input,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn map_runtime_reward_equipment_slot(
|
||||
slot: module_runtime_item::RuntimeItemEquipmentSlot,
|
||||
) -> InventoryEquipmentSlot {
|
||||
match slot {
|
||||
module_runtime_item::RuntimeItemEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
|
||||
module_runtime_item::RuntimeItemEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
|
||||
module_runtime_item::RuntimeItemEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_reward_seed(updated_at_micros: i64, index: usize) -> i64 {
|
||||
updated_at_micros.saturating_add(index as i64 + 1)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_story_session_snapshot_from_row(row: &StorySession) -> StorySessionSnapshot {
|
||||
|
||||
Reference in New Issue
Block a user