From 6a2703ed6cd870fbd7faca0493331d5e9fb7b391 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 00:06:08 -0700 Subject: [PATCH 01/12] update inmemory gateway to reflect how inventory actually works --- src/entity/gateway/inmemory.rs | 52 ++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 88636ec..db485e8 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -213,6 +213,12 @@ impl EntityGatewayTransaction for InMemoryGatewayTransaction { } } +#[derive(Clone)] +enum InventoryItemElement { + Individual(ItemEntityId), + Stacked(Vec), +} + #[derive(Clone)] pub struct InMemoryGateway { users: Arc>>, @@ -221,7 +227,7 @@ pub struct InMemoryGateway { character_meseta: Arc>>, bank_meseta: Arc>>, items: Arc>>, - inventories: Arc>>, + inventories: Arc>>>, banks: Arc>>, equips: Arc>>, mag_modifiers: Arc>>>, @@ -538,7 +544,29 @@ impl EntityGateway for InMemoryGateway { Ok(inventories .iter() .find(|(id, _)| **id == *char_id) - .map(|(_, inv)| inv.clone()) + .map(|(_, inv)| { + InventoryEntity { + items: inv + .iter() + .map(|inv_item_id| { + match inv_item_id { + InventoryItemElement::Individual(individual_id) => { + InventoryItemEntity::Individual(items.get(individual_id).unwrap().clone()) + }, + InventoryItemElement::Stacked(stacked_ids) => { + InventoryItemEntity::Stacked( + stacked_ids.iter() + .map(|stacked_id| { + items.get(stacked_id).unwrap().clone() + }) + .collect() + ) + } + } + }) + .collect() + } + }) .map(|inv| apply_modifiers(&items, &weapon_modifiers, &mag_modifiers, inv)) .unwrap_or_default()) } @@ -554,7 +582,25 @@ impl EntityGateway for InMemoryGateway { async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> { let mut inventories = self.inventories.lock().await; - inventories.insert(*char_id, inventory.clone()); + inventories.insert(*char_id, inventory.items + .iter() + .map(|inventory_item| { + match inventory_item { + InventoryItemEntity::Individual(individual) => { + InventoryItemElement::Individual(individual.id) + }, + InventoryItemEntity::Stacked(stacked) => { + InventoryItemElement::Stacked( + stacked.iter() + .map(|stacked| { + stacked.id + }) + .collect() + ) + } + } + }) + .collect()); Ok(()) } -- 2.36.0 From f27c121539874ec4f5afc79a50fd1535c1a9d8f4 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 00:06:34 -0700 Subject: [PATCH 02/12] take ref to weapon modifier --- src/entity/gateway/entitygateway.rs | 2 +- src/entity/gateway/inmemory.rs | 6 +++--- src/entity/gateway/postgres/postgres.rs | 6 +++--- src/ship/items/actions.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index a8c47dc..db2f5f0 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -96,7 +96,7 @@ pub trait EntityGateway: Send + Sync { unimplemented!(); } - async fn add_weapon_modifier(&mut self, _item_id: &ItemEntityId, _modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { + async fn add_weapon_modifier(&mut self, _item_id: &ItemEntityId, _modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { unimplemented!(); } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index db485e8..13b281c 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -104,7 +104,7 @@ impl EntityGateway for InMemoryGatewayTransaction { self.working_gateway.use_mag_cell(mag_item_id, mag_cell_id).await } - async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { + async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { self.working_gateway.add_weapon_modifier(item_id, modifier).await } @@ -528,11 +528,11 @@ impl EntityGateway for InMemoryGateway { Ok(()) } - async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { + async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { self.weapon_modifiers.lock().await .entry(*item_id) .or_insert_with(Vec::new) - .push(modifier); + .push(modifier.clone()); Ok(()) } diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs index dbc49d4..bb8c018 100644 --- a/src/entity/gateway/postgres/postgres.rs +++ b/src/entity/gateway/postgres/postgres.rs @@ -380,7 +380,7 @@ async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, Ok(()) } -async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> +async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);") .bind(item_id.0) @@ -688,7 +688,7 @@ impl<'t> EntityGateway for PostgresGateway<'t> { use_mag_cell(&mut *self.pool.acquire().await?, mag_item_id, mag_cell_id).await } - async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { + async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { add_weapon_modifier(&mut *self.pool.acquire().await?, item_id, modifier).await } @@ -814,7 +814,7 @@ impl<'c> EntityGateway for PostgresTransaction<'c> { use_mag_cell(&mut *self.pgtransaction.lock().await, mag_item_id, mag_cell_id).await } - async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { + async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { add_weapon_modifier(&mut *self.pgtransaction.lock().await, item_id, modifier).await } diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index be6748b..984fdbd 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -1006,7 +1006,7 @@ where match (&mut inventory_item.item, modifier) { (InventoryItemDetail::Individual(IndividualItemDetail{entity_id, item: ItemDetail::Weapon(ref mut weapon), ..}), ItemModifier::WeaponModifier(modifier)) => { weapon.apply_modifier(&modifier); - transaction.gateway().add_weapon_modifier(entity_id, modifier).await?; + transaction.gateway().add_weapon_modifier(entity_id, &modifier).await?; }, _ => return Err(ItemStateError::InvalidModifier.into()) } -- 2.36.0 From 28c0073dffbbee2fd1f4b4dee224d153aa07fb70 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 00:08:56 -0700 Subject: [PATCH 03/12] implement grinders --- src/entity/item/weapon.rs | 80 ++++++++++++++++++--------------- src/ship/items/apply_item.rs | 30 ++++++++++++- src/ship/items/inventory.rs | 13 ++++++ src/ship/items/state.rs | 8 ++++ tests/test_item_use.rs | 86 +++++++++++++++++++++++++++++------- 5 files changed, 164 insertions(+), 53 deletions(-) diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index 8e9db4b..29a9357 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -1479,44 +1479,52 @@ impl Weapon { } pub fn apply_modifier(&mut self, modifier: &WeaponModifier) { - if let WeaponModifier::Tekked{special, percent, grind} = modifier { - match special { - TekSpecialModifier::Plus => { - self.special = self.special.map(|special| { - special.rank_up() - }); - }, - TekSpecialModifier::Minus => { - self.special = self.special.map(|special| { - special.rank_down() - }); - }, - TekSpecialModifier::Neutral => { - }, - } - for i in 0..3 { - self.attrs[i] = self.attrs[i].map(|mut attr| { - match percent { - TekPercentModifier::PlusPlus => { - attr.value += 10; - }, - TekPercentModifier::Plus => { - attr.value += 5; - }, - TekPercentModifier::MinusMinus => { - attr.value -= 10; - }, - TekPercentModifier::Minus => { - attr.value -= 5; - }, - TekPercentModifier::Neutral => { + match modifier { + WeaponModifier::Tekked{special, percent, grind} => { + match special { + TekSpecialModifier::Plus => { + self.special = self.special.map(|special| { + special.rank_up() + }); + }, + TekSpecialModifier::Minus => { + self.special = self.special.map(|special| { + special.rank_down() + }); + }, + TekSpecialModifier::Neutral => { + }, + } + for i in 0..3 { + self.attrs[i] = self.attrs[i].map(|mut attr| { + match percent { + TekPercentModifier::PlusPlus => { + attr.value += 10; + }, + TekPercentModifier::Plus => { + attr.value += 5; + }, + TekPercentModifier::MinusMinus => { + attr.value -= 10; + }, + TekPercentModifier::Minus => { + attr.value -= 5; + }, + TekPercentModifier::Neutral => { + } } - } - attr - }); + attr + }); + } + self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8; + self.tekked = true; + }, + WeaponModifier::AddGrind {amount, ..} => { + self.grind += *amount as u8; + }, + WeaponModifier::AddPercents {..} => { + // TODO } - self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8; - self.tekked = true; } } diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs index 9820226..ad2f27d 100644 --- a/src/ship/items/apply_item.rs +++ b/src/ship/items/apply_item.rs @@ -10,6 +10,7 @@ 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::{InventoryItem, InventoryItemDetail}; @@ -34,7 +35,7 @@ pub enum ApplyItemError { pub enum ApplyItemAction { UpdateCharacter(Box), CreateItem(ItemDetail), - //TransformItem, + //TransformItem(ItemDetail), //RemoveItem, } @@ -241,6 +242,30 @@ fn jack_o_lantern() -> Result, anyhow::Error> 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, @@ -271,6 +296,9 @@ where ToolType::Antidote => Ok(Vec::new()), ToolType::Antiparalysis => Ok(Vec::new()), ToolType::TrapVision => 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 diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index bfd709b..d7e74fd 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -7,6 +7,7 @@ use std::future::Future; use crate::entity::character::CharacterEntityId; use crate::entity::item::tool::ToolType; use crate::entity::item::mag::Mag; +use crate::entity::item::weapon::Weapon; use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; use crate::ship::items::state::ItemStateError; use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult}; @@ -453,6 +454,18 @@ impl InventoryState { .find(|(entity_id, _)| *entity_id == mag_id) } + pub fn equipped_weapon_mut(&mut self) -> Option<(ItemEntityId, &mut Weapon)> { + let weapon_id = self.equipped.weapon?; + 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_weapon_mut()?)) + }) + .find(|(entity_id, _)| *entity_id == weapon_id) + } + pub fn sort(&mut self, item_ids: &[ClientItemId]) { 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/items/state.rs b/src/ship/items/state.rs index a166d8b..79f24eb 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -91,6 +91,14 @@ impl IndividualItemDetail { } } + pub fn as_weapon_mut(&mut self) -> Option<&mut Weapon> { + match &mut self.item { + ItemDetail::Weapon(weapon) => Some(weapon), + _ => None + } + } + + pub fn as_client_bytes(&self) -> [u8; 16] { match &self.item { ItemDetail::Weapon(w) => w.as_bytes(), diff --git a/tests/test_item_use.rs b/tests/test_item_use.rs index f9a9b67..6474e6f 100644 --- a/tests/test_item_use.rs +++ b/tests/test_item_use.rs @@ -312,22 +312,6 @@ async fn test_use_barta_1() { let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; - /* - let mut p1_inv = Vec::new(); - for tool in vec![item::tool::ToolType::PowerMaterial, item::tool::ToolType::].into_iter() { - let mut item = Vec::new(); - for _ in 0..5usize { - item.push(entity_gateway.create_item( - item::NewItemEntity { - item: item::ItemDetail::Tool( - item::tool::Tool { - tool: tool - } - ), - }).await.unwrap()); - } - p1_inv.push(item::InventoryItemEntity::Stacked(item)); -}*/ let inv = vec![ entity_gateway.create_item( item::NewItemEntity { @@ -391,6 +375,76 @@ async fn test_use_barta_1() { assert!(char.techs.techs.get(&item::tech::Technique::Barta).is_none()); } + +#[async_std::test] +async fn test_use_monogrinder() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + + let saber = entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(); + + let mut grinders = Vec::new(); + for _ in 0..3usize { + grinders.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monogrinder, + } + ), + }).await.unwrap()); + } + + let equipped = item::EquippedEntity { + weapon: Some(saber.id), + armor: None, + shield: None, + unit: [None; 4], + mag: None, + }; + entity_gateway.set_character_equips(&char1.id, &equipped).await.unwrap(); + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![item::InventoryItemEntity::Individual(saber), + item::InventoryItemEntity::Stacked(grinders)])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10001, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10001, + })))).await.unwrap(); + + let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(inventory_items.items.len(), 2); + + assert!(matches!(inventory_items.items[0], item::InventoryItemEntity::Individual(item::ItemEntity{ item: item::ItemDetail::Weapon(item::weapon::Weapon {grind: 2, ..}), ..}))); +} + + + // TODO: tests for ALL ITEMS WOW /* -- 2.36.0 From 52da851d8a02817db79803c5036008e2c898da1f Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 00:13:08 -0700 Subject: [PATCH 04/12] stray println --- tests/test_rooms.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_rooms.rs b/tests/test_rooms.rs index 45a5210..05a3852 100644 --- a/tests/test_rooms.rs +++ b/tests/test_rooms.rs @@ -72,7 +72,6 @@ async fn test_item_ids_reset_when_rejoining_rooms() { match &p[1].1 { SendShipPacket::AddToRoom(add_to) => { - println!("addto {:?}", add_to); assert_eq!(add_to.playerinfo.inventory.items.iter().map(|k| k.item_id).collect::>(), vec![0x210000,0x210001,0x210002,0x210003,0x210004,0x210005,0x210006,0x210007,0x210008,0x210009, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); -- 2.36.0 From cccf385ee99e367cb468f1d10f7706293607f796 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 19:18:18 -0700 Subject: [PATCH 05/12] recv pkt trace -> info --- src/common/mainloop/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/mainloop/client.rs b/src/common/mainloop/client.rs index 0ace697..faa33c4 100644 --- a/src/common/mainloop/client.rs +++ b/src/common/mainloop/client.rs @@ -133,7 +133,7 @@ where match pkt_receiver.recv_pkts::().await { Ok(pkts) => { for pkt in pkts { - trace!("[recv from {:?}] {:#?}", client_id, pkt); + info!("[recv from {:?}] {:#?}", client_id, pkt); match state.handle(client_id, pkt).await { Ok(response) => { for resp in response { -- 2.36.0 From 9123c4842b81672c25ff4a7802fe32cbbd70fced Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 19:22:11 -0700 Subject: [PATCH 06/12] start of fixing item_id parity issues --- src/ship/items/actions.rs | 34 ++++++++++- src/ship/items/apply_item.rs | 4 +- src/ship/items/bank.rs | 27 ++++++--- src/ship/items/inventory.rs | 93 +++++++++++++++++++++++------- src/ship/items/state.rs | 63 ++++++++++++++++---- src/ship/items/tasks.rs | 2 +- src/ship/ship.rs | 1 - tests/test_bank.rs | 2 +- tests/test_item_pickup.rs | 2 +- tests/test_item_use.rs | 1 + tests/test_rooms.rs | 109 +---------------------------------- 11 files changed, 182 insertions(+), 156 deletions(-) diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index 984fdbd..53a59db 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -97,6 +97,30 @@ where } +pub(super) fn remove_item_from_inventory( + character_id: CharacterEntityId, + item_id: ClientItemId, + amount: u32, +) -> impl Fn((ItemStateProxy, TR), ()) + -> BoxFuture> +where + EG: EntityGateway, + TR: EntityGatewayTransaction + 'static, +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id).await?; + let item = inventory.remove_item(&item_id, amount) + .await + .ok_or_else(|| ItemStateError::NoInventoryItem(item_id))?; + + transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; + item_state.set_inventory(inventory).await; + + Ok(((item_state, transaction), item)) + }) + } +} pub(super) fn take_item_from_inventory( character_id: CharacterEntityId, @@ -111,7 +135,9 @@ where move |(mut item_state, mut transaction), _| { Box::pin(async move { let mut inventory = item_state.inventory(&character_id).await?; - let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoInventoryItem(item_id))?; + let item = inventory.take_item(&item_id, amount) + .await + .ok_or_else(|| ItemStateError::NoInventoryItem(item_id))?; transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; item_state.set_inventory(inventory).await; @@ -306,7 +332,9 @@ where move |(mut item_state, mut transaction), _| { Box::pin(async move { let mut bank = item_state.bank(&character_id).await?; - let item = bank.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoBankItem(item_id))?; + let item = bank.take_item(&item_id, amount) + .await + .ok_or_else(|| ItemStateError::NoBankItem(item_id))?; transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?; item_state.set_bank(bank).await; @@ -471,7 +499,7 @@ where pub(super) fn use_consumed_item( character: &CharacterEntity, -) -> impl Fn((ItemStateProxy, TR), InventoryItem) +) -> impl Fn((ItemStateProxy, TR), InventoryItemDetail) -> BoxFuture), anyhow::Error>> where EG: EntityGateway + Clone + 'static, diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs index ad2f27d..d73941e 100644 --- a/src/ship/items/apply_item.rs +++ b/src/ship/items/apply_item.rs @@ -356,12 +356,12 @@ where pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &mut CharacterEntity, - item: InventoryItem + item: InventoryItemDetail ) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized + Clone + 'static { - match item.item { + 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, diff --git a/src/ship/items/bank.rs b/src/ship/items/bank.rs index 5494102..4f922af 100644 --- a/src/ship/items/bank.rs +++ b/src/ship/items/bank.rs @@ -3,6 +3,7 @@ use libpso::character::character; use crate::ship::items::ClientItemId; use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity, BankName}; use std::future::Future; +use async_std::sync::{Arc, Mutex}; use crate::entity::character::CharacterEntityId; use crate::ship::items::state::ItemStateError; @@ -96,18 +97,26 @@ impl Bank { #[derive(Clone, Debug)] pub struct BankState { pub character_id: CharacterEntityId, - pub item_id_counter: u32, + pub item_id_counter: Arc>, pub name: BankName, pub bank: Bank, pub meseta: Meseta, } +async fn new_item_id(item_id_counter: &Arc>) -> ClientItemId { + let mut item_id_counter = item_id_counter.lock().await; + let item_id = *item_id_counter; + *item_id_counter += 1; + + ClientItemId(item_id) +} + impl BankState { pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState { bank.0.sort(); BankState { character_id, - item_id_counter: 0, + item_id_counter: Arc::new(Mutex::new(0)), name, bank, meseta, @@ -118,11 +127,14 @@ impl BankState { self.bank.0.len() } - pub fn initialize_item_ids(&mut self, base_item_id: u32) { + pub async fn initialize_item_ids(&mut self, base_item_id: Arc>) { + self.item_id_counter = base_item_id; + let mut bitem_id = self.item_id_counter.lock().await; for (i, item) in self.bank.0.iter_mut().enumerate() { - item.item_id = ClientItemId(base_item_id + i as u32); + item.item_id = ClientItemId(*bitem_id + i as u32); } - self.item_id_counter = base_item_id + self.bank.0.len() as u32; + + *bitem_id = *bitem_id + self.bank.0.len() as u32; } pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> { @@ -191,7 +203,7 @@ impl BankState { } } - pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + pub async fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { let idx = self.bank.0 .iter() .position(|i| i.item_id == *item_id)?; @@ -211,9 +223,8 @@ impl BankState { } else { let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); - self.item_id_counter += 1; Some(BankItem { - item_id: ClientItemId(self.item_id_counter), + item_id: new_item_id(&self.item_id_counter).await, item: BankItemDetail::Stacked(StackedItemDetail { entity_ids, tool: stacked_item.tool, diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index d7e74fd..19fa372 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -3,6 +3,7 @@ use libpso::character::character; use crate::ship::items::ClientItemId; use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity}; use std::future::Future; +use async_std::sync::{Arc, Mutex}; use crate::entity::character::CharacterEntityId; use crate::entity::item::tool::ToolType; @@ -117,22 +118,12 @@ impl InventoryItemDetail { } } -} - - -#[derive(Clone, Debug)] -pub struct InventoryItem { - pub item_id: ClientItemId, - pub item: InventoryItemDetail, -} - -impl InventoryItem { pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result where F: FnMut(T, ItemEntityId) -> Fut, Fut: Future>, { - match &self.item { + match &self { InventoryItemDetail::Individual(individual_item) => { param = func(param, individual_item.entity_id).await?; }, @@ -145,6 +136,23 @@ impl InventoryItem { Ok(param) } +} + + +#[derive(Clone, Debug)] +pub struct InventoryItem { + pub item_id: ClientItemId, + pub item: InventoryItemDetail, +} + +impl InventoryItem { + pub async fn with_entity_id(&self, param: T, func: F) -> Result + where + F: FnMut(T, ItemEntityId) -> Fut, + Fut: Future>, + { + self.item.with_entity_id(param, func).await + } pub async fn with_mag(&self, mut param: T, mut func: F) -> Result where @@ -183,23 +191,38 @@ pub enum InventoryError { #[derive(Clone, Debug)] pub struct InventoryState { pub character_id: CharacterEntityId, - pub item_id_counter: u32, + pub item_id_counter: Arc>, pub inventory: Inventory, pub equipped: EquippedEntity, pub meseta: Meseta, } +async fn new_item_id(item_id_counter: &Arc>) -> ClientItemId { + let mut item_id_counter = item_id_counter.lock().await; + let item_id = *item_id_counter; + *item_id_counter += 1; + + ClientItemId(item_id) +} + impl InventoryState { - pub fn initialize_item_ids(&mut self, base_item_id: u32) { + pub async fn initialize_item_ids(&mut self, base_item_id: Arc>) { + self.item_id_counter = base_item_id; + let mut bitem_id = self.item_id_counter.lock().await; + for (i, item) in self.inventory.0.iter_mut().enumerate() { - item.item_id = ClientItemId(base_item_id + i as u32); + item.item_id = ClientItemId(*bitem_id + i as u32); } - self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1; + + *bitem_id = *bitem_id + self.inventory.0.len() as u32; } - pub fn new_item_id(&mut self) -> ClientItemId { - self.item_id_counter += 1; - ClientItemId(self.item_id_counter) + pub async fn new_item_id(&mut self) -> ClientItemId { + let mut item_id_counter = self.item_id_counter.lock().await; + let item_id = *item_id_counter; + *item_id_counter += 1; + + ClientItemId(item_id) } pub fn count(&self) -> usize { @@ -326,7 +349,36 @@ impl InventoryState { } } - pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + pub async fn remove_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + let idx = self.inventory.0 + .iter() + .position(|i| i.item_id == *item_id)?; + match &mut self.inventory.0[idx].item { + InventoryItemDetail::Individual(_individual_item) => { + Some(self.inventory.0.remove(idx).item) + }, + InventoryItemDetail::Stacked(stacked_item) => { + let remove_all = (amount == 0) || match stacked_item.entity_ids.len().cmp(&(amount as usize)) { + Ordering::Equal => true, + Ordering::Greater => false, + Ordering::Less => return None, + }; + + if remove_all { + Some(self.inventory.0.remove(idx).item) + } + else { + let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); + Some(InventoryItemDetail::Stacked(StackedItemDetail { + entity_ids, + tool: stacked_item.tool, + })) + } + } + } + } + + pub async fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { let idx = self.inventory.0 .iter() .position(|i| i.item_id == *item_id)?; @@ -346,9 +398,8 @@ impl InventoryState { } else { let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); - self.item_id_counter += 1; Some(InventoryItem { - item_id: ClientItemId(self.item_id_counter), + item_id: new_item_id(&self.item_id_counter).await, item: InventoryItemDetail::Stacked(StackedItemDetail { entity_ids, tool: stacked_item.tool, diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index 79f24eb..b22a25c 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -134,15 +134,40 @@ pub enum AddItemResult { Meseta, } +#[derive(Clone, Debug)] +struct RoomGemItemIdCounter { + inventory: [Arc>; 4], + bank: [Arc>; 4], +} + +impl Default for RoomGemItemIdCounter { + fn default() -> RoomGemItemIdCounter { + RoomGemItemIdCounter { + inventory: core::array::from_fn(|gem| Arc::new(Mutex::new(((gem as u32) << 21) | 0x10000))), + bank: core::array::from_fn(|gem| Arc::new(Mutex::new(((gem as u32) << 21) | 0x20000))), + } + } +} + +impl RoomGemItemIdCounter { + fn inventory(&self, area_client: &AreaClient) -> Arc> { + self.inventory[area_client.local_client.id() as usize].clone() + } + + fn bank(&self, area_client: &AreaClient) -> Arc> { + self.bank[area_client.local_client.id() as usize].clone() + } +} + #[derive(Clone, Debug)] pub struct ItemState { character_inventory: Arc>>>, character_bank: Arc>>>, - character_room: Arc>>, character_floor: Arc>>>, room_floor: Arc>>>, + room_gem_item_ids: Arc>>, room_item_id_counter: Arc>, } @@ -155,6 +180,7 @@ impl Default for ItemState { character_room: Arc::new(RwLock::new(HashMap::new())), character_floor: Arc::new(RwLock::new(HashMap::new())), room_floor: Arc::new(RwLock::new(HashMap::new())), + room_gem_item_ids: Arc::new(RwLock::new(HashMap::new())), room_item_id_counter: Arc::new(RwLock::new(0x00810000)), } } @@ -230,12 +256,12 @@ impl ItemState { let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let inventory_state = InventoryState { character_id: character.id, - item_id_counter: 0, + item_id_counter: Arc::new(Mutex::new(0)), inventory: Inventory::new(inventory_items), equipped, meseta: character_meseta, }; - + let bank_items = join_all( bank.items.into_iter() .map(|item| { @@ -271,7 +297,7 @@ impl ItemState { .await .into_iter() .collect::, anyhow::Error>>()?; - + let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; let bank_state = BankState::new(character.id, BankName("".into()), Bank::new(bank_items), bank_meseta); @@ -287,7 +313,13 @@ impl ItemState { } pub async fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) { - let base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000; + let mut base_item_ids = self.room_gem_item_ids + .write() + .await; + let base_item_ids = base_item_ids + .entry(room_id) + .or_insert_with(RoomGemItemIdCounter::default); + self.character_inventory .read() .await @@ -295,8 +327,8 @@ impl ItemState { .unwrap() .write() .await - .initialize_item_ids(base_inventory_id); - let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000; + .initialize_item_ids(base_item_ids.inventory(&area_client).clone()) + .await; self.character_bank .read() .await @@ -304,7 +336,8 @@ impl ItemState { .unwrap() .write() .await - .initialize_item_ids(base_bank_id); + .initialize_item_ids(base_item_ids.bank(&area_client)) + .await; self.character_room .write() .await @@ -486,16 +519,22 @@ impl ItemStateProxy { } pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result { - let room_id = *self.item_state.character_room.read().await.get(character_id).unwrap(); + let room_id = *self.item_state.character_room.read().await.get(character_id) + .ok_or_else(|| anyhow::Error::from(ItemStateError::NoCharacter(*character_id))) + .with_context(|| format!("character {character_id}\nrooms: {:#?}", self.item_state.character_room))?; Ok(FloorState { character_id: *character_id, - local: get_or_clone(&self.item_state.character_floor, &self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter).await?, - shared: get_or_clone(&self.item_state.room_floor, &self.proxied_state.room_floor, room_id, ItemStateError::NoRoom).await?, + local: get_or_clone(&self.item_state.character_floor, &self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter).await + .with_context(|| format!("no local_floor state: {character_id:?} {:#?}\nproxy: {:#?}", self.item_state.character_floor, self.proxied_state.character_floor))?, + shared: get_or_clone(&self.item_state.room_floor, &self.proxied_state.room_floor, room_id, ItemStateError::NoRoom).await + .with_context(|| format!("no share_floor state: {character_id:?} {:#?}\nproxy: {:#?}", self.item_state.room_floor, self.proxied_state.room_floor))?, }) } pub async fn set_floor(&mut self, floor: FloorState) { - let room_id = *self.item_state.character_room.read().await.get(&floor.character_id).unwrap(); + let room_id = *self.item_state.character_room.read().await.get(&floor.character_id) + .ok_or_else(|| anyhow::Error::from(ItemStateError::NoCharacter(floor.character_id))) + .with_context(|| format!("character {}\nrooms: {:#?}", floor.character_id, self.item_state.character_room)).unwrap(); self.proxied_state.character_floor.lock().await.insert(floor.character_id, floor.local); self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared); } diff --git a/src/ship/items/tasks.rs b/src/ship/items/tasks.rs index ad3e585..c0d71ed 100644 --- a/src/ship/items/tasks.rs +++ b/src/ship/items/tasks.rs @@ -281,7 +281,7 @@ where entity_gateway.with_transaction(|transaction| async move { let item_state_proxy = ItemStateProxy::new(item_state.clone()); let ((item_state_proxy, transaction), (pkts, new_character)) = ItemStateAction::default() - .act(actions::take_item_from_inventory(character.id, *item_id, amount)) + .act(actions::remove_item_from_inventory(character.id, *item_id, amount)) .act(actions::use_consumed_item(character)) .act(actions::fork( actions::foreach(actions::apply_item_action_packets(character.id, area_client)), diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 1d1bb47..6f53fdf 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -489,7 +489,6 @@ impl Blocks { .get_mut(block) .ok_or_else(|| ShipError::InvalidBlock(block).into()) } - } #[derive(Clone)] diff --git a/tests/test_bank.rs b/tests/test_bank.rs index e809371..2034070 100644 --- a/tests/test_bank.rs +++ b/tests/test_bank.rs @@ -1072,7 +1072,7 @@ async fn test_withdraw_partial_stacked_item() { assert!(packets.len() == 2); assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)})) - if create_item.item_id == 0x20002 + if create_item.item_id == 0x20001 )); let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); diff --git a/tests/test_item_pickup.rs b/tests/test_item_pickup.rs index b216c13..820e167 100644 --- a/tests/test_item_pickup.rs +++ b/tests/test_item_pickup.rs @@ -734,7 +734,7 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() { ship.handle(ClientId(2), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { client: 0, target: 0, - item_id: 0x10003, + item_id: 0x10001, map_area: 0, unknown: [0; 3] })))).await.unwrap(); diff --git a/tests/test_item_use.rs b/tests/test_item_use.rs index 6474e6f..b6b8502 100644 --- a/tests/test_item_use.rs +++ b/tests/test_item_use.rs @@ -445,6 +445,7 @@ async fn test_use_monogrinder() { + // TODO: tests for ALL ITEMS WOW /* diff --git a/tests/test_rooms.rs b/tests/test_rooms.rs index 05a3852..00b512d 100644 --- a/tests/test_rooms.rs +++ b/tests/test_rooms.rs @@ -12,112 +12,6 @@ mod common; use common::*; -#[async_std::test] -async fn test_item_ids_reset_when_rejoining_rooms() { - let mut entity_gateway = InMemoryGateway::default(); - - let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; - let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; - - let mut p1_inv = Vec::new(); - for _ in 0..3usize { - p1_inv.push(entity_gateway.create_item( - item::NewItemEntity { - item: item::ItemDetail::Weapon( - item::weapon::Weapon { - weapon: item::weapon::WeaponType::Saber, - grind: 0, - special: None, - attrs: [None, None, None], - tekked: true, - } - ), - }).await.unwrap()); - } - - let mut p2_inv = Vec::new(); - for _ in 0..10usize { - p2_inv.push(entity_gateway.create_item( - item::NewItemEntity { - item: item::ItemDetail::Weapon( - item::weapon::Weapon { - weapon: item::weapon::WeaponType::Saber, - grind: 0, - special: None, - attrs: [None, None, None], - tekked: true, - } - ), - }).await.unwrap()); - } - - entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); - entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); - - let mut ship = Box::new(ShipServerState::builder() - .gateway(entity_gateway.clone()) - .build()); - log_in_char(&mut ship, ClientId(1), "a1", "a").await; - log_in_char(&mut ship, ClientId(2), "a2", "a").await; - - join_lobby(&mut ship, ClientId(1)).await; - join_lobby(&mut ship, ClientId(2)).await; - - create_room(&mut ship, ClientId(1), "room", "").await; - let p = ship.handle(ClientId(2), RecvShipPacket::MenuSelect(MenuSelect { - menu: ROOM_MENU_ID, - item: 0, - })).await.unwrap(); - ship.handle(ClientId(2), RecvShipPacket::DoneBursting(DoneBursting {})).await.unwrap(); - - match &p[1].1 { - SendShipPacket::AddToRoom(add_to) => { - assert_eq!(add_to.playerinfo.inventory.items.iter().map(|k| k.item_id).collect::>(), - vec![0x210000,0x210001,0x210002,0x210003,0x210004,0x210005,0x210006,0x210007,0x210008,0x210009, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); - }, - _ => panic!(), - } - - leave_room(&mut ship, ClientId(2)).await; - - let p = ship.handle(ClientId(2), RecvShipPacket::MenuSelect(MenuSelect { - menu: ROOM_MENU_ID, - item: 0, - })).await.unwrap(); - - match &p[1].1 { - SendShipPacket::AddToRoom(add_to) => { - assert_eq!(add_to.playerinfo.inventory.items.iter().map(|k| k.item_id).collect::>(), - vec![0x210000,0x210001,0x210002,0x210003,0x210004,0x210005,0x210006,0x210007,0x210008,0x210009, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); - }, - _ => panic!(), - } -} - -/* -#[async_std::test] -async fn test_load_rare_monster_default_appear_rates() { - let mut entity_gateway = InMemoryGateway::default(); - let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; - let mut ship = Box::new(ShipServerState::builder() - .gateway(entity_gateway.clone()) - .build()); - log_in_char(&mut ship, ClientId(1), "a1", "a").await; - join_lobby(&mut ship, ClientId(1)).await; - create_room(&mut ship, ClientId(1), "room", "").await; - - // assume episode 1 - ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move { - let rates = &*room.rare_monster_table; - for (_monster, rate) in rates.clone().appear_rate { - assert_eq!(rate, 0.001953125f32); // 1/512 = 0.001953125 - } - })).await.unwrap(); -} -*/ - #[async_std::test] async fn test_set_valid_quest_group() { let mut entity_gateway = InMemoryGateway::default(); @@ -226,3 +120,6 @@ async fn test_cannot_join_room_after_its_closed() { msg: _expectedmsg, // wow yes cool rust is so great literally the best i can't put a String::from() directly in here. })))); } + + +// TODO: test joining twice errors not hangs forever -- 2.36.0 From aac2e429ed9802faec0357f3e6e0fc0dea775198 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 19:23:20 -0700 Subject: [PATCH 07/12] tests for item_id parity issues --- tests/test_item_id.rs | 298 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 tests/test_item_id.rs diff --git a/tests/test_item_id.rs b/tests/test_item_id.rs new file mode 100644 index 0000000..dfe8b7b --- /dev/null +++ b/tests/test_item_id.rs @@ -0,0 +1,298 @@ +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::item; +use elseware::ship::ship::{ShipServerState, RecvShipPacket}; +use elseware::entity::character::TechLevel; +//use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType}; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; + +#[path = "common.rs"] +mod common; +use common::*; + + +#[async_std::test] +async fn test_use_monomate_after_leaving_and_rejoining_room() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + + let mut p1_items = Vec::new(); + for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() { + let mut item = Vec::new(); + for _ in 0..2usize { + item.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + }).await.unwrap()); + } + p1_items.push(item::InventoryItemEntity::Stacked(item)); + } + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap(); + + let mut p2_items = Vec::new(); + for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() { + let mut item = Vec::new(); + for _ in 0..2usize { + item.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + }).await.unwrap()); + } + p2_items.push(item::InventoryItemEntity::Stacked(item)); + } + + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_items)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + join_room(&mut ship, ClientId(2), 0).await; + leave_room(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).await; + leave_room(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).await; + leave_room(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).await; + + leave_room(&mut ship, ClientId(1)).await; + join_room(&mut ship, ClientId(1), 0).await; + + ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10003, + })))).await.unwrap(); + + ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x210006, + })))).await.unwrap(); + + let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(inventory_items.items.len(), 2); + inventory_items.items[0].with_stacked(|items| { + assert_eq!(items.len(), 2) + }).unwrap(); + inventory_items.items[1].with_stacked(|items| { + assert_eq!(items.len(), 1) + }).unwrap(); + + let inventory_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(inventory_items.items.len(), 2); + inventory_items.items[0].with_stacked(|items| { + assert_eq!(items.len(), 1) + }).unwrap(); + inventory_items.items[1].with_stacked(|items| { + assert_eq!(items.len(), 2) + }).unwrap(); +} + + + +#[async_std::test] +async fn test_using_some_monomates_after_a_convoluted_series_of_leaves_and_joins() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + let (_user3, char3) = new_user_character(&mut entity_gateway, "a3", "a", 1).await; + + let mut p1_items = Vec::new(); + for tool in vec![item::tool::ToolType::Monofluid, item::tool::ToolType::Difluid, item::tool::ToolType::Trifluid].into_iter() { + let mut item = Vec::new(); + for _ in 0..2usize { + item.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + }).await.unwrap()); + } + p1_items.push(item::InventoryItemEntity::Stacked(item)); + } + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap(); + + let mut p2_items = Vec::new(); + for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() { + let mut item = Vec::new(); + for _ in 0..6usize { + item.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + }).await.unwrap()); + } + p2_items.push(item::InventoryItemEntity::Stacked(item)); + } + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_items)).await.unwrap(); + + let mut p3_items = Vec::new(); + for _ in 0..5usize { + p3_items.push( + item::InventoryItemEntity::Individual( + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap() + )); + } + entity_gateway.set_character_inventory(&char3.id, &item::InventoryEntity::new(p3_items)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + log_in_char(&mut ship, ClientId(3), "a3", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + join_lobby(&mut ship, ClientId(3)).await; + + // so lets trace the item_ids here as it is dumb: + create_room(&mut ship, ClientId(1), "room", "").await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2 : ; 0x210000 + // g3 : ; 0x410000 + + join_room(&mut ship, ClientId(2), 0).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x10003 + // g2/p2: 0x210000 0x210001 ; 0x210002 + // g3 : ; 0x410000 + + ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x210000, + })))).await.unwrap(); + + join_room(&mut ship, ClientId(3), 0).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2/p2: 0x210000 0x210001 ; 0x210002 + // g3/p3: 0x410000 0x410001 0x410002 0x410003 0x0410004 ; 0x410005 + + leave_room(&mut ship, ClientId(2)).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2 : ; 0x210002 + // g3/p3: 0x410000 0x410001 0x410002 0x410003 0x410004 ; 0x410005 + + join_room(&mut ship, ClientId(2), 0).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2/p2: 0x210002 0x210003 ; 0x210004 + // g3/p3: 0x410000 0x410001 0x410002 0x410003 0x410004 ; 0x410005 + + leave_room(&mut ship, ClientId(2)).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2 : ; 0x210004 + // g3/p3: 0x410000 0x410001 0x410002 0x410003 0x410004 ; 0x410005 + + leave_room(&mut ship, ClientId(3)).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2 : ; 0x210004 + // g3 : ; 0x410005 + + join_room(&mut ship, ClientId(3), 0).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2/p3: 0x210004 0x210005 0x210006 0x210007 0x210008 ; 0x210009 + // g3 : ; 0x410007 + + join_room(&mut ship, ClientId(2), 0).await; + // g1/p1: 0x010000 0x010001 0x010002 ; 0x010003 + // g2/p3: 0x210004 0x210005 0x210006 0x210007 0x210008 ; 0x210009 + // g3/p2: 0x410005 0x410006 ; 0x410007 + + ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x410005, + })))).await.unwrap(); + + leave_room(&mut ship, ClientId(1)).await; + leave_room(&mut ship, ClientId(2)).await; + // g1 : ; 0x010003 + // g2/p3: 0x210004 0x210005 0x210006 0x210007 0x210008 ; 0x210009 + // g3 : ; 0x410007 + + join_room(&mut ship, ClientId(2), 0).await; + // g1/p2: 0x010003 0x010004 ; 0x010005 + // g2/p3: 0x210004 0x210005 0x210006 0x210007 0x210008 ; 0x210009 + // g3 : ; 0x410007 + + join_room(&mut ship, ClientId(1), 0).await; + // g1/p2: 0x010003 0x010004 ; 0x010005 + // g2/p3: 0x210004 0x210005 0x210006 0x210007 0x210008 ; 0x210009 + // g3/p1: 0x410008 0x410009 0x41000A ; 0x41000B + + ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x010003, + })))).await.unwrap(); + + leave_room(&mut ship, ClientId(2)).await; + leave_room(&mut ship, ClientId(3)).await; + join_room(&mut ship, ClientId(3), 0).await; + join_room(&mut ship, ClientId(2), 0).await; + // g1/p3: 0x010005 0x010006 0x010007 0x010008 0x010009 ; 0x010009 + // g2/p2: 0x210009 0x21000A ; 0x21000B + // g3/p1: 0x410008 0x410009 0x41000A ; 0x41000B + + ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x210009, + })))).await.unwrap(); + + leave_room(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).await; + // g1/p3: 0x010005 0x010006 0x010007 0x010008 0x010009 ; 0x010009 + // g2/p2: 0x21000B 0x21000C ; 0x21000D + // g3/p1: 0x410008 0x410009 0x401000A ; 0x41000B + + ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x21000B, + })))).await.unwrap(); + + + let inventory_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(inventory_items.items.len(), 2); + inventory_items.items[0].with_stacked(|items| { + assert_eq!(items.len(), 1) + }).unwrap(); + inventory_items.items[1].with_stacked(|items| { + assert_eq!(items.len(), 6) + }).unwrap(); +} + -- 2.36.0 From 0d3161e1b44debd4fb2b3e2da2e3c6ca8a4d03be Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 20:00:03 -0700 Subject: [PATCH 08/12] more error context --- src/ship/items/actions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index 53a59db..4351d09 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -5,6 +5,7 @@ use async_std::sync::Arc; use std::future::Future; use std::pin::Pin; use std::iter::IntoIterator; +use anyhow::Context; use libpso::packet::{ship::Message, messages::GameMessage}; use crate::ship::map::MapArea; @@ -112,7 +113,8 @@ where let mut inventory = item_state.inventory(&character_id).await?; let item = inventory.remove_item(&item_id, amount) .await - .ok_or_else(|| ItemStateError::NoInventoryItem(item_id))?; + .ok_or_else(|| ItemStateError::NoInventoryItem(item_id)) + .with_context(|| format!("{inventory:#?}"))?; transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; item_state.set_inventory(inventory).await; -- 2.36.0 From 85d9fc9ce3f1137222ea4616e327a732412d08d2 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 31 Jan 2023 20:00:18 -0700 Subject: [PATCH 09/12] a test for bank item_id stuff --- tests/test_item_id.rs | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_item_id.rs b/tests/test_item_id.rs index dfe8b7b..f8f165f 100644 --- a/tests/test_item_id.rs +++ b/tests/test_item_id.rs @@ -296,3 +296,78 @@ async fn test_using_some_monomates_after_a_convoluted_series_of_leaves_and_joins }).unwrap(); } + +#[async_std::test] +async fn test_depositing_a_full_stack_then_withdrawing_part() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + + let mut p1_items = Vec::new(); + for tool in vec![item::tool::ToolType::Monofluid, item::tool::ToolType::Difluid, item::tool::ToolType::Trifluid].into_iter() { + let mut item = Vec::new(); + for _ in 0..5usize { + item.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + }).await.unwrap()); + } + p1_items.push(item::InventoryItemEntity::Stacked(item)); + } + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap(); + + let mut monomates = Vec::new(); + for _ in 0..3usize { + monomates.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ), + }).await.unwrap()); + } + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), &item::BankName("".into())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankRequest(BankRequest { + client: 0, + target: 0, + unknown: 0, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + client: 0, + target: 0, + item_id: 0x10001, + action: 0, + item_amount: 5, + meseta_amount: 0, + unknown: 0, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + client: 0, + target: 0, + item_id: 0x10001, + action: 1, + item_amount: 3, + meseta_amount: 0, + unknown: 0, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x20001, + })))).await.unwrap(); +} -- 2.36.0 From 6399cbbb0e45463427e2618735468040f0c9b633 Mon Sep 17 00:00:00 2001 From: jake Date: Wed, 1 Feb 2023 19:21:30 -0700 Subject: [PATCH 10/12] fix mag pbs maybe I dunno --- data/item_stats/mag_stats.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/item_stats/mag_stats.toml b/data/item_stats/mag_stats.toml index 6fb9d47..68376e4 100644 --- a/data/item_stats/mag_stats.toml +++ b/data/item_stats/mag_stats.toml @@ -11,7 +11,7 @@ photon_blast = "Pilla" [Surya] feed_table = 3 -photon_blast = "Leilla" +photon_blast = "Golla" [Vayu] feed_table = 4 @@ -19,7 +19,7 @@ photon_blast = "MyllaYoulla" [Varaha] feed_table = 4 -photon_blast = "Leilla" +photon_blast = "Golla" [Kama] feed_table = 4 @@ -27,7 +27,7 @@ photon_blast = "Pilla" [Ushasu] feed_table = 4 -photon_blast = "Leilla" +photon_blast = "Golla" [Apsaras] feed_table = 4 @@ -35,7 +35,7 @@ photon_blast = "Estlla" [Kumara] feed_table = 4 -photon_blast = "Leilla" +photon_blast = "Golla" [Kaitabha] feed_table = 4 @@ -55,7 +55,7 @@ photon_blast = "Estlla" [Rudra] feed_table = 2 -photon_blast = "Leilla" +photon_blast = "Golla" [Marutah] feed_table = 2 @@ -63,7 +63,7 @@ photon_blast = "Pilla" [Yaksa] feed_table = 5 -photon_blast = "Leilla" +photon_blast = "Golla" [Sita] feed_table = 5 @@ -99,7 +99,7 @@ photon_blast = "Estlla" [Vritra] feed_table = 1 -photon_blast = "Golla" +photon_blast = "Leilla" [Namuci] feed_table = 2 @@ -107,7 +107,7 @@ photon_blast = "MyllaYoulla" [Sumba] feed_table = 2 -photon_blast = "Leilla" +photon_blast = "Golla" [Naga] feed_table = 6 @@ -144,7 +144,7 @@ photon_blast = "Estlla" [Naraka] feed_table = 6 -photon_blast = "Leilla" +photon_blast = "Golla" [Madhu] feed_table = 6 -- 2.36.0 From 77f69a9d232c3cfe80c02b0804698ed61cd4ef66 Mon Sep 17 00:00:00 2001 From: jake Date: Wed, 1 Feb 2023 23:30:53 -0700 Subject: [PATCH 11/12] fix more dumb meseta things --- src/ship/items/actions.rs | 1 + tests/test_bank.rs | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index 4351d09..f4dcbec 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -292,6 +292,7 @@ where let mut inventory = item_state.inventory(&character_id).await?; inventory.add_meseta_no_overflow(amount)?; transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + item_state.set_inventory(inventory).await; Ok(((item_state, transaction), ())) }) diff --git a/tests/test_bank.rs b/tests/test_bank.rs index 2034070..e2c200b 100644 --- a/tests/test_bank.rs +++ b/tests/test_bank.rs @@ -1579,3 +1579,57 @@ async fn test_withdraw_meseta_inventory_is_maxed() { assert!(c1_meseta.0 == 999999); assert!(c1_bank_meseta.0 == 300); } + + + +#[async_std::test] +async fn test_withdraw_meseta_and_buy_a_few_monomates_with_it() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + entity_gateway.set_character_meseta(&char1.id, item::Meseta(100)).await.unwrap(); + entity_gateway.set_bank_meseta(&char1.id, &item::BankName("".into()), item::Meseta(300)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankRequest(BankRequest { + client: 0, + target: 0, + unknown: 0, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + action: 1, + item_amount: 0, + meseta_amount: 60, + unknown: 0, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest { + client: 255, + target: 255, + shop_type: 0, + })))).await.unwrap(); + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem { + client: 255, + target: 255, + item_id: 0x10000, + shop_type: 0, + shop_index: 0, + amount: 3, + unknown1: 0, + })))).await.unwrap(); + + //let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); + //let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); + //assert!(c1_meseta.0 == 23); + //assert!(c1_bank_meseta.0 == 277); +} -- 2.36.0 From 45996e9d011e9221f5ed6b8cbb1d0bcc81d7a2f3 Mon Sep 17 00:00:00 2001 From: jake Date: Thu, 2 Feb 2023 00:10:15 -0700 Subject: [PATCH 12/12] clippy --- src/ship/items/apply_item.rs | 2 +- src/ship/items/bank.rs | 2 +- src/ship/items/inventory.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs index d73941e..6978f2e 100644 --- a/src/ship/items/apply_item.rs +++ b/src/ship/items/apply_item.rs @@ -12,7 +12,7 @@ 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::{InventoryItem, InventoryItemDetail}; +use crate::ship::items::inventory::InventoryItemDetail; #[derive(Error, Debug)] diff --git a/src/ship/items/bank.rs b/src/ship/items/bank.rs index 4f922af..cf88ae3 100644 --- a/src/ship/items/bank.rs +++ b/src/ship/items/bank.rs @@ -134,7 +134,7 @@ impl BankState { item.item_id = ClientItemId(*bitem_id + i as u32); } - *bitem_id = *bitem_id + self.bank.0.len() as u32; + *bitem_id += self.bank.0.len() as u32; } pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> { diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index 19fa372..da131d8 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -214,7 +214,7 @@ impl InventoryState { item.item_id = ClientItemId(*bitem_id + i as u32); } - *bitem_id = *bitem_id + self.inventory.0.len() as u32; + *bitem_id += self.inventory.0.len() as u32; } pub async fn new_item_id(&mut self) -> ClientItemId { -- 2.36.0