use std::convert::TryInto; use futures::future::join_all; use thiserror::Error; use anyhow::Context; use rand::SeedableRng; use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::{CharacterEntity, TechLevel}; use crate::entity::item::mag::{MagCell, MagCellError}; use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::tech::TechniqueDisk; use crate::entity::item::{ItemDetail, ItemEntityId}; use crate::entity::item::weapon::WeaponModifier; use crate::ship::items::state::ItemStateProxy; use crate::ship::items::inventory::InventoryItemDetail; #[derive(Error, Debug)] pub enum ApplyItemError { #[error("no character")] NoCharacter, #[error("item not equipped")] ItemNotEquipped, #[error("could not use item invalid item")] InvalidItem, #[error("invalid tool")] InvalidTool, #[error("gateway error {0}")] GatewayError(#[from] GatewayError), #[error("magcell error {0}")] MagCellError(#[from] MagCellError), } #[derive(Debug, Clone)] pub enum ApplyItemAction { UpdateCharacter(Box), CreateItem(ItemDetail), //TransformItem(ItemDetail), //RemoveItem, } async fn power_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.power += 1; entity_gateway.save_character(character).await?; Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } async fn mind_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.mind += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } async fn evade_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.evade += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } async fn def_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.def += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } async fn luck_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.luck += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } async fn hp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.hp += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } async fn tp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.tp += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } /* async fn mag_cell(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory, mag_cell_type: MagCell) -> Result<(), ApplyItemError> { let mut mag_handle = inventory.get_equipped_mag_handle().ok_or(ApplyItemError::ItemNotEquipped)?; let mag_item = mag_handle.item_mut() .ok_or(ApplyItemError::InvalidItem)?; let actual_mag = mag_item .individual_mut() .ok_or(ApplyItemError::InvalidItem)? .mag_mut() .ok_or(ApplyItemError::InvalidItem)?; actual_mag.apply_mag_cell(mag_cell_type); for mag_entity_id in mag_item.entity_ids() { for cell_entity_id in used_cell.entity_ids() { entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await.unwrap(); } } Ok(()) } */ async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &CharacterEntity, cell_entity_id: ItemEntityId, mag_cell_type: MagCell) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized, { let mut inventory = item_state.inventory(&character.id).await?; let (mag_entity_id, mag) = inventory.equipped_mag_mut() .ok_or(ApplyItemError::ItemNotEquipped)?; mag.apply_mag_cell(mag_cell_type)?; entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await?; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; item_state.set_inventory(inventory).await; Ok(Vec::new()) } /* pub async fn cell_of_mag_502(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, inventory, MagCell::CellOfMag502).await } pub async fn cell_of_mag_213(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag213).await } pub async fn parts_of_robochao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::PartsOfRobochao).await } pub async fn heart_of_opaopa(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfOpaOpa).await } pub async fn heart_of_pian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfPian).await } pub async fn heart_of_chao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfChao).await } pub async fn heart_of_angel(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfAngel).await } pub async fn kit_of_hamburger(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfHamburger).await } pub async fn panthers_spirit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::PanthersSpirit).await } pub async fn kit_of_mark3(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMark3).await } pub async fn kit_of_master_system(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMasterSystem).await } pub async fn kit_of_genesis(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfGenesis).await } pub async fn kit_of_sega_saturn(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfSegaSaturn).await } pub async fn kit_of_dreamcast(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfDreamcast).await } pub async fn tablet(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::Tablet).await } pub async fn dragon_scale(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::DragonScale).await } pub async fn heaven_striker_coat(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::HeavenStrikerCoat).await } pub async fn pioneer_parts(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::PioneerParts).await } pub async fn amities_memo(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::AmitiesMemo).await } pub async fn heart_of_morolian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfMorolian).await } pub async fn rappys_beak(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::RappysBeak).await } pub async fn yahoos_engine(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::YahoosEngine).await } pub async fn d_photon_core(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::DPhotonCore).await } pub async fn liberta_kit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { mag_cell(entity_gateway, used_cell, inventory, MagCell::LibertaKit).await } */ fn jack_o_lantern() -> Result, anyhow::Error> { let mag_rate = WeightedIndex::new(&[13, 13, 13, 13, 12, 12, 12, 12]).unwrap(); let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) { 0 => ToolType::CellOfMag502, 1 => ToolType::CellOfMag213, 2 => ToolType::HeartOfChuChu, 3 => ToolType::HeartOfKapuKapu, 4 => ToolType::PartsOfRobochao, 5 => ToolType::HeartOfOpaOpa, 6 => ToolType::HeartOfPian, 7 => ToolType::HeartOfChao, _ => unreachable!(), }; Ok(vec![ApplyItemAction::CreateItem(ItemDetail::Tool(Tool {tool: mag_type}))]) } async fn weapon_grind<'a, EG>(item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &mut CharacterEntity, entity_id: ItemEntityId, grind: u32,) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized, { let modifier = WeaponModifier::AddGrind { amount: grind, grinder: entity_id, }; let mut inventory = item_state.inventory(&character.id).await?; let (weapon_entity_id, weapon) = inventory.equipped_weapon_mut() .ok_or(ApplyItemError::ItemNotEquipped)?; weapon.apply_modifier(&modifier); entity_gateway.add_weapon_modifier(&weapon_entity_id, &modifier).await?; item_state.set_inventory(inventory).await; Ok(Vec::new()) } async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &mut CharacterEntity, entity_id: ItemEntityId, tool: ToolType) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized, { match tool { ToolType::PowerMaterial => power_material(entity_gateway, character).await, ToolType::MindMaterial => mind_material(entity_gateway, character).await, ToolType::EvadeMaterial => evade_material(entity_gateway, character).await, ToolType::DefMaterial => def_material(entity_gateway, character).await, ToolType::LuckMaterial => luck_material(entity_gateway, character).await, ToolType::HpMaterial => hp_material(entity_gateway, character).await, ToolType::TpMaterial => tp_material(entity_gateway, character).await, ToolType::Monomate => Ok(Vec::new()), ToolType::Dimate => Ok(Vec::new()), ToolType::Trimate => Ok(Vec::new()), ToolType::Monofluid => Ok(Vec::new()), ToolType::Difluid => Ok(Vec::new()), ToolType::Trifluid => Ok(Vec::new()), ToolType::SolAtomizer => Ok(Vec::new()), ToolType::MoonAtomizer => Ok(Vec::new()), ToolType::StarAtomizer => Ok(Vec::new()), ToolType::Telepipe => Ok(Vec::new()), ToolType::Antidote => Ok(Vec::new()), ToolType::Antiparalysis => Ok(Vec::new()), ToolType::TrapVision => Ok(Vec::new()), ToolType::ScapeDoll => Ok(Vec::new()), ToolType::Monogrinder => weapon_grind(item_state, entity_gateway, character, entity_id, 1).await, ToolType::Digrinder => weapon_grind(item_state, entity_gateway, character, entity_id, 2).await, ToolType::Trigrinder => weapon_grind(item_state, entity_gateway, character, entity_id, 3).await, ToolType::HuntersReport => Ok(Vec::new()), ToolType::CellOfMag502 | ToolType::CellOfMag213 | ToolType::PartsOfRobochao | ToolType::HeartOfOpaOpa | ToolType::HeartOfPian | ToolType::HeartOfChao | ToolType::HeartOfAngel | ToolType::KitOfHamburger | ToolType::PanthersSpirit | ToolType::KitOfMark3 | ToolType::KitOfMasterSystem | ToolType::KitOfGenesis | ToolType::KitOfSegaSaturn | ToolType::KitOfDreamcast | ToolType::Tablet | ToolType::DragonScale | ToolType::HeavenStrikerCoat | ToolType::PioneerParts | ToolType::AmitiesMemo | ToolType::HeartOfMorolian | ToolType::RappysBeak | ToolType::YahoosEngine | ToolType::DPhotonCore | ToolType::LibertaKit => { mag_cell(item_state, entity_gateway, character, entity_id, tool.try_into()?).await } ToolType::JackOLantern => jack_o_lantern(), // TODO: rest of these _ => Err(anyhow::Error::from(ApplyItemError::InvalidTool)) .with_context(|| { format!("invalid tool {tool:?}") }) } } async fn apply_tech<'a, EG>(_item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &mut CharacterEntity, _entity_id: ItemEntityId, tech: TechniqueDisk) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized, { // TODO: make sure the class can learn that specific tech character.techs.set_tech(tech.tech, TechLevel(tech.level as u8)); entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &mut CharacterEntity, item: InventoryItemDetail ) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized + Clone + 'static { match item { InventoryItemDetail::Individual(individual_item) => { match individual_item.item { ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await, ItemDetail::TechniqueDisk(tech) => apply_tech(item_state, entity_gateway, character, individual_item.entity_id, tech).await, _ => Err(anyhow::Error::from(ApplyItemError::InvalidItem)) .with_context(|| { format!("item {individual_item:?}") }) } }, InventoryItemDetail::Stacked(stacked_item) => { Ok(join_all(stacked_item.entity_ids.iter() .map(|entity_id| { let mut entity_gateway = entity_gateway.clone(); let mut character = character.clone(); let mut item_state = item_state.clone(); async move { apply_tool(&mut item_state, &mut entity_gateway, &mut character, *entity_id, stacked_item.tool.tool).await } }) .collect::>()) .await .into_iter() .collect::>, _>>()? .into_iter() .flatten() .collect()) }, } }