Browse Source

Merge pull request 'i t e m r e f a c t o r' (#189) from it_never_ends into master

pbs
jake 4 years ago
parent
commit
c0421e0c16
  1. 4
      src/entity/character.rs
  2. 4
      src/entity/gateway/entitygateway.rs
  3. 8
      src/entity/gateway/inmemory.rs
  4. 6
      src/entity/item/mod.rs
  5. 15
      src/ship/character.rs
  6. 881
      src/ship/items.rs
  7. 186
      src/ship/items/bank.rs
  8. 251
      src/ship/items/floor.rs
  9. 380
      src/ship/items/inventory.rs
  10. 529
      src/ship/items/manager.rs
  11. 15
      src/ship/items/mod.rs
  12. 36
      src/ship/packet/builder/message.rs
  13. 62
      src/ship/packet/handler/direct_message.rs
  14. 2
      src/ship/packet/handler/lobby.rs
  15. 17
      src/ship/packet/handler/message.rs
  16. 10
      src/ship/ship.rs
  17. 2
      tests/common.rs
  18. 219
      tests/test_bank.rs
  19. 1
      tests/test_item_pickup.rs

4
src/entity/character.rs

@ -259,6 +259,7 @@ pub struct NewCharacterEntity {
pub tech_menu: CharacterTechMenu,
pub meseta: u32,
pub bank_meseta: u32,
}
impl NewCharacterEntity {
@ -278,6 +279,7 @@ impl NewCharacterEntity {
materials: CharacterMaterials::default(),
tech_menu: CharacterTechMenu::new(),
meseta: 0,
bank_meseta: 0,
}
}
}
@ -303,4 +305,6 @@ pub struct CharacterEntity {
pub tech_menu: CharacterTechMenu,
pub meseta: u32,
// TODO: this should not be tied to the character
pub bank_meseta: u32,
}

4
src/entity/gateway/entitygateway.rs

@ -56,6 +56,10 @@ pub trait EntityGateway: Send + Sync + Clone {
unimplemented!();
}
async fn change_item_location(&mut self, _item_id: &ItemEntityId, _item_location: ItemLocation) {
unimplemented!();
}
async fn get_items_by_character(&self, _char: &CharacterEntity) -> Vec<ItemEntity> {
unimplemented!();
}

8
src/entity/gateway/inmemory.rs

