From d3d8d766b83a0a7c5957dff8643d5bdc513bc945 Mon Sep 17 00:00:00 2001 From: jake Date: Mon, 20 Jun 2022 15:40:30 -0600 Subject: [PATCH] magstuff --- src/entity/gateway/inmemory.rs | 2 +- src/entity/gateway/postgres/postgres.rs | 3 +- src/entity/item/mag.rs | 27 +++++-- src/entity/item/mod.rs | 3 +- src/ship/items/actions.rs | 72 ++++++++++++++++- src/ship/items/apply_item.rs | 100 +++++++++++++++++++++--- src/ship/items/state.rs | 69 +++++++++++++++- src/ship/packet/handler/message.rs | 6 +- src/ship/ship.rs | 2 +- 9 files changed, 253 insertions(+), 31 deletions(-) diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 93d10d7..8180fe0 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -126,7 +126,7 @@ impl InMemoryGateway { mag::MagModifier::MagCell(mag_cell_id) => { if let Some(mag_cell) = items.get(mag_cell_id) { if let ItemDetail::Tool(mag_cell) = mag_cell.item { - mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()) + mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()).unwrap() } } }, diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs index 266eab2..938c946 100644 --- a/src/entity/gateway/postgres/postgres.rs +++ b/src/entity/gateway/postgres/postgres.rs @@ -65,6 +65,7 @@ impl PostgresGateway { } +// TODO: remove unwraps, return Result async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntity) -> ItemEntity { let ItemEntity {id, item} = item; @@ -108,7 +109,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit mag.bank() }, mag::MagModifier::MagCell(_) => { - mag.apply_mag_cell(mag::MagCell::try_from(Into::::into(cell.unwrap().0).tool).unwrap()) + mag.apply_mag_cell(mag::MagCell::try_from(Into::::into(cell.unwrap().0).tool).unwrap()).unwrap() }, mag::MagModifier::OwnerChange(class, section_id) => { mag.change_owner(class, section_id) diff --git a/src/entity/item/mag.rs b/src/entity/item/mag.rs index 64efc2e..e55491c 100644 --- a/src/entity/item/mag.rs +++ b/src/entity/item/mag.rs @@ -1,3 +1,4 @@ +use thiserror::Error; use std::collections::HashMap; use serde::{Serialize, Deserialize}; use crate::entity::item::tool::ToolType; @@ -419,9 +420,9 @@ pub enum MagCell { } impl std::convert::TryFrom for MagCell { - type Error = (); + type Error = MagCellError; - fn try_from(tool: ToolType) -> Result { + fn try_from(tool: ToolType) -> Result { match tool { ToolType::CellOfMag502 => Ok(MagCell::CellOfMag502), ToolType::CellOfMag213 => Ok(MagCell::CellOfMag213), @@ -448,7 +449,7 @@ impl std::convert::TryFrom for MagCell { ToolType::YahoosEngine => Ok(MagCell::YahoosEngine), ToolType::DPhotonCore => Ok(MagCell::DPhotonCore), ToolType::LibertaKit => Ok(MagCell::LibertaKit), - _ => Err(()), + _ => Err(MagCellError::IsNotMagCell), } } } @@ -509,6 +510,15 @@ impl MagAttributeOrdering { } } + +#[derive(Error, Debug)] +pub enum MagCellError { + #[error("not a mag cell")] + IsNotMagCell, + #[error("mag is rare")] + IsRareMag, +} + #[derive(Debug, Clone, PartialEq)] pub enum MagModifier { FeedMag{ @@ -1047,7 +1057,10 @@ impl Mag { } // TODO: this needs more checks on validity - pub fn apply_mag_cell(&mut self, mag_cell: MagCell) { + pub fn apply_mag_cell(&mut self, mag_cell: MagCell) -> Result<(), MagCellError> { + if self.is_rare_item() { + return Err(MagCellError::IsRareMag) + } self.mag = match mag_cell { MagCell::CellOfMag502 => { match self.id { @@ -1097,11 +1110,11 @@ impl Mag { MagCell::YahoosEngine => MagType::Yahoo, MagCell::DPhotonCore => MagType::GaelGiel, MagCell::LibertaKit => MagType::Agastya, - } + }; + Ok(()) } - // TODO: is this even needed? mags are not shop sellable...yet - pub fn is_rare_item(self) -> bool { + pub fn is_rare_item(&self) -> bool { matches!( self.mag, MagType::Pitri diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 203d819..2a8dad4 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -46,8 +46,9 @@ pub enum ItemNote { y: f32, z: f32, }, - Consumed, + Consumed, // TODO: character_id FedToMag { + //character_id: CharacterEntityId, mag: ItemEntityId, }, BoughtAtShop { diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index f01c7b5..216c523 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -594,7 +594,7 @@ fn use_consumed_item(character: CharacterEntity) -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) -> Pin, Box), CharacterEntity), ItemStateError>> + Send + 'a>> { - move |(mut item_state, mut transaction), inventory_item| { + move |(mut item_state, transaction), inventory_item| { let mut character = character.clone(); Box::pin(async move { let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { @@ -623,7 +623,6 @@ where entity_gateway.with_transaction(|transaction| async move { let item_state_proxy = ItemStateProxy::new(item_state); let ((item_state_proxy, transaction), new_character) = ItemStateAction::default() - //.act(consume_inventory_tool(character.id, *item_id, 1)) .act(take_item_from_inventory(character.id, *item_id, amount)) .act(use_consumed_item(character.clone())) .commit((item_state_proxy, transaction)) @@ -633,3 +632,72 @@ where Ok((transaction, ())) }).await } + +fn feed_mag_item(character: CharacterEntity, mag_item_id: ClientItemId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), CharacterEntity), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), tool| { + let character = character.clone(); + Box::pin(async move { + let mut inventory = item_state.inventory(&character.id)?; + let mag_entity = inventory.get_by_client_id_mut(&mag_item_id) + .ok_or_else(|| ItemStateError::InvalidItemId(mag_item_id))? + .item + .as_individual_mut() + .ok_or_else(|| ItemStateError::NotAMag(mag_item_id))?; + let mag_entity_id = mag_entity.entity_id; + + let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag { + //character_id: character.id, + mag: mag_entity_id, + }).await?; + transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?; + Ok(transaction) + }}).await?; + + let food_tool = tool + .item + .stacked() + .ok_or_else(|| ItemStateError::NotMagFood(tool.item_id))? + .tool + .tool; + + let mag_entity = mag_entity + .as_mag_mut() + .ok_or_else(|| ItemStateError::NotAMag(mag_item_id))?; + + mag_entity.feed(food_tool); + + transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), character)) + }) + } +} + + +pub async fn feed_mag<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + mag_item_id: &ClientItemId, + tool_item_id: &ClientItemId, +) -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), _) = ItemStateAction::default() + .act(take_item_from_inventory(character.id, *tool_item_id, 1)) + .act(feed_mag_item(character.clone(), *mag_item_id)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, ())) + }).await +} diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs index 90a1211..1028ed7 100644 --- a/src/ship/items/apply_item.rs +++ b/src/ship/items/apply_item.rs @@ -1,10 +1,12 @@ use thiserror::Error; +use std::convert::TryFrom; +use std::convert::TryInto; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::CharacterEntity; -use crate::entity::item::mag::MagCell; +use crate::entity::item::mag::{MagCell, MagCellError}; use crate::entity::item::tool::ToolType; -use crate::entity::item::ItemDetail; -use crate::ship::items::state::{ItemStateProxy, InventoryState, InventoryItem, InventoryItemDetail}; +use crate::entity::item::{ItemDetail, ItemEntityId}; +use crate::ship::items::state::{ItemStateProxy, InventoryState, InventoryItem, InventoryItemDetail, ItemStateError}; #[derive(Error, Debug)] @@ -17,6 +19,18 @@ pub enum ApplyItemError { InvalidItem, #[error("gateway error {0}")] GatewayError(#[from] GatewayError), + + #[error("itemstate error {0}")] + ItemStateError(Box), + + #[error("magcell error {0}")] + MagCellError(#[from] MagCellError), +} + +impl From for ApplyItemError { + fn from(other: ItemStateError) -> ApplyItemError { + ApplyItemError::ItemStateError(Box::new(other)) + } } // TODO: make all these functions not-pub @@ -81,9 +95,34 @@ async fn mag_cell(entity_gateway: &mut EG, used_cell: &Consum Ok(()) } + */ + + +async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy<'a>, + entity_gateway: &mut EG, + character: &CharacterEntity, + cell_entity_id: ItemEntityId, + mag_cell_type: MagCell) + -> Result<(), ApplyItemError> +where + EG: EntityGateway + ?Sized, +{ + let mut inventory = item_state.inventory(&character.id)?; + + let (mag_entity_id, mag) = inventory.equipped_mag_mut() + .ok_or_else(|| 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); + + Ok(()) +} + +/* pub async fn cell_of_mag_502(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag502).await + 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> { @@ -179,7 +218,15 @@ pub async fn liberta_kit(entity_gateway: &mut EG, used_cell: } */ -async fn apply_tool<'a, EG: EntityGateway + ?Sized>(item_state: &mut ItemStateProxy<'a>, entity_gateway: &mut EG, character: &mut CharacterEntity, tool: ToolType) -> Result<(), ApplyItemError> { +async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy<'a>, + entity_gateway: &mut EG, + character: &mut CharacterEntity, + entity_id: ItemEntityId, + tool: ToolType) + -> Result<(), ApplyItemError> +where + EG: EntityGateway + ?Sized, +{ match tool { ToolType::PowerMaterial => power_material(entity_gateway, character).await, ToolType::MindMaterial => mind_material(entity_gateway, character).await, @@ -195,6 +242,32 @@ async fn apply_tool<'a, EG: EntityGateway + ?Sized>(item_state: &mut ItemStatePr ToolType::Difluid => Ok(()), ToolType::Trifluid => Ok(()), ToolType::HuntersReport => Ok(()), + 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 + } // TODO: rest of these _ => Err(ApplyItemError::InvalidItem) } @@ -203,17 +276,18 @@ async fn apply_tool<'a, EG: EntityGateway + ?Sized>(item_state: &mut ItemStatePr pub async fn apply_item<'a, EG: EntityGateway + ?Sized>(item_state: &mut ItemStateProxy<'a>, entity_gateway: &mut EG, character: &mut CharacterEntity, item: InventoryItem) -> Result<(), ApplyItemError> { - let item_detail = match item.item { + match item.item { InventoryItemDetail::Individual(individual_item) => { - individual_item.item + match individual_item.item { + ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await, + _ => Err(ApplyItemError::InvalidItem) + } }, InventoryItemDetail::Stacked(stacked_item) => { - ItemDetail::Tool(stacked_item.tool) + for entity_id in stacked_item.entity_ids { + apply_tool(item_state, entity_gateway, character, entity_id, stacked_item.tool.tool).await? + } + Ok(()) }, - }; - - match item_detail { - ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, tool.tool).await, - _ => Err(ApplyItemError::InvalidItem) } } diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index f65a6d9..7c33c3f 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -36,6 +36,9 @@ pub enum ItemStateError { #[error("bank error {0}")] BankError(#[from] BankError), + #[error("invalid item id {0}")] + InvalidItemId(ClientItemId), + #[error("invalid drop? {0:?} (this shouldn't occur)")] BadItemDrop(ItemDrop), @@ -56,6 +59,12 @@ pub enum ItemStateError { #[error("apply item {0}")] ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError), + + #[error("item is not a mag {0}")] + NotAMag(ClientItemId), + + #[error("item is not mag food {0}")] + NotMagFood(ClientItemId), } pub enum FloorType { @@ -207,6 +216,22 @@ pub struct IndividualItemDetail { pub item: ItemDetail, } +impl IndividualItemDetail { + pub fn as_mag(&self) -> Option<&Mag> { + match &self.item { + ItemDetail::Mag(mag) => Some(mag), + _ => None + } + } + + pub fn as_mag_mut(&mut self) -> Option<&mut Mag> { + match &mut self.item { + ItemDetail::Mag(mag) => Some(mag), + _ => None + } + } +} + #[derive(Clone, Debug)] pub struct StackedItemDetail { pub entity_ids: Vec, @@ -226,19 +251,35 @@ pub enum InventoryItemDetail { } impl InventoryItemDetail { - fn stacked(&self) -> Option<&StackedItemDetail> { + // TODO: rename as_stacked for consistency + pub fn stacked(&self) -> Option<&StackedItemDetail> { match self { InventoryItemDetail::Stacked(sitem) => Some(sitem), _ => None, } } - fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { + // TODO: rename as_stacked_mut for consistency + pub fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { match self { InventoryItemDetail::Stacked(sitem) => Some(sitem), _ => None, } } + pub fn as_individual(&self) -> Option<&IndividualItemDetail> { + match self { + InventoryItemDetail::Individual(iitem) => Some(iitem), + _ => None, + } + } + + pub fn as_individual_mut(&mut self) -> Option<&mut IndividualItemDetail> { + match self { + InventoryItemDetail::Individual(iitem) => Some(iitem), + _ => None, + } + } + pub fn as_client_bytes(&self) -> [u8; 16] { match self { InventoryItemDetail::Individual(item) => { @@ -633,6 +674,18 @@ impl InventoryState { } } + pub fn get_by_client_id(&self, item_id: &ClientItemId) -> Option<&InventoryItem> { + self.inventory.0 + .iter() + .find(|i| i.item_id == *item_id) + } + + pub fn get_by_client_id_mut(&mut self, item_id: &ClientItemId) -> Option<&mut InventoryItem> { + self.inventory.0 + .iter_mut() + .find(|i| i.item_id == *item_id) + } + pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { if self.meseta.0 == 999999 { return Err(ItemStateError::FullOfMeseta) @@ -704,6 +757,18 @@ impl InventoryState { } } + pub fn equipped_mag_mut(&mut self) -> Option<(ItemEntityId, &mut Mag)> { + let mag_id = self.equipped.mag?; + self.inventory.0 + .iter_mut() + .filter_map(|i| { + let individual = i.item.as_individual_mut()?; + let entity_id = individual.entity_id; + Some((entity_id, individual.as_mag_mut()?)) + }) + .find(|(entity_id, _)| *entity_id == mag_id) + } + pub fn sort(&mut self, item_ids: &Vec) { self.inventory.0.sort_by(|a, b| { let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id); diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 60dd075..f25e4cc 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -8,7 +8,7 @@ use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::items::{ItemManager, ClientItemId}; use crate::ship::packet::builder; use crate::ship::items::state::ItemState; -use crate::ship::items::actions::{drop_item, drop_partial_item, drop_meseta, equip_item, unequip_item, sort_inventory, use_item}; +use crate::ship::items::actions::{drop_item, drop_partial_item, drop_meseta, equip_item, unequip_item, sort_inventory, use_item, feed_mag}; pub async fn request_exp(id: ClientId, request_exp: &RequestExp, @@ -320,13 +320,13 @@ pub async fn player_feed_mag(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - item_manager.player_feeds_mag_item(entity_gateway, &client.character, ClientItemId(mag_feed.mag_id), ClientItemId(mag_feed.item_id)).await?; + feed_mag(item_state, entity_gateway, &client.character, &ClientItemId(mag_feed.mag_id), &ClientItemId(mag_feed.item_id)).await?; let mag_feed = mag_feed.clone(); Ok(Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 9958226..c3e4090 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -511,7 +511,7 @@ impl ShipServerState { }, GameMessage::PlayerFeedMag(player_feed_mag) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_manager).await? + handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerEquipItem(player_equip_item) => { handler::message::player_equips_item(id, player_equip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await?