Browse Source

Merge pull request 'bankstuff' (#193) from bankstuff into master

pbs
jake 4 years ago
parent
commit
744bf774ad
  1. 122
      src/ship/items/bank.rs
  2. 4
      src/ship/items/floor.rs
  3. 209
      src/ship/items/inventory.rs
  4. 109
      src/ship/items/manager.rs
  5. 23
      src/ship/packet/builder/message.rs
  6. 112
      src/ship/packet/handler/direct_message.rs
  7. 12
      src/ship/packet/handler/message.rs
  8. 6
      src/ship/ship.rs
  9. 1569
      tests/test_bank.rs
  10. 11
      tests/test_item_pickup.rs

122
src/ship/items/bank.rs

@ -2,6 +2,9 @@ use crate::ship::items::ClientItemId;
use libpso::character::character;//::InventoryItem;
use crate::entity::item::{ItemEntityId, ItemDetail};
use crate::entity::item::tool::Tool;
use crate::ship::items::inventory::{InventoryItemHandle, InventoryItem};
const BANK_CAPACITY: usize = 200;
#[derive(Debug, Clone)]
pub struct IndividualBankItem {
@ -21,6 +24,15 @@ impl StackedBankItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
pub fn take_entity_ids(&mut self, amount: usize) -> Option<Vec<ItemEntityId>> {
if amount <= self.count() {
Some(self.entity_ids.drain(..amount).collect())
}
else {
None
}
}
}
#[derive(Debug, Clone)]
@ -123,22 +135,62 @@ impl BankItem {
}
}
pub struct CharacterBank(Vec<BankItem>);
pub struct BankItemHandle<'a> {
bank: &'a mut CharacterBank,
index: usize
}
impl<'a> BankItemHandle<'a> {
pub fn item(&'a self) -> Option<&'a BankItem> {
self.bank.items.get(self.index)
}
pub fn item_mut(&mut self) -> Option<&mut BankItem> {
self.bank.items.get_mut(self.index)
}
pub fn remove_from_bank(self) {
self.bank.items.remove(self.index);
}
}
pub struct CharacterBank {
item_id_counter: u32,
items: Vec<BankItem>
}
impl CharacterBank {
pub fn new(mut items: Vec<BankItem>) -> CharacterBank {
items.sort();
CharacterBank(items)
CharacterBank {
item_id_counter: 0,
items: items,
}
}
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
for (i, item) in self.0.iter_mut().enumerate() {
for (i, item) in self.items.iter_mut().enumerate() {
item.set_item_id(ClientItemId(base_item_id + i as u32));
}
self.item_id_counter = base_item_id + self.items.len() as u32 + 1;
}
pub fn get_item_handle_by_id<'a>(&'a mut self, item_id: ClientItemId) -> Option<BankItemHandle<'a>> {
let (index, _) = self.items.iter()
.enumerate()
.filter(|(_, item)| {
item.item_id() == item_id
})
.nth(0)?;
Some(BankItemHandle {
bank: self,
index: index,
})
}
pub fn as_client_bank_items(&self) -> character::Bank {
self.0.iter()
self.items.iter()
.enumerate()
.fold(character::Bank::default(), |mut bank, (slot, item)| {
bank.item_count = (slot + 1) as u32;
@ -152,7 +204,7 @@ impl CharacterBank {
}
pub fn as_client_bank_request(&self) -> Vec<character::BankItem> {
self.0.iter()
self.items.iter()
.map(|item| {
let bytes = item.as_client_bytes();
let mut data1 = [0; 12];
@ -179,7 +231,65 @@ impl CharacterBank {
}
pub fn count(&self) -> usize {
self.0.len()
self.items.len()
}
pub fn deposit_item(&mut self, mut inventory_item: InventoryItemHandle, amount: usize) -> Option<&BankItem> {
let remove = match inventory_item.item_mut()? {
InventoryItem::Individual(individual_inventory_item) => {
if self.items.len() >= BANK_CAPACITY {
return None
}
self.items.push(BankItem::Individual(IndividualBankItem {
entity_id: individual_inventory_item.entity_id,
item_id: individual_inventory_item.item_id,
item: individual_inventory_item.item.clone(),
}));
true
},
InventoryItem::Stacked(stacked_inventory_item) => {
let existing_bank_item = self.items.iter_mut()
.find_map(|item| {
if let BankItem::Stacked(stacked_bank_item) = item {
if stacked_bank_item.tool == stacked_inventory_item.tool {
return Some(stacked_bank_item)
}
}
None
});
match existing_bank_item {
Some(stacked_bank_item) => {
if stacked_bank_item.count() + stacked_inventory_item.count() > stacked_inventory_item.tool.max_stack() {
return None
}
let mut deposited_entity_ids = stacked_inventory_item.take_entity_ids(amount)?;
stacked_bank_item.entity_ids.append(&mut deposited_entity_ids);
}
None => {
if self.items.len() >= BANK_CAPACITY {
return None
}
let deposited_entity_ids = stacked_inventory_item.take_entity_ids(amount)?;
self.item_id_counter += 1;
self.items.push(BankItem::Stacked(StackedBankItem {
entity_ids: deposited_entity_ids,
item_id: ClientItemId(self.item_id_counter),
tool: stacked_inventory_item.tool,
}))
}
}
stacked_inventory_item.count() == 0
}
};
if remove {
inventory_item.remove_from_inventory();
}
self.items.last()
}
}

4
src/ship/items/floor.rs

@ -228,9 +228,9 @@ impl RoomFloorItems {
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 {
if let ItemDetail::Tool(tool) = consumed_item.item() {
self.0.push(FloorItem::Stacked(StackedFloorItem {
entity_ids: consumed_item.entity_ids,
entity_ids: consumed_item.entity_ids(),
item_id: new_item_id,
tool: tool,
map_area: item_drop_location.0,

209
src/ship/items/inventory.rs

@ -1,11 +1,13 @@
use std::cmp::Ordering;
use thiserror::Error;
use libpso::character::character;//::InventoryItem;
use crate::entity::item::{ItemEntityId, ItemDetail};
use crate::entity::item::{ItemEntityId, ItemDetail, ItemType};
use crate::entity::item::tool::Tool;
use crate::ship::items::ClientItemId;
use crate::ship::items::{ClientItemId, BankItem, BankItemHandle};
use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem};
const INVENTORY_CAPACITY: usize = 30;
#[derive(Debug, Clone)]
pub struct InventorySlot(pub usize);
@ -29,6 +31,15 @@ impl StackedInventoryItem {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
pub fn take_entity_ids(&mut self, amount: usize) -> Option<Vec<ItemEntityId>> {
if amount <= self.count() {
Some(self.entity_ids.drain(..amount).collect())
}
else {
None
}
}
}
#[derive(Debug, Clone)]
@ -68,6 +79,18 @@ impl InventoryItem {
}
}
pub fn item_type(&self) -> ItemType {
match self {
InventoryItem::Individual(individual_inventory_item) => {
individual_inventory_item.item.item_type()
},
InventoryItem::Stacked(stacked_inventory_item) => {
ItemType::Tool(stacked_inventory_item.tool.tool)
}
}
}
// TOOD: delete?
pub fn are_same_stackable_tool(&self, other_stacked_item: &StackedFloorItem) -> bool {
match self {
InventoryItem::Stacked(self_stacked_item) => {
@ -78,6 +101,7 @@ impl InventoryItem {
}
}
// TOOD: delete?
pub fn can_combine_stacks(&self, other_stacked_item: &StackedFloorItem) -> bool {
match self {
InventoryItem::Stacked(self_stacked_item) => {
@ -90,6 +114,7 @@ impl InventoryItem {
}
// TODO: result
// TOOD: delete?
pub fn combine_stacks(&mut self, other_stacked_item: &mut StackedFloorItem) {
match self {
InventoryItem::Stacked(self_stacked_item) => {
@ -163,11 +188,46 @@ pub enum InventoryItemConsumeError {
InvalidAmount,
}
pub struct ConsumedItem {
pub struct IndividualConsumedItem {
pub entity_id: ItemEntityId,
pub item: ItemDetail,
}
pub struct StackedConsumedItem {
pub entity_ids: Vec<ItemEntityId>,
pub item: ItemDetail
pub tool: Tool
}
pub enum ConsumedItem {
Individual(IndividualConsumedItem),
Stacked(StackedConsumedItem),
}
impl ConsumedItem {
pub fn entity_ids(&self) -> Vec<ItemEntityId> {
match self {
ConsumedItem::Individual(individual_consumed_item) => {
vec![individual_consumed_item.entity_id]
},
ConsumedItem::Stacked(stacked_consumed_item) => {
stacked_consumed_item.entity_ids.clone()
}
}
}
pub fn item(&self) -> ItemDetail {
match self {
ConsumedItem::Individual(individual_consumed_item) => {
individual_consumed_item.item.clone()
},
ConsumedItem::Stacked(stacked_consumed_item) => {
ItemDetail::Tool(stacked_consumed_item.tool)
}
}
}
}
pub struct InventoryItemHandle<'a> {
inventory: &'a mut CharacterInventory,
slot: usize,
@ -175,37 +235,41 @@ pub struct InventoryItemHandle<'a> {
impl<'a> InventoryItemHandle<'a> {
pub fn item(&'a self) -> Option<&'a InventoryItem> {
self.inventory.0.get(self.slot)
self.inventory.items.get(self.slot)
}
pub fn item_mut(&mut self) -> Option<&mut InventoryItem> {
self.inventory.items.get_mut(self.slot)
}
pub fn remove_from_inventory(self) {
self.inventory.0.remove(self.slot);
self.inventory.items.remove(self.slot);
}
pub fn consume(self, amount: usize) -> Result<ConsumedItem, InventoryItemConsumeError> {
enum RemoveMethod {
EntireThing(ConsumedItem),
Partial(ItemDetail),
Partial(Tool),
}
let inventory_item = self.inventory.0.get(self.slot).ok_or(InventoryItemConsumeError::InconsistentState)?;
let inventory_item = self.inventory.items.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],
RemoveMethod::EntireThing(ConsumedItem::Individual(IndividualConsumedItem {
entity_id: 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 {
RemoveMethod::EntireThing(ConsumedItem::Stacked(StackedConsumedItem {
entity_ids: stacked_inventory_item.entity_ids.clone(),
item: ItemDetail::Tool(stacked_inventory_item.tool),
})
tool: stacked_inventory_item.tool,
}))
},
Ordering::Greater => {
RemoveMethod::Partial(ItemDetail::Tool(stacked_inventory_item.tool))
RemoveMethod::Partial(stacked_inventory_item.tool)
},
Ordering::Less => {
return Err(InventoryItemConsumeError::InvalidAmount)
@ -216,11 +280,11 @@ impl<'a> InventoryItemHandle<'a> {
match remove_method {
RemoveMethod::EntireThing(consumed_item) => {
self.inventory.0.remove(self.slot);
self.inventory.items.remove(self.slot);
Ok(consumed_item)
},
RemoveMethod::Partial(item_detail) => {
let entity_ids = self.inventory.0.get_mut(self.slot)
RemoveMethod::Partial(tool) => {
let entity_ids = self.inventory.items.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<_>>())
@ -230,10 +294,10 @@ impl<'a> InventoryItemHandle<'a> {
}
})
.ok_or(InventoryItemConsumeError::InvalidAmount)?;
Ok(ConsumedItem {
Ok(ConsumedItem::Stacked(StackedConsumedItem {
entity_ids: entity_ids,
item: item_detail,
})
tool: tool,
}))
}
}
}
@ -243,21 +307,28 @@ impl<'a> InventoryItemHandle<'a> {
#[derive(Debug)]
pub struct CharacterInventory(Vec<InventoryItem>);
pub struct CharacterInventory {
item_id_counter: u32,
items: Vec<InventoryItem>,
}
impl CharacterInventory {
pub fn new(items: Vec<InventoryItem>) -> CharacterInventory {
CharacterInventory(items)
CharacterInventory{
item_id_counter: 0,
items: items,
}
}
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
for (i, item) in self.0.iter_mut().enumerate() {
for (i, item) in self.items.iter_mut().enumerate() {
item.set_item_id(ClientItemId(base_item_id + i as u32));
}
self.item_id_counter = base_item_id + self.items.len() as u32 + 1;
}
pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] {
self.0.iter()
self.items.iter()
.enumerate()
.fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| {
let bytes = item.as_client_bytes();
@ -273,15 +344,15 @@ impl CharacterInventory {
}
pub fn slot(&self, slot: usize) -> Option<&InventoryItem> {
self.0.get(slot)
self.items.get(slot)
}
pub fn count(&self) -> usize {
self.0.len()
self.items.len()
}
pub fn get_item_handle_by_id<'a>(&'a mut self, item_id: ClientItemId) -> Option<InventoryItemHandle<'a>> {
let (slot, _) = self.0.iter()
let (slot, _) = self.items.iter()
.enumerate()
.filter(|(_, item)| {
item.item_id() == item_id
@ -294,7 +365,7 @@ impl CharacterInventory {
}
pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&InventoryItem> {
self.0.iter()
self.items.iter()
.filter(|item| {
item.item_id() == item_id
})
@ -302,14 +373,14 @@ impl CharacterInventory {
}
pub fn take_item_by_id(&mut self, item_id: ClientItemId) -> Option<InventoryItem> {
self.0
self.items
.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);
self.items.push(item);
Ok(())
}
@ -319,14 +390,14 @@ impl CharacterInventory {
return None;
}
self.0.push(InventoryItem::Individual(IndividualInventoryItem {
self.items.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() {
if let Some(InventoryItem::Individual(new_item)) = self.items.last() {
Some((new_item, InventorySlot(self.count())))
}
else {
@ -336,7 +407,7 @@ impl CharacterInventory {
// 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()
let existing_stack_position = self.items.iter()
.position(|inventory_item| {
if let InventoryItem::Stacked(stacked_inventory_item) = inventory_item {
if stacked_inventory_item.tool == floor_item.tool {
@ -347,7 +418,7 @@ impl CharacterInventory {
});
if let Some(existing_stack_position) = existing_stack_position {
if let Some(InventoryItem::Stacked(stacked_item)) = self.0.get_mut(existing_stack_position) {
if let Some(InventoryItem::Stacked(stacked_item)) = self.items.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)))
@ -367,8 +438,8 @@ impl CharacterInventory {
tool: floor_item.tool,
});
self.0.push(new_stacked_item);
if let Some(InventoryItem::Stacked(new_item)) = self.0.last() {
self.items.push(new_stacked_item);
if let Some(InventoryItem::Stacked(new_item)) = self.items.last() {
Some((new_item, InventorySlot(self.count())))
}
else {
@ -376,5 +447,69 @@ impl CharacterInventory {
}
}
}
pub fn withdraw_item(&mut self, mut bank_item: BankItemHandle, amount: usize) -> Option<(&InventoryItem, usize)> {
let (remove, slot) = match bank_item.item_mut()? {
BankItem::Individual(individual_bank_item) => {
if self.items.len() >= INVENTORY_CAPACITY {
return None
}
self.items.push(InventoryItem::Individual(IndividualInventoryItem {
entity_id: individual_bank_item.entity_id,
item_id: individual_bank_item.item_id,
item: individual_bank_item.item.clone(),
equipped: false,
}));
(true, self.count())
},
BankItem::Stacked(stacked_bank_item) => {
let existing_inventory_item = self.items.iter_mut()
.enumerate()
.find_map(|(index, item)| {
if let InventoryItem::Stacked(stacked_inventory_item) = item {
if stacked_inventory_item.tool == stacked_inventory_item.tool {
return Some((index, stacked_inventory_item))
}
}
None
});
let slot = match existing_inventory_item {
Some((slot, stacked_inventory_item)) => {
if stacked_inventory_item.count() + stacked_bank_item.count() > stacked_bank_item.tool.max_stack() {
return None
}
let mut withdrawn_entity_ids = stacked_bank_item.take_entity_ids(amount)?;
stacked_inventory_item.entity_ids.append(&mut withdrawn_entity_ids);
slot
}
None => {
if self.items.len() >= INVENTORY_CAPACITY {
return None
}
let withdrawn_entity_ids = stacked_bank_item.take_entity_ids(amount)?;
self.item_id_counter += 1; // oh no
self.items.push(InventoryItem::Stacked(StackedInventoryItem {
entity_ids: withdrawn_entity_ids,
item_id: ClientItemId(self.item_id_counter),
tool: stacked_bank_item.tool,
}));
self.count()
}
};
(stacked_bank_item.count() == 0, slot)
}
};
if remove {
bank_item.remove_from_bank();
}
self.items.last().map(|item| {
(item, slot)
})
}
}

109
src/ship/items/manager.rs

@ -32,15 +32,18 @@ pub enum ItemManagerError {
Idunnoman,
CouldNotSplitItem(ClientItemId),
CouldNotDropMeseta,
InvalidBankName(BankName),
NotEnoughTools(Tool, usize, usize), // have, expected
InventoryItemConsumeError(#[from] InventoryItemConsumeError),
BankFull,
}
pub struct ItemManager {
id_counter: u32,
character_inventory: HashMap<CharacterEntityId, CharacterInventory>,
character_bank: HashMap<CharacterEntityId, BTreeMap<BankName, CharacterBank>>,
//character_bank: HashMap<CharacterEntityId, BTreeMap<BankName, CharacterBank>>,
character_bank: HashMap<CharacterEntityId, CharacterBank>,
character_floor: HashMap<CharacterEntityId, RoomFloorItems>,
character_room: HashMap<CharacterEntityId, RoomId>,
@ -101,7 +104,8 @@ impl ItemManager {
acc
});
let bank_items = items.into_iter()
// TODO: not using BankName anymore, clean this up
let mut bank_items = items.into_iter()
.filter_map(|item| {
match item.location {
ItemLocation::Bank{name, ..} => Some((item.id, item.item, name)),
@ -158,7 +162,7 @@ impl ItemManager {
.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);
self.character_bank.insert(character.id, bank_items.remove(&BankName("".to_string())).unwrap_or(CharacterBank::new(Vec::new())));
}
pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) {
@ -166,7 +170,7 @@ impl ItemManager {
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()));
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);
@ -192,11 +196,20 @@ impl ItemManager {
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
.ok_or(ItemManagerError::NoCharacter(character.id))?)
//.get(&BankName("".to_string()))
//.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?)
}
/*pub fn get_character_bank_mut(&mut self, character: &CharacterEntity) -> Result<&CharacterBank, ItemManagerError> {
Ok(self.character_bank
.get_mut(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?
.entry(BankName("".to_string()))
.or_insert(CharacterBank::new(Vec::new())))
//.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?)
}*/
pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
self.character_inventory.remove(&character.id);
self.character_floor.remove(&character.id);
@ -256,7 +269,6 @@ impl ItemManager {
},
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)) => {
@ -501,29 +513,86 @@ impl ItemManager {
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 {
for entity_id in consumed_item.entity_ids() {
entity_gateway.change_item_location(&entity_id,
ItemLocation::Consumed).await;
}
Ok(consumed_item.item)
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)
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: ClientItemId,
amount: usize)
-> Result<(), ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let bank = self.character_bank
.get_mut(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?;
let item_to_deposit = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
let bank_item = bank.deposit_item(item_to_deposit, amount).ok_or(ItemManagerError::Idunnoman)?;
match bank_item {
BankItem::Individual(individual_bank_item) => {
entity_gateway.change_item_location(&individual_bank_item.entity_id,
ItemLocation::Bank {
character_id: character.id,
name: BankName("".to_string())
}).await;
},
BankItem::Stacked(stacked_bank_item) => {
for entity_id in &stacked_bank_item.entity_ids {
entity_gateway.change_item_location(entity_id,
ItemLocation::Bank {
character_id: character.id,
name: BankName("".to_string())
}).await;
}
}
}
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(())
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: ClientItemId,
amount: usize)
-> Result<&InventoryItem, ItemManagerError> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let bank = self.character_bank
.get_mut(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?;
let item_to_withdraw = bank.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
let inventory_item = inventory.withdraw_item(item_to_withdraw, amount).ok_or(ItemManagerError::Idunnoman)?;
match inventory_item {
(InventoryItem::Individual(individual_inventory_item), slot) => {
entity_gateway.change_item_location(&individual_inventory_item.entity_id,
ItemLocation::Inventory {
character_id: character.id,
slot: slot,
equipped: false,
}).await;
},
(InventoryItem::Stacked(stacked_inventory_item), slot) => {
for entity_id in &stacked_inventory_item.entity_ids {
entity_gateway.change_item_location(entity_id,
ItemLocation::Inventory {
character_id: character.id,
slot: slot,
equipped: false,
}).await;
}
}
}
Ok(inventory_item.0)
}
}

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

@ -2,7 +2,7 @@ use libpso::packet::messages::*;
use libpso::packet::ship::*;
use crate::common::leveltable::CharacterStats;
use crate::ship::ship::{ShipError};
use crate::ship::items::{StackedFloorItem, FloorItem, CharacterBank};
use crate::ship::items::{ClientItemId, InventoryItem, StackedFloorItem, FloorItem, CharacterBank};
use crate::ship::location::AreaClient;
use std::convert::TryInto;
@ -37,6 +37,18 @@ pub fn create_item(area_client: AreaClient, item: &FloorItem) -> Result<CreateIt
})
}
pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &InventoryItem) -> Result<CreateItem, ShipError> {
let bytes = item.as_client_bytes();
Ok(CreateItem {
client: area_client.local_client.id(),
target: 0,
item_data: bytes[0..12].try_into()?,
item_id: item.item_id().0,
item_data2: bytes[12..16].try_into()?,
unknown: 0,
})
}
pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Result<RemoveItemFromFloor, ShipError> {
Ok(RemoveItemFromFloor {
client: area_client.local_client.id(),
@ -118,3 +130,12 @@ pub fn bank_item_list(bank: &CharacterBank) -> BankItemList {
items: bank.as_client_bank_request()
}
}
pub fn player_no_longer_has_item(area_client: AreaClient, item_id: ClientItemId, amount: u32) -> PlayerNoLongerHasItem {
PlayerNoLongerHasItem {
client: area_client.local_client.id(),
target: 0,
item_id: item_id.0,
amount: amount,
}
}

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

@ -72,45 +72,6 @@ where
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
/*
let item_drop_packets = clients_in_area.into_iter()
.filter_map(|area_client| {
room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
warn!("drop is? {:?}", item_drop_type);
(area_client, item_drop_type)
})
})
.map(|(area_client, item_drop_type)| async {
let item_drop = ItemDrop {
map_area: monster.map_area,
x: request_item.x,
y: request_item.y,
z: request_item.z,
item: item_drop_type,
};
let client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?;
let floor_item = item_manager.enemy_drop_item_on_local_floor(entity_gateway, &client.character, item_drop).await.unwrap(); // TODO: unwrap
let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &floor_item)?;
// I am not able to manually specify a closure return type when also using the async keyword
let result: Result<(ClientId, SendShipPacket), ShipError> = Ok((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg)))));
result
})
.map(|item_drop_pkt| async {
item_drop_pkt.await
});
let item_drop_packets = join_all(item_drop_packets).await.into_iter()
.filter_map(|item_drop_pkt| {
// TODO: log errors here
item_drop_pkt.ok()
});
//.collect::<Vec<_>>(); // TODO: can EntityGateway be Sync?
Ok(Box::new(item_drop_packets))
*/
let client_and_drop = clients_in_area.into_iter()
.filter_map(|area_client| {
room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
@ -209,41 +170,6 @@ EG: EntityGateway
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
/*let item_drop_packets = clients_in_area.into_iter()
.filter_map(|area_client| {
room.drop_table.get_box_drop(&box_object.map, &box_object).map(|item_drop_type| {
warn!("drop is? {:?}", item_drop_type);
(area_client, item_drop_type)
})
})
.map(async move |(area_client, item_drop_type)| -> Result<_, ShipError> {
let item_drop = ItemDrop {
map_area: box_object.map,
x: box_drop_request.x,
y: 0.0,
z: box_drop_request.z,
item: item_drop_type,
};
let client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?;
let floor_item = item_manager.enemy_drop_item_on_local_floor(entity_gateway, &client.character, item_drop).await.unwrap(); // TODO: unwrap
let item_drop_msg = builder::message::item_drop(box_drop_request.client, box_drop_request.target, &floor_item)?;
Ok((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg)))))
})
/*.filter_map(|item_drop_pkt| {
// TODO: log errors here
item_drop_pkt.ok()
})
.collect::<Vec<_>>(); // TODO: can EntityGateway be Sync?*/
;
let item_drop_packets = join_all(item_drop_packets).await.into_iter()
.filter_map(|item_drop_pkt| {
// TODO: log errors here
item_drop_pkt.ok()
});
Ok(Box::new(item_drop_packets))
*/
let client_and_drop = clients_in_area.into_iter()
.filter_map(|area_client| {
room.drop_table.get_box_drop(&box_object.map, &box_object).map(|item_drop_type| {
@ -287,6 +213,7 @@ pub async fn send_bank_list(id: ClientId,
pub async fn bank_interaction<EG>(id: ClientId,
bank_interaction: &BankInteraction,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
@ -294,32 +221,51 @@ where
EG: EntityGateway
{
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
match bank_interaction.action {
let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
let other_clients_in_area = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?;
let bank_action_pkts = 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;
if client.character.meseta > bank_interaction.meseta_amount && (bank_interaction.meseta_amount + client.character.bank_meseta) <= 999999 {
client.character.meseta -= bank_interaction.meseta_amount;
client.character.bank_meseta += bank_interaction.meseta_amount;
entity_gateway.save_character(&client.character).await;
}
Vec::new()
}
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?;
let player_no_longer_has_item = builder::message::player_no_longer_has_item(area_client, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32);
vec![SendShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(player_no_longer_has_item)))]
}
},
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;
client.character.bank_meseta -= bank_interaction.meseta_amount;
entity_gateway.save_character(&client.character).await;
}
Vec::new()
}
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?;
let item_added_to_inventory = item_manager.player_withdraws_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?;
let item_created = builder::message::create_withdrawn_inventory_item(area_client, &item_added_to_inventory)?;
vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(item_created)))]
}
},
_ => {}
}
Ok(Box::new(None.into_iter()))
_ => {
Vec::new()
}
};
Ok(Box::new(other_clients_in_area.into_iter()
.map(move |c| {
bank_action_pkts.clone().into_iter()
.map(move |pkt| {
(c.client, pkt)
})
})
.flatten()
))
}

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

@ -117,7 +117,7 @@ pub fn drop_coordinates(id: ClientId,
}
pub async fn split_item_stack<EG>(id: ClientId,
split_item_stack: &PlayerSplitItemStack,
no_longer_has_item: &PlayerNoLongerHasItem,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
@ -131,12 +131,12 @@ where
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let drop_location = client.item_drop_location.ok_or(ShipError::ItemDropLocationNotSet)?;
if drop_location.item_id.0 != split_item_stack.item_id {
return Err(ShipError::DropInvalidItemId(split_item_stack.item_id));
if drop_location.item_id.0 != no_longer_has_item.item_id {
return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id));
}
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?;
if no_longer_has_item.item_id == 0xFFFFFFFF {
let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, no_longer_has_item.amount as u32).await?;
let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?;
client.item_drop_location = None;
@ -148,7 +148,7 @@ where
})))
}
else {
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 = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, no_longer_has_item.amount as usize).await?;
let dropped_item_pkt = builder::message::drop_split_stack(area_client, dropped_item)?;
client.item_drop_location = None;

6
src/ship/ship.rs

@ -283,8 +283,8 @@ impl<EG: EntityGateway> ShipServerState<EG> {
GameMessage::DropCoordinates(drop_coordinates) => {
handler::message::drop_coordinates(id, drop_coordinates, &self.client_location, &mut self.clients, &self.rooms)
},
GameMessage::PlayerSplitItemStack(split_item_stack) => {
handler::message::split_item_stack(id, split_item_stack, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => {
handler::message::split_item_stack(id, no_longer_has_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
},
GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) |
GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) |
@ -327,7 +327,7 @@ impl<EG: EntityGateway> ShipServerState<EG> {
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
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &self.client_location, &mut self.clients, &mut self.item_manager).await
},
_ => {
let cmsg = msg.clone();

1569
tests/test_bank.rs
File diff suppressed because it is too large
View File

11
tests/test_item_pickup.rs

@ -205,7 +205,7 @@ async fn test_pick_up_meseta_when_inventory_full() {
z: 0.0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack {
ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -445,7 +445,7 @@ async fn test_can_not_drop_more_meseta_than_is_held() {
z: 0.0,
})))).await.unwrap().for_each(drop);
let split_attempt = ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack {
let split_attempt = ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -565,7 +565,7 @@ async fn test_can_not_pick_up_meseta_when_full() {
z: 0.0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack {
ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -579,7 +579,6 @@ async fn test_can_not_pick_up_meseta_when_full() {
map_area: 0,
unknown: [0; 3]
})))).await.unwrap().collect::<Vec<_>>();
println!("pkts {:?}", packets);
assert!(packets.len() == 0);
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
@ -622,7 +621,7 @@ async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() {
z: 0.0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack {
ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -688,7 +687,7 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() {
z: 0.0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack {
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {
client: 0,
target: 0,
item_id: 0x10000,

Loading…
Cancel
Save