@ -123,6 +123,7 @@ impl EntityGateway for InMemoryGateway {
materials: character.materials,
tech_menu: character.tech_menu,
meseta: character.meseta,
bank_meseta: character.bank_meseta,
};
characters.insert(new_character.id, new_character.clone());
Some(new_character)
@ -157,6 +158,13 @@ impl EntityGateway for InMemoryGateway {
items.insert(item.id, item.clone());
}
async fn change_item_location(&mut self, item_id: &ItemEntityId, item_location: ItemLocation) {
self.items.lock().unwrap().get_mut(&item_id)
.map(|item_entity| {
item_entity.location = item_location
});
}
async fn get_items_by_character(&self, character: &CharacterEntity) -> Vec<ItemEntity> {
let items = self.items.lock().unwrap();
items

6
src/entity/item/mod.rs

@ -15,8 +15,8 @@ use crate::ship::drops::ItemDropType;
pub struct ItemEntityId(pub u32);
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
pub struct ItemId(u32);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BankName(String);
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BankName(pub String);
#[derive(Clone, Debug, PartialEq)]
pub enum ItemLocation {
@ -27,7 +27,7 @@ pub enum ItemLocation {
},
Bank {
character_id: CharacterEntityId,
slot: BankName,
name: BankName,
},
LocalFloor {
character_id: CharacterEntityId,

15
src/ship/character.rs

@ -1,7 +1,7 @@
use libpso::character::character;
use crate::common::leveltable::CharacterStats;
use crate::entity::character::CharacterEntity;
use crate::ship::items::CharacterInventory;
use crate::ship::items::{CharacterInventory, CharacterBank};
pub struct CharacterBytesBuilder<'a> {
character: Option<&'a CharacterEntity>,
@ -80,7 +80,8 @@ pub struct FullCharacterBytesBuilder<'a> {
character: Option<&'a CharacterEntity>,
stats: Option<&'a CharacterStats>,
level: Option<u32>,
inventory: Option<&'a CharacterInventory<'a>>,
inventory: Option<&'a CharacterInventory>,
bank: Option<&'a CharacterBank>,
key_config: Option<&'a [u8; 0x16C]>,
joystick_config: Option<&'a [u8; 0x38]>,
symbol_chat: Option<&'a [u8; 1248]>,
@ -95,6 +96,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
stats: None,
level: None,
inventory: None,
bank: None,
key_config: None,
joystick_config: None,
symbol_chat: None,
@ -130,6 +132,13 @@ impl<'a> FullCharacterBytesBuilder<'a> {
}
}
pub fn bank(self, bank: &'a CharacterBank) -> FullCharacterBytesBuilder<'a> {
FullCharacterBytesBuilder {
bank: Some(bank),
..self
}
}
pub fn key_config(self, key_config: &'a [u8; 0x16C]) -> FullCharacterBytesBuilder<'a> {
FullCharacterBytesBuilder {
key_config: Some(key_config),
@ -163,6 +172,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
let stats = self.stats.unwrap();
let level = self.level.unwrap();
let inventory = self.inventory.unwrap();
let bank = self.bank.unwrap();
let key_config = self.key_config.unwrap();
let joystick_config = self.joystick_config.unwrap();
let symbol_chat = self.symbol_chat.unwrap();
@ -196,6 +206,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
info_board: character.info_board.as_bytes(),
symbol_chats: *symbol_chat,
tech_menu: *tech_menu,
bank: bank.as_client_bank_items(),
..character::FullCharacter::default()
}
}

881
src/ship/items.rs

@ -1,881 +0,0 @@
use std::collections::{HashMap, BTreeMap};
use std::cmp::Ordering;
use thiserror::Error;
use futures::future::join_all;
use libpso::character::character;//::InventoryItem;
use crate::entity::gateway::EntityGateway;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, ItemLocation};
use crate::entity::item::{Meseta, NewItemEntity};
use crate::entity::item::tool::{Tool, ToolType};
use crate::ship::map::MapArea;
use crate::ship::ship::ItemDropLocation;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::location::{AreaClient, RoomId};
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ActiveItemId(pub u32);
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct RoomItemId(RoomId, u32);
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ClientItemId(pub u32);
#[derive(Debug, Clone)]
pub struct IndividualInventoryItem {
pub entity_id: ItemEntityId,
pub item_id: ClientItemId,
pub item: ItemDetail,
pub equipped: bool,
}
#[derive(Debug, Clone)]
pub struct StackedInventoryItem {
pub entity_ids: Vec<ItemEntityId>,
pub item_id: ClientItemId,
pub tool: Tool,
}
impl StackedInventoryItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
}
#[derive(Debug, Clone)]
pub enum InventoryItem {
Individual(IndividualInventoryItem),
Stacked(StackedInventoryItem),
}
pub enum InventoryItemAddToError {
BothAreNotStacked,
DifferentTool,
ExceedsCapacity,
}
impl InventoryItem {
pub fn item_id(&self) -> ClientItemId {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.item_id
},
InventoryItem::Stacked(stacked_inventory_item) => {
stacked_inventory_item.item_id
}
}
}
pub fn set_item_id(&mut self, item_id: ClientItemId) {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.item_id = item_id
},
InventoryItem::Stacked(stacked_inventory_item) => {
stacked_inventory_item.item_id = item_id
}
}
}
pub fn are_same_stackable_tool(&self, other_stacked_item: &StackedFloorItem) -> bool {
match self {
InventoryItem::Stacked(self_stacked_item) => {
self_stacked_item.tool == other_stacked_item.tool
&& self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable()
},
_ => false
}
}
pub fn can_combine_stacks(&self, other_stacked_item: &StackedFloorItem) -> bool {
match self {
InventoryItem::Stacked(self_stacked_item) => {
self_stacked_item.tool == other_stacked_item.tool
&& self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable()
&& self_stacked_item.count() + other_stacked_item.count() <= self_stacked_item.tool.max_stack()
},
_ => false
}
}
// TODO: result
pub fn combine_stacks(&mut self, other_stacked_item: &mut StackedFloorItem) {
match self {
InventoryItem::Stacked(self_stacked_item) => {
self_stacked_item.entity_ids.append(&mut other_stacked_item.entity_ids);
},
_ => {
}
}
}
pub fn equipped(&self) -> bool {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.equipped
},
InventoryItem::Stacked(_) => {
false
}
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
InventoryItem::Individual(item) => {
match &item.item {
ItemDetail::Weapon(w) => w.as_bytes(),
ItemDetail::Armor(a) => a.as_bytes(),
ItemDetail::Shield(s) => s.as_bytes(),
ItemDetail::Unit(u) => u.as_bytes(),
ItemDetail::Tool(t) => t.as_individual_bytes(),
ItemDetail::TechniqueDisk(d) => d.as_bytes(),
ItemDetail::Mag(m) => m.as_bytes(),
}
},
InventoryItem::Stacked(item) => {
item.tool.as_stacked_bytes(item.entity_ids.len())
},
}
}
pub fn can_add_to(&mut self, stacked_floor_item: &StackedFloorItem) -> Result<(), InventoryItemAddToError> {
if let InventoryItem::Stacked(stacked_inventory_item) = self {
if stacked_floor_item.tool != stacked_inventory_item.tool {
return Err(InventoryItemAddToError::DifferentTool)
}
if stacked_floor_item.tool.tool.max_stack() < (stacked_floor_item.count() + stacked_inventory_item.count()) {
return Err(InventoryItemAddToError::ExceedsCapacity)
}
Ok(())
}
else {
Err(InventoryItemAddToError::BothAreNotStacked)
}
}
pub fn add_to(&mut self, mut stacked_floor_item: StackedFloorItem) -> Result<(), InventoryItemAddToError> {
self.can_add_to(&stacked_floor_item)?;
if let InventoryItem::Stacked(stacked_inventory_item) = self {
stacked_inventory_item.entity_ids.append(&mut stacked_floor_item.entity_ids);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct IndividualFloorItem {
pub entity_id: ItemEntityId,
pub item_id: ClientItemId,
pub item: ItemDetail,
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Debug, Clone)]
pub struct StackedFloorItem {
pub entity_ids: Vec<ItemEntityId>,
pub item_id: ClientItemId,
pub tool: Tool,
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
}
impl StackedFloorItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
}
#[derive(Debug, Clone)]
pub struct MesetaFloorItem {
pub item_id: ClientItemId,
pub meseta: Meseta,
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Debug, Clone)]
pub enum FloorItem {
Individual(IndividualFloorItem),
Stacked(StackedFloorItem),
Meseta(MesetaFloorItem),
}
impl FloorItem {
pub fn item_id(&self) -> ClientItemId {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.item_id
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.item_id
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.item_id
}
}
}
pub fn x(&self) -> f32 {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.x
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.x
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.x
}
}
}
pub fn y(&self) -> f32 {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.y
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.y
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.y
}
}
}
pub fn z(&self) -> f32 {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.z
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.z
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.z
}
}
}
pub fn map_area(&self) -> MapArea {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.map_area
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.map_area
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.map_area
}
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.item.as_client_bytes()
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.tool.as_stacked_bytes(stacked_floor_item.count())
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.meseta.as_bytes()
}
}
}
}
#[derive(Debug)]
pub struct CharacterInventory<'a>(&'a Vec<InventoryItem>);
// TODO: this should actually do things
impl<'a> CharacterInventory<'a> {
pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] {
self.0.iter()
.enumerate()
.fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| {
let bytes = item.as_client_bytes();
inventory[slot].data1.copy_from_slice(&bytes[0..12]);
inventory[slot].data2.copy_from_slice(&bytes[12..16]);
inventory[slot].item_id = item.item_id().0;
// does this do anything?
inventory[slot].equipped = if item.equipped() { 1 } else { 0 };
// because this actually equips the item
inventory[slot].flags |= if item.equipped() { 8 } else { 0 };
inventory
})
}
pub fn slot(&self, slot: usize) -> Option<&'a InventoryItem> {
self.0.get(slot)
}
pub fn count(&self) -> usize {
self.0.len()
}
// TODO: function that gives out a handle to an item, allowing deletion of it from the inventory
}
pub enum TriggerCreateItem {
Yes,
No
}
#[derive(Error, Debug)]
#[error("")]
pub enum ItemManagerError {
EntityGatewayError,
NoSuchItemId(ClientItemId),
NoCharacter(CharacterEntityId),
CouldNotAddToInventory(FloorItem),
//ItemBelongsToOtherPlayer,
Idunnoman,
CouldNotSplitItem(InventoryItem),
CouldNotDropMeseta,
NotEnoughTools(Tool, usize, usize), // have, expected
}
pub struct ItemManager {
id_counter: u32,
character_inventory: HashMap<CharacterEntityId, Vec<InventoryItem>>,
character_floor: HashMap<CharacterEntityId, Vec<FloorItem>>,
character_room: HashMap<CharacterEntityId, RoomId>,
room_floor: HashMap<RoomId, Vec<FloorItem>>,
room_item_id_counter: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>,
}
impl ItemManager {
pub fn new() -> ItemManager {
ItemManager {
id_counter: 0,
character_inventory: HashMap::new(),
character_floor: HashMap::new(),
character_room: HashMap::new(),
room_floor: HashMap::new(),
room_item_id_counter: HashMap::new(),
}
}
pub fn next_global_item_id(&mut self) -> ClientItemId {
self.id_counter += 1;
ClientItemId(self.id_counter)
}
// TODO: Result
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) {
let items = entity_gateway.get_items_by_character(&character).await;
let inventory_items = items.into_iter()
.filter_map(|item| {
match item.location {
ItemLocation::Inventory{slot, equipped, ..} => Some((item.id, item.item, slot, equipped)),
_ => None,
}
})
.fold(BTreeMap::new(), |mut acc, (id, item, slot, equipped)| {
if item.is_stackable() {
if let ItemDetail::Tool(tool) = item {
let inventory_item = acc.entry(slot).or_insert(InventoryItem::Stacked(StackedInventoryItem {
entity_ids: Vec::new(),
item_id: self.next_global_item_id(),
tool: tool,
}));
if let InventoryItem::Stacked(ref mut stacked_inventory_item) = inventory_item {
stacked_inventory_item.entity_ids.push(id);
}
}
}
else {
acc.insert(slot, InventoryItem::Individual(IndividualInventoryItem {
entity_id: id,
item_id: self.next_global_item_id(),
item: item,
equipped: equipped,
}));
}
acc
});
let inventory = inventory_items.into_iter().map(|(k, v)| v).take(30).collect();
self.character_inventory.insert(character.id, inventory);
}
pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) {
let base_id = ((area_client.local_client.id() as u32) << 21) | 0x10000;
let inventory = self.character_inventory.get_mut(&character.id).unwrap();
for (i, item) in inventory.iter_mut().enumerate() {
item.set_item_id(ClientItemId(base_id + i as u32));
}
self.character_room.insert(character.id, room_id);
self.character_floor.insert(character.id, Vec::new());
self.room_floor.entry(room_id).or_insert(Vec::new());
let mut inc = 0xF0000000;
self.room_item_id_counter.entry(room_id).or_insert(Box::new(move || {
inc += 1;
ClientItemId(inc)
}));
}
pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<CharacterInventory, ItemManagerError> {
Ok(CharacterInventory(self.character_inventory.get(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?))
}
pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
self.character_inventory.remove(&character.id);
self.character_floor.remove(&character.id);
self.character_room.remove(&character.id)
.as_ref()
.map(|room| {
if self.character_room.iter().find(|(_, r)| *r == room).is_none() {
self.room_floor.remove(room);
}
});
}
pub fn get_inventory_item_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result<InventoryItem, ItemManagerError> {
let inventory = self.character_inventory.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
inventory.iter()
.filter(|item| {
item.item_id() == item_id
})
.nth(0)
.ok_or(ItemManagerError::NoSuchItemId(item_id))
.map(Clone::clone)
}
pub fn get_floor_item_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result<FloorItem, ItemManagerError> {
let floor = self.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get(room).ok_or(ItemManagerError::NoCharacter(character.id))?;
floor.iter()
.chain(shared_floor.iter())
.filter(|item| {
item.item_id() == item_id
})
.nth(0)
.ok_or(ItemManagerError::NoSuchItemId(item_id))
.map(Clone::clone)
}
pub async fn character_picks_up_item<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, floor_item: FloorItem) -> Result<TriggerCreateItem, ItemManagerError> {
let local_floor = self.character_floor.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let (floor_to_remove_from, position) = if let Some(position) = local_floor.iter().position(|item| item.item_id() == floor_item.item_id()) {
(local_floor, position)
}
else if let Some(position) = shared_floor.iter().position(|item| item.item_id() == floor_item.item_id()) {
(shared_floor, position)
}
else {
return Err(ItemManagerError::NoSuchItemId(floor_item.item_id()))
};
let trigger_create_item = match floor_item.clone() {
FloorItem::Individual(individual_floor_item) => {
if inventory.len() >= 30 {
return Err(ItemManagerError::CouldNotAddToInventory(floor_item));
}
entity_gateway.save_item(&ItemEntity {
id: individual_floor_item.entity_id,
item: individual_floor_item.item.clone(),
location: ItemLocation::Inventory {
character_id: character.id,
slot: inventory.len(),
equipped: false,
}
}).await;
let inventory_item = InventoryItem::Individual(IndividualInventoryItem {
entity_id: individual_floor_item.entity_id,
item_id: individual_floor_item.item_id,
item: individual_floor_item.item,
equipped: false,
});
inventory.push(inventory_item);
TriggerCreateItem::Yes
},
FloorItem::Stacked(mut stacked_floor_item) => {
let mut tool_in_inventory = inventory.iter_mut()
.enumerate()
.find(|(_, item)| {
item.are_same_stackable_tool(&stacked_floor_item)
});
match tool_in_inventory {
Some((slot, ref mut tool_in_inventory)) => {
if tool_in_inventory.can_combine_stacks(&stacked_floor_item) {
for entity_id in stacked_floor_item.entity_ids.iter() {
entity_gateway.save_item(&ItemEntity {
id: *entity_id,
item: ItemDetail::Tool(stacked_floor_item.tool),
location: ItemLocation::Inventory {
character_id: character.id,
slot: slot,
equipped: false,
}
}).await;
}
tool_in_inventory.combine_stacks(&mut stacked_floor_item)
}
else {
return Err(ItemManagerError::CouldNotAddToInventory(floor_item))
}
TriggerCreateItem::No
},
None => {
let slot = inventory.len();
for entity_id in stacked_floor_item.entity_ids.iter() {
entity_gateway.save_item(&ItemEntity {
id: *entity_id,
item: ItemDetail::Tool(stacked_floor_item.tool),
location: ItemLocation::Inventory {
character_id: character.id,
slot: slot,
equipped: false,
}
}).await;
}
inventory.push(InventoryItem::Stacked(StackedInventoryItem {
entity_ids: stacked_floor_item.entity_ids,
item_id: stacked_floor_item.item_id,
tool: stacked_floor_item.tool,
}));
TriggerCreateItem::Yes
}
}
},
FloorItem::Meseta(meseta_floor_item) => {
if character.meseta == 999999 {
return Err(ItemManagerError::CouldNotAddToInventory(floor_item));
}
character.meseta = std::cmp::min(character.meseta + meseta_floor_item.meseta.0, 999999);
entity_gateway.save_character(&character).await;
TriggerCreateItem::No
}
};
floor_to_remove_from.remove(position);
Ok(trigger_create_item)
}
pub async fn enemy_drop_item_on_local_floor<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&FloorItem, ItemManagerError> {
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
enum ItemOrMeseta {
Individual(ItemDetail),
Stacked(Tool),
Meseta(Meseta)
}
let item = match item_drop.item {
ItemDropType::Weapon(w) => ItemOrMeseta::Individual(ItemDetail::Weapon(w)),
ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)),
ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)),
ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)),
ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)),
ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)),
//ItemDropType::IndividualTool(t) => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
//ItemDropType::StackedTool(t, _) => ItemOrMeseta::Stacked(t),
ItemDropType::Tool(t) if t.tool.is_stackable() => ItemOrMeseta::Stacked(t),
ItemDropType::Tool(t) if !t.tool.is_stackable() => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)),
_ => unreachable!() // rust isnt smart enough to see that the conditional on tool catches everything
};
let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let floor_item = match item {
ItemOrMeseta::Individual(item_detail) => {
let entity = entity_gateway.create_item(NewItemEntity {
item: item_detail.clone(),
location: ItemLocation::LocalFloor {
character_id: character.id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}
}).await.ok_or(ItemManagerError::EntityGatewayError)?;
FloorItem::Individual(IndividualFloorItem {
entity_id: entity.id,
item_id: item_id,
item: item_detail,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
})
},
ItemOrMeseta::Stacked(tool) => {
let entity = entity_gateway.create_item(NewItemEntity {
item: ItemDetail::Tool(tool),
location: ItemLocation::LocalFloor {
character_id: character.id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}
}).await.ok_or(ItemManagerError::EntityGatewayError)?;
FloorItem::Stacked(StackedFloorItem {
entity_ids: vec![entity.id],
item_id: item_id,
tool: tool,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
})
},
ItemOrMeseta::Meseta(meseta) => {
FloorItem::Meseta(MesetaFloorItem {
item_id: item_id,
meseta: meseta,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
})
},
};
self.character_floor.entry(character.id).or_insert(Vec::new()).push(floor_item);
self.character_floor.get(&character.id).ok_or(ItemManagerError::Idunnoman)?.last().ok_or(ItemManagerError::Idunnoman)
}
pub async fn player_drop_item_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
inventory_item: InventoryItem,
item_drop_location: (MapArea, f32, f32, f32))
-> Result<(), ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let dropped_inventory_item = inventory
.drain_filter(|i| i.item_id() == inventory_item.item_id())
.nth(0)
.ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?;
let dropped_floor_item = match dropped_inventory_item {
InventoryItem::Individual(individual_inventory_item) => {
entity_gateway.save_item(&ItemEntity {
id: individual_inventory_item.entity_id,
item: individual_inventory_item.item.clone(),
location: ItemLocation::SharedFloor {
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}
}).await;
FloorItem::Individual(IndividualFloorItem {
entity_id: individual_inventory_item.entity_id,
item_id: individual_inventory_item.item_id,
item: individual_inventory_item.item,
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
})
},
InventoryItem::Stacked(stacked_inventory_item) => {
for entity_id in stacked_inventory_item.entity_ids.iter() {
entity_gateway.save_item(&ItemEntity {
id: *entity_id,
item: ItemDetail::Tool(stacked_inventory_item.tool),
location: ItemLocation::SharedFloor {
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}
}).await;
}
FloorItem::Stacked(StackedFloorItem {
entity_ids: stacked_inventory_item.entity_ids,
item_id: stacked_inventory_item.item_id,
tool: stacked_inventory_item.tool,
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
})
},
};
shared_floor.push(dropped_floor_item);
Ok(())
}
pub async fn player_drops_meseta_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
drop_location: ItemDropLocation,
amount: u32)
-> Result<FloorItem, ItemManagerError> {
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
if character.meseta <= amount {
return Err(ItemManagerError::CouldNotDropMeseta)
}
character.meseta -= amount;
entity_gateway.save_character(&character).await;
let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let floor_item = FloorItem::Meseta(MesetaFloorItem {
item_id: item_id,
meseta: Meseta(amount),
map_area: drop_location.map_area,
x: drop_location.x,
y: 0.0,
z: drop_location.z,
});
shared_floor.push(floor_item.clone());
Ok(floor_item)
}
pub async fn player_drops_partial_stack_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
inventory_item: InventoryItem,
drop_location: ItemDropLocation,
amount: usize)
-> Result<FloorItem, ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let item_to_split = inventory.iter_mut()
.find(|i| i.item_id() == inventory_item.item_id())
.ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?;
if let InventoryItem::Stacked(ref mut stacked_inventory_item) = item_to_split {
if stacked_inventory_item.entity_ids.len() <= amount {
return Err(ItemManagerError::CouldNotSplitItem(inventory_item));
}
let dropped_entity_ids = stacked_inventory_item.entity_ids.drain(..amount).collect::<Vec<_>>();
for de in dropped_entity_ids.iter() {
entity_gateway.save_item(&ItemEntity {
id: *de,
item: ItemDetail::Tool(stacked_inventory_item.tool),
location: ItemLocation::SharedFloor {
map_area: drop_location.map_area,
x: drop_location.x,
y: 0.0,
z: drop_location.z,
}
}).await
}
let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let floor_item = FloorItem::Stacked(StackedFloorItem {
entity_ids: dropped_entity_ids,
item_id: item_id,
tool: stacked_inventory_item.tool,
map_area: drop_location.map_area,
x: drop_location.x,
y: 0.0,
z: drop_location.z,
});
shared_floor.push(floor_item.clone());
Ok(floor_item)
}
else {
Err(ItemManagerError::CouldNotSplitItem(inventory_item))
}
}
pub async fn player_consumes_tool<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
inventory_item: InventoryItem,
amount: usize)
-> Result<ItemDetail, ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
match inventory_item.clone() {
InventoryItem::Individual(individual_inventory_item) => {
// TODO: use remove
let _used_inventory_item = inventory
.drain_filter(|i| i.item_id() == inventory_item.item_id())
.nth(0)
.ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?;
entity_gateway.save_item(&ItemEntity {
id: individual_inventory_item.entity_id,
item: individual_inventory_item.item.clone(),
location: ItemLocation::Consumed,
}).await;
Ok(individual_inventory_item.item)
},
InventoryItem::Stacked(stacked_inventory_item) => {
let used_entity_ids = match stacked_inventory_item.count().cmp(&amount) {
Ordering::Equal => {
let _used_inventory_item = inventory
.drain_filter(|i| i.item_id() == inventory_item.item_id())
.nth(0)
.ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?;
stacked_inventory_item.entity_ids
},
Ordering::Greater => {
let tool_used = inventory
.iter_mut()
.find(|i| i.item_id() == stacked_inventory_item.item_id)
.ok_or(ItemManagerError::NoSuchItemId(stacked_inventory_item.item_id))?;
if let InventoryItem::Stacked(ref mut stacked_tool_used) = tool_used {
stacked_tool_used.entity_ids.drain(..amount).collect::<Vec<_>>()
}
else {
unreachable!()
}
//stacked_inventory_item.entity_ids.iter_mut().drain(..amount).collect::<Vec<_>>()
}
Ordering::Less => {
//return Err(ItemManagerError::NotEnoughTools(tool, held_amount, amount))
return Err(ItemManagerError::Idunnoman)
},
};
for used_entity_id in used_entity_ids {
entity_gateway.save_item(&ItemEntity {
id: used_entity_id,
item: ItemDetail::Tool(stacked_inventory_item.tool),
location: ItemLocation::Consumed,
}).await;
}
Ok(ItemDetail::Tool(stacked_inventory_item.tool))
},
}
}
}

