diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index ee2c64a..220c899 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -1,12 +1,12 @@ use crate::ship::items::ClientItemId; -use crate::entity::item::ItemNote; +use crate::entity::item::{Meseta, ItemNote}; use std::future::Future; use std::pin::Pin; use crate::ship::map::MapArea; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction}; -use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateAction, ItemAction, ItemStateError, FloorItem, InventoryItem, AddItemResult}; +use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateAction, ItemAction, ItemStateError, FloorItem, InventoryItem, AddItemResult, FloorItemDetail, StackedItemDetail}; pub enum TriggerCreateItem {ItemAction, Yes, @@ -112,7 +112,7 @@ fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItem } -fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, item_id: ClientItemId, map_area: MapArea, drop_position: (f32, f32, f32)) +fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32, f32)) -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) -> Pin, Box), ()), ItemStateError>> + Send + 'a>> { @@ -134,6 +134,7 @@ fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, item_id: let mut floor = item_state.floor(&character_id)?; floor.add_inventory_item(inventory_item, map_area, drop_position); + item_state.set_floor(floor); Ok(((item_state, transaction), ())) }) @@ -155,7 +156,151 @@ where let item_state_proxy = ItemStateProxy::new(item_state); let ((item_state_proxy, transaction), result) = ItemStateAction::default() .act(take_item_from_inventory(character.id, *item_id)) - .act(add_inventory_item_to_shared_floor(character.id, *item_id, map_area, drop_position)) + .act(add_inventory_item_to_shared_floor(character.id, map_area, drop_position)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +fn take_partial_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), StackedItemDetail), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + let item = inventory.take_partial_item(&item_id, amount).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?; + + transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), item)) + }) + } +} + +fn add_partial_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32)) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), StackedItemDetail) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), stacked_item| { + Box::pin(async move { + let floor_item = FloorItem { + item_id: item_state.new_item_id()?, + item: FloorItemDetail::Stacked(stacked_item), + map_area, + x: drop_position.0, + y: 0.0, + z: drop_position.1, + }; + + let transaction = floor_item.with_entity_id(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id| { + async move { + if let Ok(transaction) = &mut transaction { + transaction.gateway().add_item_note(&entity_id, ItemNote::PlayerDrop { + character_id, + map_area, + x: drop_position.0, + y: 0.0, + z: drop_position.1, + }).await?; + } + transaction + }}).await?; + + let mut floor = item_state.floor(&character_id)?; + let floor_item = floor.add_item(floor_item).clone(); + item_state.set_floor(floor); + + Ok(((item_state, transaction), floor_item)) + }) + } +} + +pub async fn drop_partial_item<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + map_area: MapArea, + drop_position: (f32, f32), + amount: u32) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_partial_item_from_inventory(character.id, *item_id, amount)) + .act(add_partial_inventory_item_to_shared_floor(character.id, map_area, drop_position)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +fn take_meseta_from_inventory(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), u32), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.remove_meseta(amount)?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + + Ok(((item_state, transaction), amount)) + }) + } +} + +fn add_meseta_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32)) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), u32) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +{ + + move |(mut item_state, transaction), amount| { + Box::pin(async move { + let floor_item = FloorItem { + item_id: item_state.new_item_id()?, + item: FloorItemDetail::Meseta(Meseta(amount)), + map_area: map_area, + x: drop_position.0, + y: 0.0, + z: drop_position.1, + }; + + let mut floor = item_state.floor(&character_id)?; + let floor_item = floor.add_item(floor_item).clone(); + item_state.set_floor(floor); + + Ok(((item_state, transaction), floor_item)) + }) + } +} + +pub async fn drop_meseta<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + map_area: MapArea, + drop_position: (f32, f32), + amount: u32) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_meseta_from_inventory(character.id, amount)) + .act(add_meseta_to_shared_floor(character.id, map_area, drop_position)) .commit((item_state_proxy, transaction)) .await?; item_state_proxy.commit(); diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index 40131a2..ac07759 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::collections::HashMap; use crate::ship::items::ClientItemId; use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity}; @@ -7,7 +8,6 @@ use crate::ship::map::MapArea; use crate::ship::location::RoomId; use crate::entity::character::CharacterEntityId; use crate::entity::gateway::GatewayError; -use crate::entity::gateway::entitygateway::EntityGatewayTransaction; use crate::entity::item::tool::Tool; use crate::entity::item::mag::Mag; use crate::ship::drops::ItemDrop; @@ -33,6 +33,9 @@ pub enum ItemStateError { #[error("gateway")] GatewayError(#[from] GatewayError), + + #[error("tried to drop more meseta than in inventory: {0}")] + InvalidMesetaDrop(u32), } @@ -163,7 +166,7 @@ where type Start = T; type Error = E; - async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> { + async fn action(&self, s: Self::Start, _i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> { Ok((s, ())) } @@ -252,12 +255,12 @@ impl FloorItemDetail { #[derive(Clone)] pub struct FloorItem { - item_id: ClientItemId, - item: FloorItemDetail, - map_area: MapArea, - x: f32, - y: f32, - z: f32, + pub item_id: ClientItemId, + pub item: FloorItemDetail, + pub map_area: MapArea, + pub x: f32, + pub y: f32, + pub z: f32, } impl FloorItem { @@ -293,6 +296,20 @@ impl FloorItem { } param } + + pub fn as_client_bytes(&self) -> [u8; 16] { + match &self.item { + FloorItemDetail::Individual(individual_floor_item) => { + individual_floor_item.item.as_client_bytes() + }, + FloorItemDetail::Stacked(stacked_floor_item) => { + stacked_floor_item.tool.as_stacked_bytes(stacked_floor_item.entity_ids.len()) + }, + FloorItemDetail::Meseta(meseta_floor_item) => { + meseta_floor_item.as_bytes() + } + } + } } @@ -326,7 +343,7 @@ pub struct RoomFloorItems(Vec); pub struct InventoryState { character_id: CharacterEntityId, inventory: Inventory, - meseta: Meseta, + pub meseta: Meseta, } impl InventoryState { @@ -374,13 +391,13 @@ impl InventoryState { } } } - + }, FloorItemDetail::Meseta(meseta) => { if self.meseta == Meseta(999999) { Err(InventoryError::MesetaFull) } - else { + else { self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0 ,999999); Ok(AddItemResult::Meseta) } @@ -394,6 +411,40 @@ impl InventoryState { .next() } + pub fn take_partial_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + let amount = amount as usize; + let (idx, _, stacked_item) = self.inventory.0 + .iter_mut() + .enumerate() + .filter_map(|(k, item)| { + match item.item { + InventoryItemDetail::Stacked(ref mut stacked_item) => Some((k, item.item_id, stacked_item)), + _ => None + } + }) + .find(|(_, id, _)| *id == *item_id)?; + + + let remove_all = match stacked_item.entity_ids.len().cmp(&amount) { + Ordering::Equal => true, + Ordering::Greater => false, + Ordering::Less => return None, + }; + + if remove_all { + let stacked_item = stacked_item.clone(); + self.inventory.0.remove(idx); + Some(stacked_item) + } + else { + let entity_ids = stacked_item.entity_ids.drain(..amount).collect(); + Some(StackedItemDetail { + entity_ids: entity_ids, + tool: stacked_item.tool, + }) + } + } + pub fn as_inventory_entity(&self, _character_id: &CharacterEntityId) -> InventoryEntity { InventoryEntity { items: self.inventory.0.iter() @@ -420,6 +471,14 @@ impl InventoryState { .collect() } } + + pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if amount > self.meseta.0 { + return Err(ItemStateError::InvalidMesetaDrop(amount)) + } + self.meseta.0 -= amount; + Ok(()) + } } pub struct FloorState { @@ -444,7 +503,7 @@ impl FloorState { }) } - pub fn add_inventory_item(&mut self, inventory_item: InventoryItem, map_area: MapArea, position: (f32, f32, f32)) { + pub fn add_inventory_item(&mut self, inventory_item: InventoryItem, map_area: MapArea, position: (f32, f32, f32)) -> &FloorItem { let floor_item = FloorItem { item_id: inventory_item.item_id, item: match inventory_item.item { @@ -458,6 +517,12 @@ impl FloorState { }; self.shared.0.push(floor_item); + &self.shared.0[self.shared.0.len()-1] + } + + pub fn add_item(&mut self, floor_item: FloorItem) -> &FloorItem { + self.shared.0.push(floor_item); + &self.shared.0[self.shared.0.len()-1] } } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index 186e637..23f3ea1 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -4,6 +4,7 @@ use crate::entity::item; use crate::common::leveltable::CharacterStats; use crate::ship::ship::{ShipError}; use crate::ship::items::{ClientItemId, InventoryItem, StackedFloorItem, FloorItem, CharacterBank}; +use crate::ship::items::state::FloorItem as FloorItem2; use crate::ship::location::AreaClient; use std::convert::TryInto; use crate::ship::shops::ShopItem; @@ -89,7 +90,7 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu }) } -pub fn drop_split_stack(area_client: AreaClient, item: &StackedFloorItem) -> Result { +pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem2) -> Result { let item_bytes = item.as_client_bytes(); Ok(DropSplitStack { client: area_client.local_client.id(), @@ -106,18 +107,18 @@ pub fn drop_split_stack(area_client: AreaClient, item: &StackedFloorItem) -> Res }) } -pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem) -> Result { +pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem2) -> Result { let item_bytes = item.as_client_bytes(); Ok(DropSplitStack { client: area_client.local_client.id(), target: 0, variety: 0, unknown1: 0, - map_area: item.map_area().area_value(), - x: item.x(), - z: item.z(), + map_area: item.map_area.area_value(), + x: item.x, + z: item.z, item_bytes: item_bytes[0..12].try_into()?, - item_id: item.item_id().0, + item_id: item.item_id.0, item_bytes2: item_bytes[12..16].try_into()?, unknown2: 0, }) diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 9ed1af9..fbdd998 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, pick_up_item}; +use crate::ship::items::actions::{drop_item, drop_partial_item, drop_meseta}; pub async fn request_exp(id: ClientId, request_exp: &RequestExp, @@ -120,7 +120,7 @@ pub async fn no_longer_has_item(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -134,7 +134,7 @@ where } if no_longer_has_item.item_id == 0xFFFFFFFF { - let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, no_longer_has_item.amount as u32).await?; + let dropped_meseta = drop_meseta(item_state, entity_gateway, &client.character, drop_location.map_area, (drop_location.x, drop_location.z), no_longer_has_item.amount).await?; let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?; let no_longer_has_meseta_pkt = builder::message::player_no_longer_has_meseta(area_client, no_longer_has_item.amount as u32); @@ -158,9 +158,9 @@ where )) } else { - let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, no_longer_has_item.amount as usize).await?; + let dropped_item = drop_partial_item(item_state, entity_gateway, &client.character, &drop_location.item_id, drop_location.map_area, (drop_location.x, drop_location.z), no_longer_has_item.amount).await?; - let dropped_item_pkt = builder::message::drop_split_stack(area_client, dropped_item)?; + let dropped_item_pkt = builder::message::drop_split_stack(area_client, &dropped_item)?; client.item_drop_location = None; let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; diff --git a/src/ship/ship.rs b/src/ship/ship.rs index b16c6be..698d62e 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -490,7 +490,7 @@ impl ShipServerState { }, GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) | GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) |