fix picking up stacked items messing up inventory count
This commit is contained in:
		
							parent
							
								
									649e79c332
								
							
						
					
					
						commit
						c26bc895da
					
				| @ -21,16 +21,16 @@ struct RoomItemId(RoomId, u32); | ||||
| #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] | ||||
| pub struct ClientItemId(pub u32); | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| enum ActiveItemEntityId { | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub enum ActiveItemEntityId { | ||||
|     Individual(ItemEntityId), | ||||
|     Stacked(Vec<ItemEntityId>), | ||||
|     Meseta(Meseta), | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| enum HeldItemType { | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub enum HeldItemType { | ||||
|     Individual(ItemDetail), | ||||
|     Stacked(Tool, usize), | ||||
| } | ||||
| @ -89,10 +89,10 @@ impl FloorItemType { | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct InventoryItem { | ||||
|     entity_id: ActiveItemEntityId, | ||||
|     item_id: ClientItemId, | ||||
|     item: HeldItemType, | ||||
|     equipped: bool, | ||||
|     pub entity_id: ActiveItemEntityId, | ||||
|     pub item_id: ClientItemId, | ||||
|     pub item: HeldItemType, | ||||
|     pub equipped: bool, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| @ -113,6 +113,7 @@ pub struct BankItem { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct CharacterInventory<'a>(&'a Vec<InventoryItem>); | ||||
| 
 | ||||
| impl<'a> CharacterInventory<'a> { | ||||
| @ -132,6 +133,10 @@ impl<'a> CharacterInventory<'a> { | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn slot(&self, slot: usize) -> Option<&'a InventoryItem> { | ||||
|         self.0.get(slot) | ||||
|     } | ||||
| 
 | ||||
|     pub fn count(&self) -> usize { | ||||
|         self.0.len() | ||||
|     } | ||||
| @ -325,13 +330,46 @@ impl ItemManager { | ||||
|                     inventory.push(inventory_item); | ||||
|                 } // else something went very wrong TODO: log it
 | ||||
|             }, | ||||
|             FloorItemType::Stacked(tool, usize) => { | ||||
|                 let inventory_item = InventoryItem { | ||||
|                     entity_id: floor_item.entity_id, | ||||
|                     item_id: floor_item.item_id, | ||||
|                     item: HeldItemType::Stacked(tool, usize), | ||||
|                     equipped: false, | ||||
|                 }; | ||||
|             FloorItemType::Stacked(tool, amount) => { | ||||
|                 let inventory_item = inventory.iter_mut() | ||||
|                     .filter(|i| { | ||||
|                         if let HeldItemType::Stacked(tooltype, _amount) = i.item { | ||||
|                             tooltype == tool | ||||
|                         } | ||||
|                         else { | ||||
|                             false | ||||
|                         } | ||||
|                     }) | ||||
|                     .next() | ||||
|                     .map(|existing_inv_item| { | ||||
|                         // TOOD: check stack amount does not exceed limit
 | ||||
|                         if let (ActiveItemEntityId::Stacked(ref mut inv_item_id), | ||||
|                                 ActiveItemEntityId::Stacked(floor_item_id)) | ||||
|                             = (&mut existing_inv_item.entity_id, &floor_item.entity_id) | ||||
|                         { | ||||
|                             inv_item_id.append(&mut floor_item_id.clone()); | ||||
|                         } | ||||
| 
 | ||||
|                         if let (HeldItemType::Stacked(_inv_tooltype, ref mut inv_amount), | ||||
|                                 FloorItemType::Stacked(_floor_tooltype, floor_amount)) | ||||
|                             = (&mut existing_inv_item.item, &floor_item.item) | ||||
|                         { | ||||
|                             // TODO: check tools are eq?
 | ||||
|                             *inv_amount += floor_amount | ||||
|                         } | ||||
| 
 | ||||
|                         existing_inv_item.clone() | ||||
|                     }) | ||||
|                     .unwrap_or_else(|| { | ||||
|                         let picked_up_item = InventoryItem { | ||||
|                             entity_id: floor_item.entity_id, | ||||
|                             item_id: floor_item.item_id, | ||||
|                             item: HeldItemType::Stacked(tool, amount), | ||||
|                             equipped: false, | ||||
|                         }; | ||||
|                         inventory.push(picked_up_item.clone()); | ||||
|                         picked_up_item | ||||
|                     }); | ||||
| 
 | ||||
|                 if let ActiveItemEntityId::Stacked(item_ids) = &inventory_item.entity_id { | ||||
|                     for item_id in item_ids { | ||||
| @ -345,7 +383,6 @@ impl ItemManager { | ||||
|                             }, | ||||
|                         }); // TODO: error check
 | ||||
|                     }; | ||||
|                     inventory.push(inventory_item); | ||||
|                 } // else something went very wrong TODO: log it
 | ||||
|             }, | ||||
|             FloorItemType::Meseta(meseta) => { | ||||
|  | ||||
| @ -243,7 +243,7 @@ pub struct ShipServerState<EG: EntityGateway> { | ||||
|     level_table: CharacterLevelTable, | ||||
|     name: String, | ||||
|     rooms: Rooms, | ||||
|     item_manager: items::ItemManager, | ||||
|     pub item_manager: items::ItemManager, | ||||
|     quests: quests::QuestList, | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										216
									
								
								tests/test_item_pickup.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								tests/test_item_pickup.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,216 @@ | ||||
| use std::time::SystemTime; | ||||
| 
 | ||||
| use elseware::common::serverstate::{ClientId, ServerState}; | ||||
| use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; | ||||
| use elseware::entity::account::{UserAccountEntity, NewUserAccountEntity, NewUserSettingsEntity}; | ||||
| use elseware::entity::character::{CharacterEntity, NewCharacterEntity}; | ||||
| //use elseware::entity::item::{NewItemEntity, ItemDetail, ItemLocation};
 | ||||
| use elseware::entity::item; | ||||
| use elseware::ship::ship::{ShipServerState, RecvShipPacket}; | ||||
| use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType}; | ||||
| 
 | ||||
| use libpso::packet::ship::*; | ||||
| use libpso::packet::messages::*; | ||||
| use libpso::packet::login::{Login, Session}; | ||||
| use libpso::{utf8_to_array, utf8_to_utf16_array}; | ||||
| 
 | ||||
| 
 | ||||
| pub fn new_user_character<EG: EntityGateway>(entity_gateway: &mut EG, username: &str, password: &str) -> (UserAccountEntity, CharacterEntity) { | ||||
|     let new_user = NewUserAccountEntity { | ||||
|         username: username.into(), | ||||
|         password: bcrypt::hash(password, 5).unwrap(), | ||||
|         guildcard: 1, | ||||
|         team_id: None, | ||||
|         banned: false, | ||||
|         muted_until: SystemTime::now(), | ||||
|         created_at: SystemTime::now(), | ||||
|         flags: 0, | ||||
|     }; | ||||
| 
 | ||||
|     let user = entity_gateway.create_user(new_user).unwrap(); | ||||
|     let new_settings = NewUserSettingsEntity::new(user.id); | ||||
|     let _settings = entity_gateway.create_user_settings(new_settings).unwrap(); | ||||
|     let new_character = NewCharacterEntity::new(user.id); | ||||
|     let character = entity_gateway.create_character(new_character).unwrap(); | ||||
| 
 | ||||
|     (user, character) | ||||
| } | ||||
| 
 | ||||
| pub fn log_in_char<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, username: &str, password: &str) { | ||||
|     let username = username.to_string(); | ||||
|     let password = password.to_string(); | ||||
|     ship.handle(id, &RecvShipPacket::Login(Login { | ||||
|         tag: 0, | ||||
|         guildcard: 0, | ||||
|         version: 0, | ||||
|         unknown1: [0; 6], | ||||
|         team: 0, | ||||
|         username: utf8_to_array!(username, 16), | ||||
|         unknown2: [0; 32], | ||||
|         password: utf8_to_array!(password, 16), | ||||
|         unknown3: [0; 40], | ||||
|         hwinfo: [0; 8], | ||||
|         session: Session::new(), | ||||
|     })).unwrap().for_each(drop); | ||||
| } | ||||
| 
 | ||||
| pub fn join_lobby<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId) { | ||||
|     ship.handle(id, &RecvShipPacket::CharData(CharData { | ||||
|         _unknown: [0; 0x828] | ||||
|     })).unwrap().for_each(drop); | ||||
| } | ||||
| 
 | ||||
| pub fn create_room<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, name: &str, password: &str) { | ||||
|     ship.handle(id, &RecvShipPacket::CreateRoom(CreateRoom { | ||||
|         unknown: [0; 2], | ||||
|         name: utf8_to_utf16_array!(name, 16), | ||||
|         password: utf8_to_utf16_array!(password, 16), | ||||
|         difficulty: 0, | ||||
|         battle: 0, | ||||
|         challenge: 0, | ||||
|         episode: 1, | ||||
|         single_player: 0, | ||||
|         padding: [0; 3], | ||||
|     })).unwrap().for_each(drop); | ||||
|     ship.handle(id, &RecvShipPacket::DoneBursting(DoneBursting {})).unwrap().for_each(drop); | ||||
| } | ||||
| 
 | ||||
| pub fn join_room<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, room_id: u32) { | ||||
|     ship.handle(id, &RecvShipPacket::MenuSelect(MenuSelect { | ||||
|         menu: ROOM_MENU_ID, | ||||
|         item: room_id, | ||||
|     })).unwrap().for_each(drop); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[test] | ||||
| fn test_pick_up_item_stack_of_items_already_in_inventory() { | ||||
|     let mut entity_gateway = InMemoryGateway::new(); | ||||
| 
 | ||||
|     let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a"); | ||||
|     let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a"); | ||||
| 
 | ||||
|     entity_gateway.create_item( | ||||
|         item::NewItemEntity { | ||||
|             item: item::ItemDetail::Tool( | ||||
|                 item::tool::Tool { | ||||
|                     tool: item::tool::ToolType::Monomate | ||||
|                 } | ||||
|             ), | ||||
|             location: item::ItemLocation::Inventory { | ||||
|                 character_id: char1.id, | ||||
|                 slot: 0, | ||||
|                 equipped: false, | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|     for (slot, tool) in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter().enumerate() { | ||||
|         for _ in 0..5 { | ||||
|             entity_gateway.create_item( | ||||
|                 item::NewItemEntity { | ||||
|                     item: item::ItemDetail::Tool( | ||||
|                         item::tool::Tool { | ||||
|                             tool: tool | ||||
|                         } | ||||
|                     ), | ||||
|                     location: item::ItemLocation::Inventory { | ||||
|                         character_id: char2.id, | ||||
|                         slot: slot, | ||||
|                         equipped: false, | ||||
|                     } | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
|     
 | ||||
|     let mut ship = ShipServerState::new(entity_gateway.clone()); | ||||
|     log_in_char(&mut ship, ClientId(1), "a1", "a"); | ||||
|     log_in_char(&mut ship, ClientId(2), "a2", "a"); | ||||
| 
 | ||||
|     join_lobby(&mut ship, ClientId(1)); | ||||
|     join_lobby(&mut ship, ClientId(2)); | ||||
| 
 | ||||
|     create_room(&mut ship, ClientId(1), "room", ""); | ||||
|     join_room(&mut ship, ClientId(2), 0); | ||||
| 
 | ||||
|     ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDropItem(PlayerDropItem { | ||||
|         client: 0, | ||||
|         target: 0, | ||||
|         unknown1: 0, | ||||
|         area: 0, | ||||
|         item_id: 0x210000, | ||||
|         x: 0.0, | ||||
|         y: 0.0, | ||||
|         z: 0.0, | ||||
|     })))).unwrap().for_each(drop); | ||||
| 
 | ||||
|     ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { | ||||
|         client: 0, | ||||
|         target: 0, | ||||
|         item_id: 0x210000, | ||||
|         area: 0, | ||||
|         unknown: [0; 3] | ||||
|     })))).unwrap().for_each(drop); | ||||
| 
 | ||||
|     let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); | ||||
|     assert!(p1_inventory.count() == 1); | ||||
|     let inventory_item = p1_inventory.slot(0).unwrap(); | ||||
|     assert!(inventory_item.entity_id == ActiveItemEntityId::Stacked(vec![item::ItemEntityId(1), item::ItemEntityId(2), item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5), item::ItemEntityId(6)])); | ||||
|     assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_pick_up_item_stack_of_items_not_already_held() { | ||||
|     let mut entity_gateway = InMemoryGateway::new(); | ||||
| 
 | ||||
|     let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a"); | ||||
|     let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a"); | ||||
| 
 | ||||
|     entity_gateway.create_item( | ||||
|         item::NewItemEntity { | ||||
|             item: item::ItemDetail::Tool( | ||||
|                 item::tool::Tool { | ||||
|                     tool: item::tool::ToolType::Monomate | ||||
|                 } | ||||
|             ), | ||||
|             location: item::ItemLocation::Inventory { | ||||
|                 character_id: char2.id, | ||||
|                 slot: 0, | ||||
|                 equipped: false, | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|     let mut ship = ShipServerState::new(entity_gateway.clone()); | ||||
|     log_in_char(&mut ship, ClientId(1), "a1", "a"); | ||||
|     log_in_char(&mut ship, ClientId(2), "a2", "a"); | ||||
| 
 | ||||
|     join_lobby(&mut ship, ClientId(1)); | ||||
|     join_lobby(&mut ship, ClientId(2)); | ||||
| 
 | ||||
|     create_room(&mut ship, ClientId(1), "room", ""); | ||||
|     join_room(&mut ship, ClientId(2), 0); | ||||
| 
 | ||||
|     ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDropItem(PlayerDropItem { | ||||
|         client: 0, | ||||
|         target: 0, | ||||
|         unknown1: 0, | ||||
|         area: 0, | ||||
|         item_id: 0x210000, | ||||
|         x: 0.0, | ||||
|         y: 0.0, | ||||
|         z: 0.0, | ||||
|     })))).unwrap().for_each(drop); | ||||
| 
 | ||||
|     ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { | ||||
|         client: 0, | ||||
|         target: 0, | ||||
|         item_id: 0x210000, | ||||
|         area: 0, | ||||
|         unknown: [0; 3] | ||||
|     })))).unwrap().for_each(drop); | ||||
| 
 | ||||
|     let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); | ||||
|     assert!(p1_inventory.count() == 1); | ||||
|     let inventory_item = p1_inventory.slot(0).unwrap(); | ||||
|     assert!(inventory_item.entity_id == ActiveItemEntityId::Stacked(vec![item::ItemEntityId(1)])); | ||||
|     assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 1)); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user