186
src/ship/items/bank.rs

@ -0,0 +1,186 @@
use crate::ship::items::ClientItemId;
use libpso::character::character;//::InventoryItem;
use crate::entity::item::{ItemEntityId, ItemDetail};
use crate::entity::item::tool::Tool;
#[derive(Debug, Clone)]
pub struct IndividualBankItem {
pub entity_id: ItemEntityId,
pub item_id: ClientItemId,
pub item: ItemDetail,
}
#[derive(Debug, Clone)]
pub struct StackedBankItem {
pub entity_ids: Vec<ItemEntityId>,
pub item_id: ClientItemId,
pub tool: Tool,
}
impl StackedBankItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
}
#[derive(Debug, Clone)]
pub enum BankItem {
Individual(IndividualBankItem),
Stacked(StackedBankItem),
}
impl std::cmp::PartialEq for BankItem {
fn eq(&self, other: &BankItem) -> bool {
let mut self_bytes = [0u8; 4];
let mut other_bytes = [0u8; 4];
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
let self_value = u32::from_be_bytes(self_bytes);
let other_value = u32::from_be_bytes(other_bytes);
self_value.eq(&other_value)
}
}
impl std::cmp::Eq for BankItem {}
impl std::cmp::PartialOrd for BankItem {
fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
//let self_bytes = self.as_client_bytes();
//let other_bytes = other.as_client_bytes();
let mut self_bytes = [0u8; 4];
let mut other_bytes = [0u8; 4];
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
let self_value = u32::from_be_bytes(self_bytes);
let other_value = u32::from_be_bytes(other_bytes);
self_value.partial_cmp(&other_value)
}
}
impl std::cmp::Ord for BankItem {
fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
//let self_bytes = self.as_client_bytes();
//let other_bytes = other.as_client_bytes();
let mut self_bytes = [0u8; 4];
let mut other_bytes = [0u8; 4];
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
let self_value = u32::from_le_bytes(self_bytes);
let other_value = u32::from_le_bytes(other_bytes);
self_value.cmp(&other_value)
}
}
impl BankItem {
pub fn set_item_id(&mut self, item_id: ClientItemId) {
match self {
BankItem::Individual(individual_bank_item) => {
individual_bank_item.item_id = item_id
},
BankItem::Stacked(stacked_bank_item) => {
stacked_bank_item.item_id = item_id
}
}
}
pub fn item_id(&self) -> ClientItemId {
match self {
BankItem::Individual(individual_bank_item) => {
individual_bank_item.item_id
},
BankItem::Stacked(stacked_bank_item) => {
stacked_bank_item.item_id
}
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
BankItem::Individual(item) => {
match &item.item {
ItemDetail::Weapon(w) => w.as_bytes(),
ItemDetail::Armor(a) => a.as_bytes(),
ItemDetail::Shield(s) => s.as_bytes(),
ItemDetail::Unit(u) => u.as_bytes(),
ItemDetail::Tool(t) => t.as_individual_bytes(),
ItemDetail::TechniqueDisk(d) => d.as_bytes(),
ItemDetail::Mag(m) => m.as_bytes(),
}
},
BankItem::Stacked(item) => {
item.tool.as_stacked_bytes(item.entity_ids.len())
},
}
}
}
pub struct CharacterBank(Vec<BankItem>);
impl CharacterBank {
pub fn new(mut items: Vec<BankItem>) -> CharacterBank {
items.sort();
CharacterBank(items)
}
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
for (i, item) in self.0.iter_mut().enumerate() {
item.set_item_id(ClientItemId(base_item_id + i as u32));
}
}
pub fn as_client_bank_items(&self) -> character::Bank {
self.0.iter()
.enumerate()
.fold(character::Bank::default(), |mut bank, (slot, item)| {
bank.item_count = (slot + 1) as u32;
let bytes = item.as_client_bytes();
bank.items[slot].data1.copy_from_slice(&bytes[0..12]);
bank.items[slot].data2.copy_from_slice(&bytes[12..16]);
bank.items[slot].item_id = item.item_id().0;
bank
})
}
pub fn as_client_bank_request(&self) -> Vec<character::BankItem> {
self.0.iter()
.map(|item| {
let bytes = item.as_client_bytes();
let mut data1 = [0; 12];
let mut data2 = [0; 4];
data1.copy_from_slice(&bytes[0..12]);
data2.copy_from_slice(&bytes[12..16]);
let amount = match item {
BankItem::Individual(_individual_bank_item) => {
1
},
BankItem::Stacked(stacked_bank_item) => {
stacked_bank_item.count()
},
};
character::BankItem {
data1: data1,
data2: data2,
item_id: item.item_id().0,
amount: amount as u16,
flags: 1,
}
})
.collect()
}
pub fn count(&self) -> usize {
self.0.len()
}
}

251
src/ship/items/floor.rs

@ -0,0 +1,251 @@
use crate::ship::items::ClientItemId;
use crate::entity::item::{ItemEntityId, ItemDetail};
use crate::entity::item::Meseta;
use crate::entity::item::tool::Tool;
use crate::ship::map::MapArea;
use crate::ship::items::inventory::{IndividualInventoryItem, StackedInventoryItem, InventoryItemHandle};
#[derive(Debug, Clone)]
pub struct IndividualFloorItem {
pub entity_id: ItemEntityId,
pub item_id: ClientItemId,
pub item: ItemDetail,
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Debug, Clone)]
pub struct StackedFloorItem {
pub entity_ids: Vec<ItemEntityId>,
pub item_id: ClientItemId,
pub tool: Tool,
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
}
impl StackedFloorItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
pub fn as_client_bytes(&self) -> [u8; 16] {
self.tool.as_stacked_bytes(self.count())
}
}
#[derive(Debug, Clone)]
pub struct MesetaFloorItem {
pub item_id: ClientItemId,
pub meseta: Meseta,
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Debug, Clone)]
pub enum FloorItem {
Individual(IndividualFloorItem),
Stacked(StackedFloorItem),
Meseta(MesetaFloorItem),
}
impl FloorItem {
pub fn item_id(&self) -> ClientItemId {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.item_id
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.item_id
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.item_id
}
}
}
pub fn x(&self) -> f32 {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.x
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.x
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.x
}
}
}
pub fn y(&self) -> f32 {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.y
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.y
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.y
}
}
}
pub fn z(&self) -> f32 {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.z
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.z
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.z
}
}
}
pub fn map_area(&self) -> MapArea {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.map_area
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.map_area
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.map_area
}
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
FloorItem::Individual(individual_floor_item) => {
individual_floor_item.item.as_client_bytes()
},
FloorItem::Stacked(stacked_floor_item) => {
stacked_floor_item.as_client_bytes()
},
FloorItem::Meseta(meseta_floor_item) => {
meseta_floor_item.meseta.as_bytes()
}
}
}
}
pub struct FloorItemHandle<'a> {
floor: &'a mut RoomFloorItems,
index: usize,
}
impl<'a> FloorItemHandle<'a> {
pub fn item(&'a self) -> Option<&'a FloorItem> {
self.floor.0.get(self.index)
}
pub fn remove_from_floor(self) {
self.floor.0.remove(self.index);
}
}
// TODO: floors should keep track of their own item_ids
#[derive(Debug)]
pub struct RoomFloorItems(Vec<FloorItem>);
impl RoomFloorItems {
pub fn new() -> RoomFloorItems {
RoomFloorItems(Vec::new())
}
pub fn add_item(&mut self, item: FloorItem) {
self.0.push(item);
}
pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&FloorItem> {
self.0.iter().find(|item| item.item_id() == item_id)
}
pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option<FloorItemHandle> {
let index = self.0.iter().position(|item| item.item_id() == item_id)?;
Some(FloorItemHandle {
floor: self,
index: index,
})
}
pub fn take_item_by_id(&mut self, item_id: ClientItemId) -> Option<FloorItem> {
self.0
.drain_filter(|i| i.item_id() == item_id)
.nth(0)
}
pub fn drop_individual_inventory_item(&mut self, individual_inventory_item: IndividualInventoryItem, item_drop_location: (MapArea, f32, f32, f32)) -> &IndividualFloorItem {
self.0.push(FloorItem::Individual(IndividualFloorItem {
entity_id: individual_inventory_item.entity_id,
item_id: individual_inventory_item.item_id,
item: individual_inventory_item.item,
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}));
match self.0.last().unwrap() {
FloorItem::Individual(item) => item,
_ => unreachable!(),
}
}
pub fn drop_stacked_inventory_item(&mut self, stacked_inventory_item: StackedInventoryItem, item_drop_location: (MapArea, f32, f32, f32)) -> &StackedFloorItem {
self.0.push(FloorItem::Stacked(StackedFloorItem {
entity_ids: stacked_inventory_item.entity_ids,
item_id: stacked_inventory_item.item_id,
tool: stacked_inventory_item.tool,
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}));
match self.0.last().unwrap() {
FloorItem::Stacked(item) => item,
_ => unreachable!(),
}
}
// TODO: Result
// TODO: if consumed_item is not a tool items do not get placed back into inventory (should I care?)
pub fn drop_partial_stacked_inventory_item(&mut self, inventory_item: InventoryItemHandle, amount: usize, new_item_id: ClientItemId, item_drop_location: (MapArea, f32, f32, f32)) -> Option<&StackedFloorItem> {
let consumed_item = inventory_item.consume(amount).ok()?;
if let ItemDetail::Tool(tool) = consumed_item.item {
self.0.push(FloorItem::Stacked(StackedFloorItem {
entity_ids: consumed_item.entity_ids,
item_id: new_item_id,
tool: tool,
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}))
}
else {
return None
}
match self.0.last().unwrap() {
FloorItem::Stacked(item) => Some(item),
_ => unreachable!(),
}
}
}

380
src/ship/items/inventory.rs

@ -0,0 +1,380 @@
use std::cmp::Ordering;
use thiserror::Error;
use libpso::character::character;//::InventoryItem;
use crate::entity::item::{ItemEntityId, ItemDetail};
use crate::entity::item::tool::Tool;
use crate::ship::items::ClientItemId;
use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem};
#[derive(Debug, Clone)]
pub struct InventorySlot(pub usize);
#[derive(Debug, Clone)]
pub struct IndividualInventoryItem {
pub entity_id: ItemEntityId,
pub item_id: ClientItemId,
pub item: ItemDetail,
pub equipped: bool,
}
#[derive(Debug, Clone)]
pub struct StackedInventoryItem {
pub entity_ids: Vec<ItemEntityId>,
pub item_id: ClientItemId,
pub tool: Tool,
}
impl StackedInventoryItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
}
#[derive(Debug, Clone)]
pub enum InventoryItem {
Individual(IndividualInventoryItem),
Stacked(StackedInventoryItem),
}
#[derive(Error, Debug, Clone)]
#[error("")]
pub enum InventoryItemAddToError {
BothAreNotStacked,
DifferentTool,
ExceedsCapacity,
}
impl InventoryItem {
pub fn item_id(&self) -> ClientItemId {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.item_id
},
InventoryItem::Stacked(stacked_inventory_item) => {
stacked_inventory_item.item_id
}
}
}
pub fn set_item_id(&mut self, item_id: ClientItemId) {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.item_id = item_id
},
InventoryItem::Stacked(stacked_inventory_item) => {
stacked_inventory_item.item_id = item_id
}
}
}
pub fn are_same_stackable_tool(&self, other_stacked_item: &StackedFloorItem) -> bool {
match self {
InventoryItem::Stacked(self_stacked_item) => {
self_stacked_item.tool == other_stacked_item.tool
&& self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable()
},
_ => false
}
}
pub fn can_combine_stacks(&self, other_stacked_item: &StackedFloorItem) -> bool {
match self {
InventoryItem::Stacked(self_stacked_item) => {
self_stacked_item.tool == other_stacked_item.tool
&& self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable()
&& self_stacked_item.count() + other_stacked_item.count() <= self_stacked_item.tool.max_stack()
},
_ => false
}
}
// TODO: result
pub fn combine_stacks(&mut self, other_stacked_item: &mut StackedFloorItem) {
match self {
InventoryItem::Stacked(self_stacked_item) => {
self_stacked_item.entity_ids.append(&mut other_stacked_item.entity_ids);
},
_ => {
}
}
}
pub fn equipped(&self) -> bool {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.equipped
},
InventoryItem::Stacked(_) => {
false
}
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
InventoryItem::Individual(item) => {
match &item.item {
ItemDetail::Weapon(w) => w.as_bytes(),
ItemDetail::Armor(a) => a.as_bytes(),
ItemDetail::Shield(s) => s.as_bytes(),
ItemDetail::Unit(u) => u.as_bytes(),
ItemDetail::Tool(t) => t.as_individual_bytes(),
ItemDetail::TechniqueDisk(d) => d.as_bytes(),
ItemDetail::Mag(m) => m.as_bytes(),
}
},
InventoryItem::Stacked(item) => {
item.tool.as_stacked_bytes(item.entity_ids.len())
},
}
}
pub fn can_add_to(&mut self, stacked_floor_item: &StackedFloorItem) -> Result<(), InventoryItemAddToError> {
if let InventoryItem::Stacked(stacked_inventory_item) = self {
if stacked_floor_item.tool != stacked_inventory_item.tool {
return Err(InventoryItemAddToError::DifferentTool)
}
if stacked_floor_item.tool.tool.max_stack() < (stacked_floor_item.count() + stacked_inventory_item.count()) {
return Err(InventoryItemAddToError::ExceedsCapacity)
}
Ok(())
}
else {
Err(InventoryItemAddToError::BothAreNotStacked)
}
}
pub fn add_to(&mut self, mut stacked_floor_item: StackedFloorItem) -> Result<(), InventoryItemAddToError> {
self.can_add_to(&stacked_floor_item)?;
if let InventoryItem::Stacked(stacked_inventory_item) = self {
stacked_inventory_item.entity_ids.append(&mut stacked_floor_item.entity_ids);
}
Ok(())
}
}
#[derive(Error, Debug, Clone)]
#[error("")]
pub enum InventoryItemConsumeError {
InconsistentState,
InvalidAmount,
}
pub struct ConsumedItem {
pub entity_ids: Vec<ItemEntityId>,
pub item: ItemDetail
}
pub struct InventoryItemHandle<'a> {
inventory: &'a mut CharacterInventory,
slot: usize,
}
impl<'a> InventoryItemHandle<'a> {
pub fn item(&'a self) -> Option<&'a InventoryItem> {
self.inventory.0.get(self.slot)
}
pub fn remove_from_inventory(self) {
self.inventory.0.remove(self.slot);
}
pub fn consume(self, amount: usize) -> Result<ConsumedItem, InventoryItemConsumeError> {
enum RemoveMethod {
EntireThing(ConsumedItem),
Partial(ItemDetail),
}
let inventory_item = self.inventory.0.get(self.slot).ok_or(InventoryItemConsumeError::InconsistentState)?;
let remove_method = match inventory_item {
InventoryItem::Individual(individual_inventory_item) => {
RemoveMethod::EntireThing(ConsumedItem {
entity_ids: vec![individual_inventory_item.entity_id],
item: individual_inventory_item.item.clone()
})
},
InventoryItem::Stacked(stacked_inventory_item) => {
match stacked_inventory_item.count().cmp(&amount) {
Ordering::Equal => {
RemoveMethod::EntireThing(ConsumedItem {
entity_ids: stacked_inventory_item.entity_ids.clone(),
item: ItemDetail::Tool(stacked_inventory_item.tool),
})
},
Ordering::Greater => {
RemoveMethod::Partial(ItemDetail::Tool(stacked_inventory_item.tool))
},
Ordering::Less => {
return Err(InventoryItemConsumeError::InvalidAmount)
}
}
},
};
match remove_method {
RemoveMethod::EntireThing(consumed_item) => {
self.inventory.0.remove(self.slot);
Ok(consumed_item)
},
RemoveMethod::Partial(item_detail) => {
let entity_ids = self.inventory.0.get_mut(self.slot)
.and_then(|item| {
if let InventoryItem::Stacked(stacked_inventory_item) = item {
Some(stacked_inventory_item.entity_ids.drain(..amount).collect::<Vec<_>>())
}
else {
None
}
})
.ok_or(InventoryItemConsumeError::InvalidAmount)?;
Ok(ConsumedItem {
entity_ids: entity_ids,
item: item_detail,
})
}
}
}
}
#[derive(Debug)]
pub struct CharacterInventory(Vec<InventoryItem>);
impl CharacterInventory {
pub fn new(items: Vec<InventoryItem>) -> CharacterInventory {
CharacterInventory(items)
}
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
for (i, item) in self.0.iter_mut().enumerate() {
item.set_item_id(ClientItemId(base_item_id + i as u32));
}
}
pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] {
self.0.iter()
.enumerate()
.fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| {
let bytes = item.as_client_bytes();
inventory[slot].data1.copy_from_slice(&bytes[0..12]);
inventory[slot].data2.copy_from_slice(&bytes[12..16]);
inventory[slot].item_id = item.item_id().0;
// does this do anything?
inventory[slot].equipped = if item.equipped() { 1 } else { 0 };
// because this actually equips the item
inventory[slot].flags |= if item.equipped() { 8 } else { 0 };
inventory
})
}
pub fn slot(&self, slot: usize) -> Option<&InventoryItem> {
self.0.get(slot)
}
pub fn count(&self) -> usize {
self.0.len()
}
pub fn get_item_handle_by_id<'a>(&'a mut self, item_id: ClientItemId) -> Option<InventoryItemHandle<'a>> {
let (slot, _) = self.0.iter()
.enumerate()
.filter(|(_, item)| {
item.item_id() == item_id
})
.nth(0)?;
Some(InventoryItemHandle {
inventory: self,
slot: slot,
})
}
pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&InventoryItem> {
self.0.iter()
.filter(|item| {
item.item_id() == item_id
})
.nth(0)
}
pub fn take_item_by_id(&mut self, item_id: ClientItemId) -> Option<InventoryItem> {
self.0
.drain_filter(|i| i.item_id() == item_id)
.nth(0)
}
pub fn add_item(&mut self, item: InventoryItem) -> Result<(), ()> { // TODO: errors
// TODO: check slot conflict?
self.0.push(item);
Ok(())
}
// TODO: should these pick up functions take floor_item as mut and remove the ids?
pub fn pick_up_individual_floor_item(&mut self, floor_item: &IndividualFloorItem) -> Option<(&IndividualInventoryItem, InventorySlot)> {
if self.count() >= 30 {
return None;
}
self.0.push(InventoryItem::Individual(IndividualInventoryItem {
entity_id: floor_item.entity_id,
item_id: floor_item.item_id,
item: floor_item.item.clone(),
equipped: false,
}));
if let Some(InventoryItem::Individual(new_item)) = self.0.last() {
Some((new_item, InventorySlot(self.count())))
}
else {
None
}
}
// TODO: can be simplified using find instead of position
pub fn pick_up_stacked_floor_item(&mut self, floor_item: &StackedFloorItem) -> Option<(&StackedInventoryItem, InventorySlot)> {
let existing_stack_position = self.0.iter()
.position(|inventory_item| {
if let InventoryItem::Stacked(stacked_inventory_item) = inventory_item {
if stacked_inventory_item.tool == floor_item.tool {
return true
}
}
false
});
if let Some(existing_stack_position) = existing_stack_position {
if let Some(InventoryItem::Stacked(stacked_item)) = self.0.get_mut(existing_stack_position) {
if stacked_item.count() + floor_item.count() <= stacked_item.tool.max_stack() {
stacked_item.entity_ids.append(&mut floor_item.entity_ids.clone());
Some((stacked_item, InventorySlot(existing_stack_position)))
}
else {
None
}
}
else {
None
}
}
else {
let new_stacked_item = InventoryItem::Stacked(StackedInventoryItem {
entity_ids: floor_item.entity_ids.clone(),
item_id: floor_item.item_id,
tool: floor_item.tool,
});
self.0.push(new_stacked_item);
if let Some(InventoryItem::Stacked(new_item)) = self.0.last() {
Some((new_item, InventorySlot(self.count())))
}
else {
None
}
}
}
}

529
src/ship/items/manager.rs

@ -0,0 +1,529 @@
use crate::ship::items::ClientItemId;
use std::collections::{HashMap, BTreeMap};
use thiserror::Error;
use crate::entity::gateway::EntityGateway;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::item::{ItemDetail, ItemLocation, BankName};
use crate::entity::item::{Meseta, NewItemEntity};
use crate::entity::item::tool::Tool;
use crate::ship::map::MapArea;
use crate::ship::ship::ItemDropLocation;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::location::{AreaClient, RoomId};
use crate::ship::items::bank::*;
use crate::ship::items::floor::*;
use crate::ship::items::inventory::*;
pub enum TriggerCreateItem {
Yes,
No
}
#[derive(Error, Debug)]
#[error("")]
pub enum ItemManagerError {
EntityGatewayError,
NoSuchItemId(ClientItemId),
NoCharacter(CharacterEntityId),
CouldNotAddToInventory(ClientItemId),
//ItemBelongsToOtherPlayer,
Idunnoman,
CouldNotSplitItem(ClientItemId),
CouldNotDropMeseta,
NotEnoughTools(Tool, usize, usize), // have, expected
InventoryItemConsumeError(#[from] InventoryItemConsumeError),
}
pub struct ItemManager {
id_counter: u32,
character_inventory: HashMap<CharacterEntityId, CharacterInventory>,
character_bank: HashMap<CharacterEntityId, BTreeMap<BankName, CharacterBank>>,
character_floor: HashMap<CharacterEntityId, RoomFloorItems>,
character_room: HashMap<CharacterEntityId, RoomId>,
room_floor: HashMap<RoomId, RoomFloorItems>,
room_item_id_counter: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>,
}
impl ItemManager {
pub fn new() -> ItemManager {
ItemManager {
id_counter: 0,
character_inventory: HashMap::new(),
character_bank: HashMap::new(),
character_floor: HashMap::new(),
character_room: HashMap::new(),
room_floor: HashMap::new(),
room_item_id_counter: HashMap::new(),
}
}
pub fn next_global_item_id(&mut self) -> ClientItemId {
self.id_counter += 1;
ClientItemId(self.id_counter)
}
// TODO: Result
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) {
let items = entity_gateway.get_items_by_character(&character).await;
let inventory_items = items.clone().into_iter()
.filter_map(|item| {
match item.location {
ItemLocation::Inventory{slot, equipped, ..} => Some((item.id, item.item, slot, equipped)),
_ => None,
}
})
.fold(BTreeMap::new(), |mut acc, (id, item, slot, equipped)| {
if item.is_stackable() {
if let ItemDetail::Tool(tool) = item {
let inventory_item = acc.entry(slot).or_insert(InventoryItem::Stacked(StackedInventoryItem {
entity_ids: Vec::new(),
item_id: self.next_global_item_id(),
tool: tool,
}));
if let InventoryItem::Stacked(ref mut stacked_inventory_item) = inventory_item {
stacked_inventory_item.entity_ids.push(id);
}
}
}
else {
acc.insert(slot, InventoryItem::Individual(IndividualInventoryItem {
entity_id: id,
item_id: self.next_global_item_id(),
item: item,
equipped: equipped,
}));
}
acc
});
let bank_items = items.into_iter()
.filter_map(|item| {
match item.location {
ItemLocation::Bank{name, ..} => Some((item.id, item.item, name)),
_ => None,
}
})
.fold(BTreeMap::new(), |mut acc, (id, item, name)| {
acc.entry(name).or_insert(Vec::new()).push((id, item));
acc
})
.into_iter()
.map(|(bank_name, bank_items)| {
let stacked_bank_items = bank_items.into_iter()
.fold(Vec::new(), |mut acc, (id, bank_item)| {
if bank_item.is_stackable() {
let existing_item = acc.iter_mut()
.find(|item| {
if let (BankItem::Stacked(stacked_bank_item), &ItemDetail::Tool(ref tool)) = (item, &bank_item) {
stacked_bank_item.tool == *tool
}
else {
false
}
});
match existing_item {
Some(item) => {
if let BankItem::Stacked(ref mut stacked_bank_item) = item {
stacked_bank_item.entity_ids.push(id);
}
}
None => {
if let ItemDetail::Tool(tool) = bank_item {
acc.push(BankItem::Stacked(StackedBankItem {
entity_ids: vec![id],
item_id: self.next_global_item_id(),
tool: tool,
}));
}
},
}
}
else {
acc.push(BankItem::Individual(IndividualBankItem {
entity_id: id,
item_id: self.next_global_item_id(),
item: bank_item,
}));
}
acc
});
(bank_name, CharacterBank::new(stacked_bank_items))
})
.collect::<BTreeMap<_, _>>();
let inventory = CharacterInventory::new(inventory_items.into_iter().map(|(_k, v)| v).take(30).collect());
self.character_inventory.insert(character.id, inventory);
self.character_bank.insert(character.id, bank_items);
}
pub 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 inventory = self.character_inventory.get_mut(&character.id).unwrap();
inventory.initialize_item_ids(base_inventory_id);
let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000;
let default_bank = self.character_bank.get_mut(&character.id).unwrap().get_mut(&BankName("".to_string()));
match default_bank {
Some(default_bank) => {
default_bank.initialize_item_ids(base_bank_id);
},
None => {},
}
self.character_room.insert(character.id, room_id);
self.character_floor.insert(character.id, RoomFloorItems::new());
self.room_floor.entry(room_id).or_insert(RoomFloorItems::new());
let mut inc = 0xF0000000;
self.room_item_id_counter.entry(room_id).or_insert(Box::new(move || {
inc += 1;
ClientItemId(inc)
}));
}
pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<&CharacterInventory, ItemManagerError> {
Ok(self.character_inventory.get(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?)
}
pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&CharacterBank, ItemManagerError> {
Ok(self.character_bank
.get(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?
.get(&BankName("".to_string()))
.unwrap()) // TODO: make an error
}
pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
self.character_inventory.remove(&character.id);
self.character_floor.remove(&character.id);
self.character_room.remove(&character.id)
.as_ref()
.map(|room| {
if self.character_room.iter().find(|(_, r)| *r == room).is_none() {
self.room_floor.remove(room);
}
});
}
pub fn get_floor_item_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result<&FloorItem, ItemManagerError> {
let local_floor = self.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get(room).ok_or(ItemManagerError::NoCharacter(character.id))?;
local_floor.get_item_by_id(item_id)
.or_else(|| {
shared_floor.get_item_by_id(item_id)
})
.ok_or(ItemManagerError::NoSuchItemId(item_id))
}
pub async fn character_picks_up_item<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId)
-> Result<TriggerCreateItem, ItemManagerError> {
let local_floor = self.character_floor.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let floor_item = local_floor.get_item_handle_by_id(item_id)
.or_else(|| {
shared_floor.get_item_handle_by_id(item_id)
})
.ok_or(ItemManagerError::NoSuchItemId(item_id))?;
let trigger_create_item = match floor_item.item() {
Some(FloorItem::Individual(individual_floor_item)) => {
let new_inventory_item = inventory.pick_up_individual_floor_item(&individual_floor_item);
match new_inventory_item {
Some((new_inventory_item, slot)) => {
entity_gateway.change_item_location(
&new_inventory_item.entity_id,
ItemLocation::Inventory {
character_id: character.id,
slot: slot.0,
equipped: false,
}
).await;
},
None => {
return Err(ItemManagerError::CouldNotAddToInventory(item_id));
},
}
TriggerCreateItem::Yes
},
Some(FloorItem::Stacked(stacked_floor_item)) => {
let new_inventory_item = inventory.pick_up_stacked_floor_item(&stacked_floor_item);
println!("new inv item! {:?}", new_inventory_item);
match new_inventory_item {
Some((new_inventory_item, slot)) => {
for entity_id in &new_inventory_item.entity_ids {
entity_gateway.change_item_location(
&entity_id,
ItemLocation::Inventory {
character_id: character.id,
slot: slot.0,
equipped: false,
}
).await;
}
if stacked_floor_item.count() != new_inventory_item.count() {
TriggerCreateItem::No
}
else {
TriggerCreateItem::Yes
}
},
None => {
return Err(ItemManagerError::CouldNotAddToInventory(item_id));
}
}
},
Some(FloorItem::Meseta(meseta_floor_item)) => {
if character.meseta >= 999999 {
return Err(ItemManagerError::CouldNotAddToInventory(item_id));
}
character.meseta = std::cmp::min(character.meseta + meseta_floor_item.meseta.0, 999999);
entity_gateway.save_character(&character).await;
TriggerCreateItem::No
},
None => {
return Err(ItemManagerError::CouldNotAddToInventory(item_id));
}
};
floor_item.remove_from_floor();
Ok(trigger_create_item)
}
pub async fn enemy_drop_item_on_local_floor<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&FloorItem, ItemManagerError> {
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
enum ItemOrMeseta {
Individual(ItemDetail),
Stacked(Tool),
Meseta(Meseta)
}
let item = match item_drop.item {
ItemDropType::Weapon(w) => ItemOrMeseta::Individual(ItemDetail::Weapon(w)),
ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)),
ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)),
ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)),
ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)),
ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)),
//ItemDropType::IndividualTool(t) => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
//ItemDropType::StackedTool(t, _) => ItemOrMeseta::Stacked(t),
ItemDropType::Tool(t) if t.tool.is_stackable() => ItemOrMeseta::Stacked(t),
ItemDropType::Tool(t) if !t.tool.is_stackable() => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)),
_ => unreachable!() // rust isnt smart enough to see that the conditional on tool catches everything
};
let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let floor_item = match item {
ItemOrMeseta::Individual(item_detail) => {
let entity = entity_gateway.create_item(NewItemEntity {
item: item_detail.clone(),
location: ItemLocation::LocalFloor {
character_id: character.id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}
}).await.ok_or(ItemManagerError::EntityGatewayError)?;
FloorItem::Individual(IndividualFloorItem {
entity_id: entity.id,
item_id: item_id,
item: item_detail,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
})
},
ItemOrMeseta::Stacked(tool) => {
let entity = entity_gateway.create_item(NewItemEntity {
item: ItemDetail::Tool(tool),
location: ItemLocation::LocalFloor {
character_id: character.id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}
}).await.ok_or(ItemManagerError::EntityGatewayError)?;
FloorItem::Stacked(StackedFloorItem {
entity_ids: vec![entity.id],
item_id: item_id,
tool: tool,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
})
},
ItemOrMeseta::Meseta(meseta) => {
FloorItem::Meseta(MesetaFloorItem {
item_id: item_id,
meseta: meseta,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
})
},
};
self.character_floor.entry(character.id).or_insert(RoomFloorItems::new()).add_item(floor_item);
// TODO: make these real errors
self.character_floor.get(&character.id).ok_or(ItemManagerError::Idunnoman)?.get_item_by_id(item_id).ok_or(ItemManagerError::Idunnoman)
}
pub async fn player_drop_item_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
//inventory_item: InventoryItem,
item_id: ClientItemId,
item_drop_location: (MapArea, f32, f32, f32))
-> Result<(), ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let dropped_inventory_item = inventory.take_item_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
match dropped_inventory_item {
InventoryItem::Individual(individual_inventory_item) => {
let individual_floor_item = shared_floor.drop_individual_inventory_item(individual_inventory_item, item_drop_location);
entity_gateway.change_item_location(
&individual_floor_item.entity_id,
ItemLocation::SharedFloor {
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}
).await;
},
InventoryItem::Stacked(stacked_inventory_item) => {
let stacked_floor_item = shared_floor.drop_stacked_inventory_item(stacked_inventory_item, item_drop_location);
for entity_id in &stacked_floor_item.entity_ids {
entity_gateway.change_item_location(
entity_id,
ItemLocation::SharedFloor {
map_area: item_drop_location.0,
x: item_drop_location.1,
y: item_drop_location.2,
z: item_drop_location.3,
}
).await;
}
},
}
Ok(())
}
pub async fn player_drops_meseta_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
drop_location: ItemDropLocation,
amount: u32)
-> Result<FloorItem, ItemManagerError> {
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
if character.meseta <= amount {
return Err(ItemManagerError::CouldNotDropMeseta)
}
character.meseta -= amount;
entity_gateway.save_character(&character).await;
let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let floor_item = FloorItem::Meseta(MesetaFloorItem {
item_id: item_id,
meseta: Meseta(amount),
map_area: drop_location.map_area,
x: drop_location.x,
y: 0.0,
z: drop_location.z,
});
shared_floor.add_item(floor_item.clone());
Ok(floor_item)
}
pub async fn player_drops_partial_stack_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
//inventory_item: InventoryItem,
item_id: ClientItemId,
drop_location: ItemDropLocation,
amount: usize)
-> Result<&StackedFloorItem, ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let item_to_split = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
let new_item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let stacked_floor_item = shared_floor.drop_partial_stacked_inventory_item(item_to_split, amount, new_item_id, (drop_location.map_area, drop_location.x, 0.0, drop_location.z))
.ok_or(ItemManagerError::CouldNotSplitItem(item_id))?;
for entity_id in &stacked_floor_item.entity_ids {
entity_gateway.change_item_location(
entity_id,
ItemLocation::SharedFloor {
map_area: drop_location.map_area,
x: drop_location.x,
y: 0.0,
z: drop_location.z,
}
).await;
}
Ok(stacked_floor_item)
}
pub async fn player_consumes_tool<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: ClientItemId,
amount: usize)
-> Result<ItemDetail, ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let used_item = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
let consumed_item = used_item.consume(amount)?;
for entity_id in consumed_item.entity_ids {
entity_gateway.change_item_location(&entity_id,
ItemLocation::Consumed).await;
}
Ok(consumed_item.item)
}
pub async fn player_deposits_item<EG: EntityGateway>(&mut self,
_entity_gateway: &mut EG,
_character: &CharacterEntity,
_item_id: ClientItemId,
_amount: usize)
-> Result<(), ItemManagerError> {
Ok(())
}
pub async fn player_withdraws_item<EG: EntityGateway>(&mut self,
_entity_gateway: &mut EG,
_character: &CharacterEntity,
_item_id: ClientItemId,
_amount: usize)
-> Result<(), ItemManagerError> {
Ok(())
}
}

15
src/ship/items/mod.rs

@ -0,0 +1,15 @@
mod bank;
mod floor;
mod inventory;
mod manager;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ClientItemId(pub u32);
// TODO: remove these and fix use statements in the rest of the codebase
pub use inventory::*;
pub use floor::*;
pub use bank::*;
pub use manager::*;

36
src/ship/packet/builder/message.rs

@ -1,7 +1,8 @@
use libpso::packet::messages::*;
use libpso::packet::ship::*;
use crate::common::leveltable::CharacterStats;
use crate::ship::ship::{ShipError};
use crate::ship::items::{FloorItem};
use crate::ship::items::{StackedFloorItem, FloorItem, CharacterBank};
use crate::ship::location::AreaClient;
use std::convert::TryInto;
@ -48,7 +49,24 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu
})
}
pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
pub fn drop_split_stack(area_client: AreaClient, item: &StackedFloorItem) -> Result<DropSplitStack, ShipError> {
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,
item_bytes: item_bytes[0..12].try_into()?,
item_id: item.item_id.0,
item_bytes2: item_bytes[12..16].try_into()?,
unknown2: 0,
})
}
pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
let item_bytes = item.as_client_bytes();
Ok(DropSplitStack {
client: area_client.local_client.id(),
@ -86,3 +104,17 @@ pub fn character_leveled_up(area_client: AreaClient, level: u32, before_stats: C
lvl: level,
}
}
// TOOD: meseta
pub fn bank_item_list(bank: &CharacterBank) -> BankItemList {
BankItemList {
aflag: 0,
cmd: 0xBC,
unknown: [0; 3],
size: bank.count() as u32 * 0x18 + 0x14,
checksum: 0x123434,
item_count: bank.count() as u32,
meseta: 12345,
items: bank.as_client_bank_request()
}
}

62
src/ship/packet/handler/direct_message.rs

@ -10,6 +10,10 @@ use crate::entity::gateway::EntityGateway;
use libpso::utf8_to_utf16_array;
use crate::ship::packet::builder;
const BANK_ACTION_DEPOSIT: u8 = 0;
const BANK_ACTION_WITHDRAW: u8 = 1;
//const BANK_ACTION_: u8 = 1;
fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation)
-> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
@ -148,6 +152,8 @@ where
let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
// TODO: should not need to fetch the item here to construct this packet
let item = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?;
let remove_item = builder::message::remove_item_from_floor(area_client, &item)?;
let create_item = match item {
@ -155,7 +161,7 @@ where
_ => Some(builder::message::create_item(area_client, &item)?),
};
match item_manager.character_picks_up_item(entity_gateway, &mut client.character, item).await {
match item_manager.character_picks_up_item(entity_gateway, &mut client.character, ClientItemId(pickup_item.item_id)).await {
Ok(trigger_create_item) => {
Ok(Box::new(Vec::new().into_iter()
.chain(clients_in_area.clone().into_iter()
@ -263,3 +269,57 @@ EG: EntityGateway
Ok(Box::new(item_drop_packets.into_iter()))
}
// item_manager is not mutable in this, but for reasons I don't quite understand it requires the unique access of it to compile here
pub async fn send_bank_list(id: ClientId,
clients: &Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
{
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
let bank_items = item_manager.get_character_bank(&client.character)?;
let bank_items_pkt = builder::message::bank_item_list(&bank_items);
Ok(Box::new(vec![(id, SendShipPacket::BankItemList(bank_items_pkt))].into_iter()))
}
pub async fn bank_interaction<EG>(id: ClientId,
bank_interaction: &BankInteraction,
entity_gateway: &mut EG,
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
where
EG: EntityGateway
{
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
match bank_interaction.action {
BANK_ACTION_DEPOSIT => {
if bank_interaction.item_id == 0xFFFFFFFF {
if client.character.meseta < bank_interaction.meseta_amount && client.character.bank_meseta <= 999999 {
client.character.meseta += bank_interaction.meseta_amount;
entity_gateway.save_character(&client.character).await;
}
}
else {
//let inventory_item = item_manager.get_inventory_item_by_id(&client.character, ClientItemId(bank_interaction.item_id))?;
item_manager.player_deposits_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?;
}
},
BANK_ACTION_WITHDRAW => {
if bank_interaction.item_id == 0xFFFFFFFF {
if client.character.meseta + bank_interaction.meseta_amount <= 999999 {
client.character.meseta += bank_interaction.meseta_amount;
entity_gateway.save_character(&client.character).await;
}
}
else {
//let bank_item = item_manager.get_bank_item_by_id(&client.character, ClientItemId(bank_interaction.item_id))?;
item_manager.player_withdraws_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?;
}
},
_ => {}
}
Ok(Box::new(None.into_iter()))
}

2
src/ship/packet/handler/lobby.rs

@ -22,12 +22,14 @@ pub fn block_selected(id: ClientId,
let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp);
let inventory = item_manager.get_character_inventory(&client.character).unwrap();
let bank = item_manager.get_character_bank(&client.character).unwrap();
let fc = FullCharacterBytesBuilder::new()
.character(&client.character)
.stats(&stats)
.level(level)
.inventory(&inventory)
.bank(&bank)
.key_config(&client.settings.settings.key_config)
.joystick_config(&client.settings.settings.joystick_config)
.symbol_chat(&client.settings.settings.symbol_chats)

17
src/ship/packet/handler/message.rs

@ -8,7 +8,7 @@ use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation};
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::map::{MapArea};
use crate::ship::items::{ItemManager, ClientItemId, InventoryItem};
use crate::ship::items::{ItemManager, ClientItemId};
use crate::ship::packet::builder;
pub async fn request_exp<EG: EntityGateway>(id: ClientId,
@ -83,8 +83,7 @@ where
.as_mut()
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
let area = MapArea::from_value(&room.mode.episode(), player_drop_item.area as u32)?;
let item = item_manager.get_inventory_item_by_id(&client.character, ClientItemId(player_drop_item.item_id))?;
item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, item, (area, player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?;
item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, ClientItemId(player_drop_item.item_id), (area, player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?;
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
let pdi = player_drop_item.clone();
Ok(Box::new(clients_in_area.into_iter()
@ -139,7 +138,7 @@ where
if split_item_stack.item_id == 0xFFFFFFFF {
let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, split_item_stack.amount as u32).await?;
let dropped_meseta_pkt = builder::message::drop_split_stack(area_client, &dropped_meseta)?;
let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?;
client.item_drop_location = None;
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
@ -149,10 +148,9 @@ where
})))
}
else {
let item_to_split = item_manager.get_inventory_item_by_id(&client.character, drop_location.item_id)?;
let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, item_to_split, drop_location, split_item_stack.amount as usize).await?;
let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, split_item_stack.amount as usize).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() })?;
@ -216,7 +214,7 @@ where
pub async fn use_item<EG>(id: ClientId,
player_use_tool: &PlayerUseItem,
entity_gateway: &mut EG,
client_location: &ClientLocation,
_client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
@ -225,8 +223,7 @@ where
{
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
let inventory_item_used = item_manager.get_inventory_item_by_id(&client.character, ClientItemId(player_use_tool.item_id))?;
let item_used_type = item_manager.player_consumes_tool(entity_gateway, &client.character, inventory_item_used, 1).await?;
let item_used_type = item_manager.player_consumes_tool(entity_gateway, &client.character, ClientItemId(player_use_tool.item_id), 1).await?;
match item_used_type {
ItemDetail::Weapon(_w) => {

10
src/ship/ship.rs

@ -158,6 +158,7 @@ pub enum SendShipPacket {
QuestHeader(QuestHeader),
QuestChunk(QuestChunk),
DoneLoadingQuest(DoneLoadingQuest),
BankItemList(BankItemList),
}
impl SendServerPacket for SendShipPacket {
@ -192,6 +193,7 @@ impl SendServerPacket for SendShipPacket {
SendShipPacket::QuestHeader(pkt) => pkt.as_bytes(),
SendShipPacket::QuestChunk(pkt) => pkt.as_bytes(),
SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
}
}
}
@ -320,7 +322,13 @@ impl<EG: EntityGateway> ShipServerState<EG> {
},
GameMessage::BoxDropRequest(box_drop_request) => {
handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_manager).await
}
},
GameMessage::BankRequest(_bank_request) => {
handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_manager).await
},
GameMessage::BankInteraction(bank_interaction) => {
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await
},
_ => {
let cmsg = msg.clone();
Ok(Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()

2
tests/common.rs

@ -1,4 +1,4 @@
#[allow(dead_code)]
#![allow(dead_code)]
use std::time::SystemTime;
use elseware::common::serverstate::{ClientId, ServerState};

219
tests/test_bank.rs

@ -0,0 +1,219 @@
use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket};
use libpso::packet::ship::*;
use libpso::packet::messages::*;
#[path = "common.rs"]
mod common;
use common::*;
#[async_std::test]
async fn test_bank_items_sent_in_character_login() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Vulcan,
grind: 0,
special: None,
attrs: [None, None, None],
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await;
let mut ship = ShipServerState::new(entity_gateway.clone());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
let packets = ship.handle(ClientId(1), &RecvShipPacket::MenuSelect(MenuSelect {
menu: BLOCK_MENU_ID,
item: 1,
})).await.unwrap().collect::<Vec<_>>();
assert!(matches!(&packets[0], (_, SendShipPacket::FullCharacter(fc)) if fc.character.bank.items[0].data1[0..3] == [0x00, 0x08, 0x04] ));
}
#[async_std::test]
async fn test_request_bank_items() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
for _ in 0..3 {
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Vulcan,
grind: 0,
special: None,
attrs: [None, None, None],
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await;
}
let mut ship = ShipServerState::new(entity_gateway.clone());
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;
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankRequest(BankRequest {
client: 0,
target: 0,
unknown: 0,
})))).await.unwrap().collect::<Vec<_>>();
assert!(matches!(&packets[0], (_, SendShipPacket::BankItemList (bank_item_list))
if bank_item_list.item_count == 3
&& bank_item_list.size == 0x18 * 3 + 0x14
&& bank_item_list.items[0].data1[0..3] == [0x00, 0x08, 0x04]
&& bank_item_list.items[1].data1[0..3] == [0x00, 0x08, 0x04]
&& bank_item_list.items[2].data1[0..3] == [0x00, 0x08, 0x04]
));
}
#[async_std::test]
async fn test_request_stacked_bank_items() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
for _ in 0..3 {
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Tool (
item::tool::Tool {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await;
}
let mut ship = ShipServerState::new(entity_gateway.clone());
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;
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankRequest(BankRequest {
client: 0,
target: 0,
unknown: 0,
})))).await.unwrap().collect::<Vec<_>>();
assert!(matches!(&packets[0], (_, SendShipPacket::BankItemList (bank_item_list))
if bank_item_list.item_count == 1
&& bank_item_list.size == 0x18 + 0x14
&& bank_item_list.items[0].data1[0..3] == [0x03, 0x00, 0x00]
&& bank_item_list.items[0].amount == 3
));
}
#[async_std::test]
async fn test_request_bank_items_sorted() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Vulcan,
grind: 0,
special: None,
attrs: [None, None, None],
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await;
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Tool (
item::tool::Tool {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await;
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Calibur,
grind: 0,
special: None,
attrs: [None, None, None],
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await;
let mut ship = ShipServerState::new(entity_gateway.clone());
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;
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankRequest(BankRequest {
client: 0,
target: 0,
unknown: 0,
})))).await.unwrap().collect::<Vec<_>>();
println!("{:?}", packets);
assert!(matches!(&packets[0], (_, SendShipPacket::BankItemList (bank_item_list))
if bank_item_list.item_count == 3
&& bank_item_list.size == 0x18 * 3 + 0x14
&& bank_item_list.items[0].data1[0..3] == [0x00, 0x02, 0x04]
&& bank_item_list.items[1].data1[0..3] == [0x00, 0x08, 0x04]
&& bank_item_list.items[2].data1[0..3] == [0x03, 0x00, 0x00]
));
}
//test_deposit_individual_item
//test_deposit_stacked_item
//test_deposit_stacked_item_with_stack_already_in_bank
//test_deposit_stacked_item_when_full_stack_in_bank
//test_deposit_individual_item_in_full_bank
//test_deposit_stacked_item_in_full_bank
//test_deposit_meseta
//test_deposit_too_much_meseta
//test_deposit_when_bank_has_max_meseta

1
tests/test_item_pickup.rs

@ -2,7 +2,6 @@ use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::ship::items::{ClientItemId};
use libpso::packet::ship::*;
use libpso::packet::messages::*;

Loading…
Cancel
Save