Browse Source

bank itemstate stuff

kill_counters
jake 3 years ago
committed by andy
parent
commit
3e907c6066
  1. 1
      src/entity/gateway/inmemory.rs
  2. 30
      src/entity/gateway/postgres/models.rs
  3. 8
      src/entity/item/mod.rs
  4. 309
      src/ship/items/actions.rs
  5. 4
      src/ship/items/manager.rs
  6. 478
      src/ship/items/state.rs
  7. 9
      src/ship/packet/builder/lobby.rs
  8. 18
      src/ship/packet/builder/message.rs
  9. 9
      src/ship/packet/builder/mod.rs
  10. 7
      src/ship/packet/builder/room.rs
  11. 6
      src/ship/packet/handler/auth.rs
  12. 39
      src/ship/packet/handler/direct_message.rs
  13. 17
      src/ship/packet/handler/lobby.rs
  14. 12
      src/ship/packet/handler/room.rs
  15. 18
      src/ship/ship.rs
  16. 28
      tests/test_bank.rs

1
src/entity/gateway/inmemory.rs

@ -9,6 +9,7 @@ use crate::entity::item::*;
use std::sync::{Arc, Mutex};
// TODO: implement multiple banks
pub struct InMemoryGatewayTransaction<'a> {
working_gateway: InMemoryGateway,

30
src/entity/gateway/postgres/models.rs

@ -616,6 +616,14 @@ pub enum PgItemNoteDetail {
character_to: u32,
character_from: u32,
},
Withdraw {
character_id: u32,
bank: String,
},
Deposit {
character_id: u32,
bank: String,
}
}
impl From<ItemNote> for PgItemNoteDetail {
@ -649,6 +657,18 @@ impl From<ItemNote> for PgItemNoteDetail {
id: id.0,
character_to: character_to.0,
character_from: character_from.0,
},
ItemNote::Withdraw{character_id, bank} => {
PgItemNoteDetail::Withdraw {
character_id: character_id.0,
bank: bank.0,
}
},
ItemNote::Deposit{character_id, bank} => {
PgItemNoteDetail::Deposit {
character_id: character_id.0,
bank: bank.0,
}
}
}
}
@ -685,7 +705,15 @@ impl From<PgItemNoteDetail> for ItemNote {
id: TradeId(id as u32),
character_to: CharacterEntityId(character_to as u32),
character_from: CharacterEntityId(character_from as u32),
}
},
PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw {
character_id: CharacterEntityId(character_id as u32),
bank: BankName(bank),
},
PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit {
character_id: CharacterEntityId(character_id as u32),
bank: BankName(bank),
},
}
}
}

8
src/entity/item/mod.rs

@ -59,6 +59,14 @@ pub enum ItemNote {
character_to: CharacterEntityId,
character_from: CharacterEntityId,
},
Withdraw {
character_id: CharacterEntityId,
bank: BankName,
},
Deposit {
character_id: CharacterEntityId,
bank: BankName,
},
}
#[derive(Debug, Copy, Clone, PartialEq)]

309
src/ship/items/actions.rs

@ -6,7 +6,10 @@ use std::pin::Pin;
use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateAction, ItemAction, ItemStateError, FloorItem, InventoryItem, AddItemResult, FloorItemDetail, StackedItemDetail};
use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateAction, ItemAction, ItemStateError, FloorItem, InventoryItem, AddItemResult, FloorItemDetail,
StackedItemDetail, BankItem, BankItemDetail, InventoryItemDetail};
pub enum TriggerCreateItem {
Yes,
@ -94,14 +97,14 @@ where
}).await
}
fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId)
fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), InventoryItem), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id)?;
let item = inventory.take_item(&item_id).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?;
let item = inventory.take_item(&item_id, amount).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?;
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory);
@ -114,7 +117,7 @@ fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItem
fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32, f32))
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), ItemStateError>> + Send + 'a>>
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), FloorItem), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, transaction), inventory_item| {
Box::pin(async move {
@ -133,10 +136,10 @@ fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area:
}}).await?;
let mut floor = item_state.floor(&character_id)?;
floor.add_inventory_item(inventory_item, map_area, drop_position);
let floor_item = floor.add_inventory_item(inventory_item, map_area, drop_position).clone();
item_state.set_floor(floor);
Ok(((item_state, transaction), ()))
Ok(((item_state, transaction), floor_item))
})
}
}
@ -148,14 +151,14 @@ pub async fn drop_item<EG>(
item_id: &ClientItemId,
map_area: MapArea,
drop_position: (f32, f32, f32))
-> Result<(), ItemStateError>
-> Result<FloorItem, ItemStateError>
where
EG: EntityGateway,
{
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_item_from_inventory(character.id, *item_id))
.act(take_item_from_inventory(character.id, *item_id, 1))
.act(add_inventory_item_to_shared_floor(character.id, map_area, drop_position))
.commit((item_state_proxy, transaction))
.await?;
@ -164,53 +167,62 @@ where
}).await
}
pub async fn drop_partial_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
map_area: MapArea,
drop_position: (f32, f32),
amount: u32)
-> Result<FloorItem, ItemStateError>
where
EG: EntityGateway,
{
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_item_from_inventory(character.id, *item_id, amount))
.act(add_inventory_item_to_shared_floor(character.id, map_area, (drop_position.0, 0.0, drop_position.1)))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit();
Ok((transaction, result))
}).await
}
fn take_partial_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), StackedItemDetail), ItemStateError>> + Send + 'a>>
fn take_meseta_from_inventory(character_id: CharacterEntityId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id)?;
let item = inventory.take_partial_item(&item_id, amount).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?;
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory);
inventory.remove_meseta(amount)?;
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
Ok(((item_state, transaction), item))
Ok(((item_state, transaction), ()))
})
}
}
fn add_partial_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32))
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), StackedItemDetail)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), FloorItem), ItemStateError>> + Send + 'a>>
fn add_meseta_to_shared_floor(character_id: CharacterEntityId, amount: u32, map_area: MapArea, drop_position: (f32, f32))
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), FloorItem), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, transaction), stacked_item| {
move |(mut item_state, transaction), _| {
Box::pin(async move {
let floor_item = FloorItem {
item_id: item_state.new_item_id()?,
item: FloorItemDetail::Stacked(stacked_item),
map_area,
item: FloorItemDetail::Meseta(Meseta(amount)),
map_area: map_area,
x: drop_position.0,
y: 0.0,
z: drop_position.1,
};
let transaction = floor_item.with_entity_id(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id| {
async move {
if let Ok(transaction) = &mut transaction {
transaction.gateway().add_item_note(&entity_id, ItemNote::PlayerDrop {
character_id,
map_area,
x: drop_position.0,
y: 0.0,
z: drop_position.1,
}).await?;
}
transaction
}}).await?;
let mut floor = item_state.floor(&character_id)?;
let floor_item = floor.add_item(floor_item).clone();
item_state.set_floor(floor);
@ -220,11 +232,10 @@ fn add_partial_inventory_item_to_shared_floor(character_id: CharacterEntityId, m
}
}
pub async fn drop_partial_item<'a, EG>(
pub async fn drop_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
map_area: MapArea,
drop_position: (f32, f32),
amount: u32)
@ -235,8 +246,8 @@ where
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_partial_item_from_inventory(character.id, *item_id, amount))
.act(add_partial_inventory_item_to_shared_floor(character.id, map_area, drop_position))
.act(take_meseta_from_inventory(character.id, amount))
.act(add_meseta_to_shared_floor(character.id, amount, map_area, drop_position))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit();
@ -245,62 +256,228 @@ where
}
fn take_meseta_from_inventory(character_id: CharacterEntityId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), u32), ItemStateError>> + Send + 'a>>
fn take_meseta_from_bank(character_id: CharacterEntityId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id)?;
bank.remove_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
Ok(((item_state, transaction), ()))
})
}
}
fn add_meseta_from_bank_to_inventory(character_id: CharacterEntityId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id)?;
inventory.remove_meseta(amount)?;
inventory.add_meseta_no_overflow(amount)?;
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
Ok(((item_state, transaction), amount))
Ok(((item_state, transaction), ()))
})
}
}
fn add_meseta_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32))
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), u32)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), FloorItem), ItemStateError>> + Send + 'a>>
pub async fn withdraw_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
amount: u32)
-> Result<(), ItemStateError>
where
EG: EntityGateway,
{
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_meseta_from_bank(character.id, amount))
.act(add_meseta_from_bank_to_inventory(character.id, amount))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit();
Ok((transaction, result))
}).await
}
move |(mut item_state, transaction), amount| {
fn add_meseta_to_bank(character_id: CharacterEntityId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let floor_item = FloorItem {
item_id: item_state.new_item_id()?,
item: FloorItemDetail::Meseta(Meseta(amount)),
map_area: map_area,
x: drop_position.0,
y: 0.0,
z: drop_position.1,
let mut bank = item_state.bank(&character_id)?;
bank.add_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
Ok(((item_state, transaction), ()))
})
}
}
pub async fn deposit_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
amount: u32)
-> Result<(), ItemStateError>
where
EG: EntityGateway,
{
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_meseta_from_inventory(character.id, amount))
.act(add_meseta_to_bank(character.id, amount))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit();
Ok((transaction, ()))
}).await
}
fn take_item_from_bank(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), BankItem), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id)?;
let item = bank.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoBankItem(item_id))?;
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?;
item_state.set_bank(bank);
Ok(((item_state, transaction), item))
})
}
}
fn add_bank_item_to_inventory(character: &CharacterEntity)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), BankItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), InventoryItem), ItemStateError>> + Send + 'a>>
{
let character = character.clone();
move |(mut item_state, transaction), bank_item| {
let character = character.clone();
Box::pin(async move {
let bank_name = item_state.bank(&character.id)?.name;
let mut inventory = item_state.inventory(&character.id)?;
let character_id = character.id;
let transaction = bank_item.with_entity_id(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id| {
let bank_name = bank_name.clone();
async move {
if let Ok(transaction) = &mut transaction {
transaction.gateway().add_item_note(&entity_id, ItemNote::Withdraw {
character_id,
bank: bank_name,
}).await?;
}
transaction
}}).await?;
let inventory_item = InventoryItem {
item_id: bank_item.item_id,
item: match bank_item.item {
BankItemDetail::Individual(individual_item) => InventoryItemDetail::Individual(individual_item),
BankItemDetail::Stacked(stacked_item) => InventoryItemDetail::Stacked(stacked_item),
},
};
let mut floor = item_state.floor(&character_id)?;
let floor_item = floor.add_item(floor_item).clone();
item_state.set_floor(floor);
let mut transaction = inventory_item.with_mag(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id, _mag| {
let character = character.clone();
async move {
if let Ok(transaction) = &mut transaction {
transaction.gateway().change_mag_owner(&entity_id, &character).await?;
}
transaction
}}).await?;
Ok(((item_state, transaction), floor_item))
inventory.add_item(inventory_item.clone())?;
transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
item_state.set_inventory(inventory);
Ok(((item_state, transaction), inventory_item))
})
}
}
pub async fn drop_meseta<'a, EG>(
pub async fn withdraw_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
map_area: MapArea,
drop_position: (f32, f32),
item_id: &ClientItemId,
amount: u32)
-> Result<FloorItem, ItemStateError>
-> Result<InventoryItem, ItemStateError>
where
EG: EntityGateway,
{
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_meseta_from_inventory(character.id, amount))
.act(add_meseta_to_shared_floor(character.id, map_area, drop_position))
.act(take_item_from_bank(character.id, *item_id, amount))
.act(add_bank_item_to_inventory(&character))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit();
Ok((transaction, result))
}).await
}
fn add_inventory_item_to_bank(character_id: CharacterEntityId)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, transaction), inventory_item| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id)?;
let bank_name = bank.name.clone();
let mut transaction = inventory_item.with_entity_id(Ok(transaction), move |mut transaction: Result<_, ItemStateError>, entity_id| {
let bank_name = bank_name.clone();
async move {
if let Ok(transaction) = &mut transaction {
transaction.gateway().add_item_note(&entity_id, ItemNote::Deposit {
character_id,
bank: bank_name,
}).await?;
}
transaction
}}).await?;
bank.add_inventory_item(inventory_item)?;
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?;
item_state.set_bank(bank);
Ok(((item_state, transaction), ()))
})
}
}
pub async fn deposit_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
amount: u32)
-> Result<(), ItemStateError>
where
EG: EntityGateway,
{
entity_gateway.with_transaction(|transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state);
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(take_item_from_inventory(character.id, *item_id, amount))
.act(add_inventory_item_to_bank(character.id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit();

4
src/ship/items/manager.rs

@ -393,7 +393,7 @@ impl ItemManager {
.map_err(|err| err.into())
}
pub async fn enemy_drop_item_on_local_floor<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&'a FloorItem, anyhow::Error> {
pub async fn enemy_drop_item_on_local_floor<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &'a CharacterEntity, item_drop: ItemDrop) -> Result<&'a FloorItem, anyhow::Error> {
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
enum ItemOrMeseta {
@ -809,7 +809,7 @@ impl ItemManager {
}
pub async fn player_buys_item<'a, EG: EntityGateway>(&'a mut self,
entity_gateway: &mut EG,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
shop_item: &'a (dyn ShopItem + Send + Sync),
item_id: ClientItemId,

478
src/ship/items/state.rs

@ -2,7 +2,7 @@ use std::cmp::Ordering;
use std::collections::HashMap;
use libpso::character::character;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, BankItemEntity, BankName, EquippedEntity};
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, BankEntity, BankItemEntity, BankName, EquippedEntity};
use std::future::Future;
use crate::ship::map::MapArea;
@ -13,6 +13,7 @@ use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag;
use crate::ship::drops::ItemDrop;
// TODO: Commit trait that ItemStateProxy and EntityTransaction implement that .commit requires and acts on upon everything succeeding (like 3 less lines of code!)
#[derive(thiserror::Error, Debug)]
pub enum ItemStateError {
@ -23,9 +24,15 @@ pub enum ItemStateError {
#[error("floor item {0} not found")]
NoFloorItem(ClientItemId),
#[error("bank item {0} not found")]
NoBankItem(ClientItemId),
#[error("inventory error {0}")]
InventoryError(#[from] InventoryError),
#[error("bank error {0}")]
BankError(#[from] BankError),
#[error("invalid drop? {0:?} (this shouldn't occur)")]
BadItemDrop(ItemDrop),
@ -35,8 +42,11 @@ pub enum ItemStateError {
#[error("gateway")]
GatewayError(#[from] GatewayError),
#[error("tried to drop more meseta than in inventory: {0}")]
InvalidMesetaDrop(u32),
#[error("tried to remove more meseta than exists: {0}")]
InvalidMesetaRemoval(u32),
#[error("tried to add meseta when there is no more room")]
FullOfMeseta,
#[error("stacked item")]
StackedItemError(Vec<ItemEntity>),
@ -247,8 +257,8 @@ impl InventoryItemDetail {
#[derive(Clone, Debug)]
pub struct InventoryItem {
item_id: ClientItemId,
item: InventoryItemDetail,
pub item_id: ClientItemId,
pub item: InventoryItemDetail,
}
impl InventoryItem {
@ -270,6 +280,19 @@ impl InventoryItem {
param
}
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> T
where
F: FnMut(T, ItemEntityId, Mag) -> Fut,
Fut: Future<Output=T>,
{
if let InventoryItemDetail::Individual(individual_item) = &self.item {
if let ItemDetail::Mag(mag) = &individual_item.item {
param = func(param, individual_item.entity_id, mag.clone()).await;
}
}
param
}
}
@ -280,6 +303,13 @@ pub enum BankItemDetail {
}
impl BankItemDetail {
fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> {
match self {
BankItemDetail::Stacked(sitem) => Some(sitem),
_ => None,
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
BankItemDetail::Individual(item) => {
@ -303,8 +333,29 @@ impl BankItemDetail {
#[derive(Clone, Debug)]
pub struct BankItem {
item_id: ClientItemId,
item: BankItemDetail,
pub item_id: ClientItemId,
pub item: BankItemDetail,
}
impl BankItem {
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> T
where
F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=T>,
{
match &self.item {
BankItemDetail::Individual(individual_item) => {
param = func(param, individual_item.entity_id).await;
},
BankItemDetail::Stacked(stacked_item) => {
for entity_id in &stacked_item.entity_ids {
param = func(param, *entity_id).await;
}
}
}
param
}
}
#[derive(Clone)]
@ -397,6 +448,16 @@ pub enum InventoryError {
MesetaFull,
}
#[derive(thiserror::Error, Debug)]
pub enum BankError {
#[error("bank full")]
BankFull,
#[error("stack full")]
StackFull,
#[error("meseta full")]
MesetaFull,
}
#[derive(Clone)]
pub enum AddItemResult {
NewItem,
@ -428,6 +489,11 @@ impl InventoryState {
self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1;
}
pub fn new_item_id(&mut self) -> ClientItemId {
self.item_id_counter += 1;
ClientItemId(self.item_id_counter)
}
pub fn count(&self) -> usize {
self.inventory.0.len()
}
@ -490,43 +556,77 @@ impl InventoryState {
}
}
pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<InventoryItem> {
self.inventory.0
.drain_filter(|i| i.item_id == *item_id)
.next()
pub fn add_item(&mut self, item: InventoryItem) -> Result<AddItemResult, InventoryError> {
match &item.item {
InventoryItemDetail::Individual(_) => {
if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
}
else {
self.inventory.0.push(item);
Ok(AddItemResult::NewItem)
}
},
InventoryItemDetail::Stacked(sitem) => {
let existing_stack = self.inventory.0
.iter_mut()
.filter_map(|item| item.item.stacked_mut())
.find(|item| {
item.tool == sitem.tool
});
match existing_stack {
Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(InventoryError::StackFull)
}
else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
Ok(AddItemResult::AddToStack)
}
},
None => {
if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
}
else {
self.inventory.0.push(item);
Ok(AddItemResult::NewItem)
}
}
}
}
}
}
pub fn take_partial_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<StackedItemDetail> {
let amount = amount as usize;
let (idx, _, stacked_item) = self.inventory.0
.iter_mut()
.enumerate()
.filter_map(|(k, item)| {
match item.item {
InventoryItemDetail::Stacked(ref mut stacked_item) => Some((k, item.item_id, stacked_item)),
_ => None
pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<InventoryItem> {
let idx = self.inventory.0
.iter()
.position(|i| i.item_id == *item_id)?;
match &mut self.inventory.0[idx].item {
InventoryItemDetail::Individual(_individual_item) => {
Some(self.inventory.0.remove(idx))
},
InventoryItemDetail::Stacked(stacked_item) => {
let remove_all = match stacked_item.entity_ids.len().cmp(&(amount as usize)) {
Ordering::Equal => true,
Ordering::Greater => false,
Ordering::Less => return None,
};
if remove_all {
Some(self.inventory.0.remove(idx))
}
})
.find(|(_, id, _)| *id == *item_id)?;
let remove_all = match stacked_item.entity_ids.len().cmp(&amount) {
Ordering::Equal => true,
Ordering::Greater => false,
Ordering::Less => return None,
};
if remove_all {
let stacked_item = stacked_item.clone();
self.inventory.0.remove(idx);
Some(stacked_item)
}
else {
let entity_ids = stacked_item.entity_ids.drain(..amount).collect();
Some(StackedItemDetail {
entity_ids: entity_ids,
tool: stacked_item.tool,
})
else {
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
self.item_id_counter += 1;
Some(InventoryItem {
item_id: ClientItemId(self.item_id_counter),
item: InventoryItemDetail::Stacked(StackedItemDetail {
entity_ids: entity_ids,
tool: stacked_item.tool,
})})
}
}
}
}
@ -557,9 +657,25 @@ impl InventoryState {
}
}
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
if self.meseta.0 == 999999 {
return Err(ItemStateError::FullOfMeseta)
}
self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
Ok(())
}
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> {
if self.meseta.0 + amount > 999999 {
return Err(ItemStateError::FullOfMeseta)
}
self.meseta.0 += amount;
Ok(())
}
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
if amount > self.meseta.0 {
return Err(ItemStateError::InvalidMesetaDrop(amount))
return Err(ItemStateError::InvalidMesetaRemoval(amount))
}
self.meseta.0 -= amount;
Ok(())
@ -601,16 +717,130 @@ pub struct Bank(Vec<BankItem>);
pub struct BankState {
character_id: CharacterEntityId,
item_id_counter: u32,
pub name: BankName,
bank: Bank,
pub meseta: Meseta,
}
impl BankState {
pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState {
bank.0.sort();
BankState {
character_id,
item_id_counter: 0,
name,
bank,
meseta,
}
}
pub fn count(&self) -> usize {
self.bank.0.len()
}
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
for (i, item) in self.bank.0.iter_mut().enumerate() {
item.item_id = ClientItemId(base_item_id + i as u32);
}
self.item_id_counter = base_item_id + self.bank.0.len() as u32 + 1;
self.item_id_counter = base_item_id + self.bank.0.len() as u32;
}
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
if self.meseta.0 + amount > 999999 {
return Err(ItemStateError::FullOfMeseta)
}
self.meseta.0 += self.meseta.0 + amount;
Ok(())
}
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
if amount > self.meseta.0 {
return Err(ItemStateError::InvalidMesetaRemoval(amount))
}
self.meseta.0 -= amount;
Ok(())
}
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, BankError> {
match item.item {
InventoryItemDetail::Individual(iitem) => {
if self.bank.0.len() >= 30 {
Err(BankError::BankFull)
}
else {
self.bank.0.push(BankItem {
item_id: item.item_id,
item: BankItemDetail::Individual(iitem)
});
self.bank.0.sort();
Ok(AddItemResult::NewItem)
}
},
InventoryItemDetail::Stacked(sitem) => {
let existing_stack = self.bank.0
.iter_mut()
.filter_map(|item| item.item.stacked_mut())
.find(|item| {
item.tool == sitem.tool
});
match existing_stack {
Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(BankError::StackFull)
}
else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
Ok(AddItemResult::AddToStack)
}
},
None => {
if self.bank.0.len() >= 30 {
Err(BankError::BankFull)
}
else {
self.bank.0.push(BankItem {
item_id: item.item_id,
item: BankItemDetail::Stacked(sitem)
});
self.bank.0.sort();
Ok(AddItemResult::NewItem)
}
}
}
}
}
}
pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<BankItem> {
let idx = self.bank.0
.iter()
.position(|i| i.item_id == *item_id)?;
match &mut self.bank.0[idx].item {
BankItemDetail::Individual(_individual_item) => {
Some(self.bank.0.remove(idx))
},
BankItemDetail::Stacked(stacked_item) => {
let remove_all = match stacked_item.entity_ids.len().cmp(&(amount as usize)) {
Ordering::Equal => true,
Ordering::Greater => false,
Ordering::Less => return None,
};
if remove_all {
Some(self.bank.0.remove(idx))
}
else {
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
self.item_id_counter += 1;
Some(BankItem {
item_id: ClientItemId(self.item_id_counter),
item: BankItemDetail::Stacked(StackedItemDetail {
entity_ids: entity_ids,
tool: stacked_item.tool,
})})
}
}
}
}
pub fn as_client_bank_items(&self) -> character::Bank {
@ -626,6 +856,106 @@ impl BankState {
bank
})
}
pub fn as_client_bank_request(&self) -> Vec<character::BankItem> {
self.bank.0.iter()
.map(|item| {
let bytes = item.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.item {
BankItemDetail::Individual(_individual_bank_item) => {
1
},
BankItemDetail::Stacked(stacked_bank_item) => {
stacked_bank_item.count()
},
};
character::BankItem {
data1,
data2,
item_id: item.item_id.0,
amount: amount as u16,
flags: 1,
}
})
.collect()
}
pub fn as_bank_entity(&self) -> BankEntity {
BankEntity {
items: self.bank.0.iter()
.map(|item| {
match &item.item {
BankItemDetail::Individual(item) => {
BankItemEntity::Individual(ItemEntity {
id: item.entity_id,
item: item.item.clone(),
})
},
BankItemDetail::Stacked(items) => {
BankItemEntity::Stacked(items.entity_ids.iter()
.map(|id| {
ItemEntity {
id: *id,
item: ItemDetail::Tool(items.tool)
}
})
.collect())
},
}
})
.collect()
}
}
}
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.item.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.item.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 mut self_bytes = [0u8; 4];
let mut other_bytes = [0u8; 4];
self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.item.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 mut self_bytes = [0u8; 4];
let mut other_bytes = [0u8; 4];
self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.item.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)
}
}
pub struct FloorState {
@ -677,14 +1007,11 @@ impl FloorState {
pub struct ItemState {
character_inventory: HashMap<CharacterEntityId, InventoryState>,
character_bank: HashMap<CharacterEntityId, BankState>,
//character_meseta: HashMap<CharacterEntityId, Meseta>,
//bank_meseta: HashMap<CharacterEntityId, Meseta>,
character_room: HashMap<CharacterEntityId, RoomId>,
character_floor: HashMap<CharacterEntityId, LocalFloor>,
room_floor: HashMap<RoomId, SharedFloor>,
//room_item_id_counter: Arc<RefCell<HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send + Sync>>>>,
room_item_id_counter: u32,
}
@ -693,8 +1020,6 @@ impl Default for ItemState {
ItemState {
character_inventory: HashMap::new(),
character_bank: HashMap::new(),
//character_meseta: HashMap::new(),
//bank_meseta: HashMap::new(),
character_room: HashMap::new(),
character_floor: HashMap::new(),
room_floor: HashMap::new(),
@ -795,13 +1120,8 @@ impl ItemState {
})
.collect::<Result<Vec<_>, _>>()?;
let bank_meseta = entity_gateway.get_bank_meseta(&character.id, BankName("".into())).await?;
let bank_state = BankState {
character_id: character.id,
item_id_counter: 0,
bank: Bank(bank_items),
meseta: bank_meseta,
};
let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?;
let bank_state = BankState::new(character.id, BankName("".into()), Bank(bank_items), bank_meseta);
self.character_inventory.insert(character.id, inventory_state);
self.character_bank.insert(character.id, bank_state);
@ -820,14 +1140,6 @@ impl ItemState {
self.character_room.insert(character.id, room_id);
self.character_floor.insert(character.id, LocalFloor::default());
self.room_floor.entry(room_id).or_insert_with(SharedFloor::default);
/*
let mut inc = 0x00810000;
self.room_item_id_counter.borrow_mut().entry(room_id).or_insert_with(|| Box::new(move || {
inc += 1;
ClientItemId(inc)
}));
*/
}
pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
@ -864,34 +1176,12 @@ impl ItemState {
struct ProxiedItemState {
character_inventory: HashMap<CharacterEntityId, InventoryState>,
character_bank: HashMap<CharacterEntityId, BankState>,
//character_meseta: HashMap<CharacterEntityId, Meseta>,
//bank_meseta: HashMap<CharacterEntityId, Meseta>,
character_room: HashMap<CharacterEntityId, RoomId>,
character_floor: HashMap<CharacterEntityId, LocalFloor>,
room_floor: HashMap<RoomId, SharedFloor>,
//room_item_id_counter: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>,
}
/*
impl Default for ProxiedItemState {
fn default() -> Self {
ProxiedItemState {
character_inventory: HashMap::new(),
//character_bank: HashMap::new(),
character_meseta: HashMap::new(),
//bank_meseta: HashMap::new(),
character_floor: HashMap::new(),
character_room: HashMap::new(),
room_floor: HashMap::new(),
//room_item_id_counter: HashMap::new(),
}
}
}
*/
pub struct ItemStateProxy<'a> {
item_state: &'a mut ItemState,
proxied_state: ProxiedItemState,
@ -901,7 +1191,6 @@ impl<'a> ItemStateProxy<'a> {
pub fn commit(self) {
self.item_state.character_inventory.extend(self.proxied_state.character_inventory.clone());
self.item_state.character_bank.extend(self.proxied_state.character_bank.clone());
//self.item_state.character_meseta.extend(self.proxied_state.character_meseta.clone());
self.item_state.character_room.extend(self.proxied_state.character_room.clone());
self.item_state.character_floor.extend(self.proxied_state.character_floor.clone());
self.item_state.room_floor.extend(self.proxied_state.room_floor.clone());
@ -930,19 +1219,18 @@ impl<'a> ItemStateProxy<'a> {
pub fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, ItemStateError> {
get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter)
/*
Ok(InventoryState {
character_id: *character_id,
inventory: get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter)?,
meseta: get_or_clone(&self.item_state.character_meseta, &mut self.proxied_state.character_meseta, *character_id, ItemStateError::NoCharacter)?,
})
*/
}
pub fn set_inventory(&mut self, inventory: InventoryState) {
self.proxied_state.character_inventory.insert(inventory.character_id, inventory);
//self.proxied_state.character_inventory.insert(inventory.character_id, inventory.inventory);
//self.proxied_state.character_meseta.insert(inventory.character_id, inventory.meseta);
}
pub fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, ItemStateError> {
get_or_clone(&self.item_state.character_bank, &mut self.proxied_state.character_bank, *character_id, ItemStateError::NoCharacter)
}
pub fn set_bank(&mut self, bank: BankState) {
self.proxied_state.character_bank.insert(bank.character_id, bank);
}
pub fn floor(&mut self, character_id: &CharacterEntityId) -> Result<FloorState, ItemStateError> {

9
src/ship/packet/builder/lobby.rs

@ -4,6 +4,7 @@ use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{ShipError, Clients};
use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError};
use crate::ship::packet::builder::{player_info};
use crate::ship::items::state::ItemState;
use crate::ship::items::ItemManager;
@ -11,14 +12,14 @@ pub fn join_lobby(id: ClientId,
lobby: LobbyId,
client_location: &ClientLocation,
clients: &Clients,
item_manager: &ItemManager,
item_state: &ItemState,
level_table: &CharacterLevelTable)
-> Result<JoinLobby, anyhow::Error> {
let lobby_clients = client_location.get_clients_in_lobby(lobby).map_err(|err| -> ClientLocationError { err.into() })?;
let playerinfo = lobby_clients.iter()
.map(|area_client| {
let client = clients.get(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client)).unwrap();
player_info(0x100, client, area_client, item_manager, level_table)
player_info(0x100, client, area_client, item_state, level_table)
});
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap();
@ -40,7 +41,7 @@ pub fn add_to_lobby(id: ClientId,
lobby: LobbyId,
client_location: &ClientLocation,
clients: &Clients,
item_manager: &ItemManager,
item_state: &ItemState,
level_table: &CharacterLevelTable)
-> Result<AddToLobby, ShipError> {
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap();
@ -55,7 +56,7 @@ pub fn add_to_lobby(id: ClientId,
block: client.block as u16,
event: 0,
padding: 0,
playerinfo: player_info(0x100, client, &area_client, item_manager, level_table),
playerinfo: player_info(0x100, client, &area_client, item_state, level_table),
})
}

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

@ -5,6 +5,8 @@ use crate::common::leveltable::CharacterStats;
use crate::ship::ship::{ShipError};
use crate::ship::items::{ClientItemId, InventoryItem, StackedFloorItem, FloorItem, CharacterBank};
use crate::ship::items::state::FloorItem as FloorItem2;
use crate::ship::items::state::InventoryItem as InventoryItem2;
use crate::ship::items::state::{BankState};
use crate::ship::location::AreaClient;
use std::convert::TryInto;
use crate::ship::shops::ShopItem;
@ -78,6 +80,18 @@ pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &Inventory
})
}
pub fn create_withdrawn_inventory_item2(area_client: AreaClient, item: &InventoryItem2) -> Result<CreateItem, ShipError> {
let bytes = item.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: &FloorItem2) -> Result<RemoveItemFromFloor, ShipError> {
Ok(RemoveItemFromFloor {
client: area_client.local_client.id(),
@ -147,7 +161,7 @@ pub fn character_leveled_up(area_client: AreaClient, level: u32, before_stats: C
}
// TOOD: checksum?
pub fn bank_item_list(bank: &CharacterBank, char_bank_meseta: u32) -> BankItemList {
pub fn bank_item_list(bank: &BankState) -> BankItemList {
BankItemList {
aflag: 0,
cmd: 0xBC,
@ -155,7 +169,7 @@ pub fn bank_item_list(bank: &CharacterBank, char_bank_meseta: u32) -> BankItemLi
size: bank.count() as u32 * 0x18 + 0x14,
checksum: 0x123434,
item_count: bank.count() as u32,
meseta: char_bank_meseta,
meseta: bank.meseta.0,
items: bank.as_client_bank_request()
}
}

9
src/ship/packet/builder/mod.rs

@ -10,7 +10,7 @@ use crate::common::leveltable::CharacterLevelTable;
use crate::ship::character::CharacterBytesBuilder;
use crate::ship::ship::ClientState;
use crate::ship::location::AreaClient;
use crate::ship::items::ItemManager;
use crate::ship::items::state::ItemState;
pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -> PlayerHeader {
PlayerHeader {
@ -23,15 +23,14 @@ pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -
}
}
pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_manager: &ItemManager, level_table: &CharacterLevelTable) -> PlayerInfo {
pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_state: &ItemState, level_table: &CharacterLevelTable) -> PlayerInfo {
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 meseta = item_manager.get_character_meseta(&client.character.id).unwrap();
let inventory = item_state.get_character_inventory(&client.character).unwrap();
let character = CharacterBytesBuilder::default()
.character(&client.character)
.stats(&stats)
.level(level - 1)
.meseta(*meseta)
.meseta(inventory.meseta)
.build();
PlayerInfo {
header: player_header(tag, client, area_client),

7
src/ship/packet/builder/room.rs

@ -4,6 +4,7 @@ use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{ShipError, ClientState, Clients};
use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationError};
use crate::ship::room::RoomState;
use crate::ship::items::state::ItemState;
use crate::ship::items::ItemManager;
use crate::ship::packet::builder::{player_header, player_info};
use std::convert::TryInto;
@ -53,7 +54,7 @@ pub fn add_to_room(_id: ClientId,
client: &ClientState,
area_client: &AreaClient,
leader: &AreaClient,
item_manager: &ItemManager,
item_state: &ItemState,
level_table: &CharacterLevelTable,
_room_id: RoomId,
)
@ -68,7 +69,7 @@ pub fn add_to_room(_id: ClientId,
block: 0,
event: 0,
padding: 0,
playerinfo: player_info(0x10000, client, area_client, item_manager, level_table),
playerinfo: player_info(0x10000, client, area_client, item_state, level_table),
})
}
@ -76,4 +77,4 @@ pub fn build_rare_monster_list(rare_monster_vec: Vec<u16>) -> RareMonsterList {
RareMonsterList {
ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]),
}
}
}

6
src/ship/packet/handler/auth.rs

@ -4,7 +4,7 @@ use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients};
use crate::login::login::get_login_status;
use crate::entity::gateway::EntityGateway;
use crate::ship::items::ItemManager;
use crate::ship::items::state::ItemState;
use crate::common::interserver::ShipMessage;
#[allow(clippy::too_many_arguments)]
@ -12,7 +12,7 @@ pub async fn validate_login<EG: EntityGateway>(id: ClientId,
pkt: &Login,
entity_gateway: &mut EG,
clients: &mut Clients,
item_manager: &mut ItemManager,
item_state: &mut ItemState,
shipgate_sender: &Option<Box<dyn Fn(ShipMessage) + Send + Sync>>,
ship_name: &str,
num_blocks: usize)
@ -30,7 +30,7 @@ pub async fn validate_login<EG: EntityGateway>(id: ClientId,
.clone();
let settings = entity_gateway.get_user_settings_by_user(&user).await?;
item_manager.load_character(entity_gateway, &character).await?;
item_state.load_character(entity_gateway, &character).await?;
if let Some(shipgate_sender) = shipgate_sender.as_ref() {
shipgate_sender(ShipMessage::AddUser(user.id));

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

@ -15,7 +15,7 @@ use libpso::utf8_to_utf16_array;
use crate::ship::packet::builder;
use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem};
use crate::ship::items::state::{ItemState, FloorType, FloorItemDetail};
use crate::ship::items::actions::{pick_up_item, TriggerCreateItem};
use crate::ship::items::actions::{pick_up_item, withdraw_meseta, deposit_meseta, withdraw_item, deposit_item, TriggerCreateItem};
const BANK_ACTION_DEPOSIT: u8 = 0;
const BANK_ACTION_WITHDRAW: u8 = 1;
@ -248,13 +248,12 @@ EG: EntityGateway
// 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)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
{
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
let bank_items = item_manager.get_character_bank(&client.character)?;
let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?;
let bank_items_pkt = builder::message::bank_item_list(bank_items, bank_meseta.0);
let bank = item_state.get_character_bank(&client.character)?;
let bank_items_pkt = builder::message::bank_item_list(bank);
Ok(Box::new(vec![(id, SendShipPacket::BankItemList(bank_items_pkt))].into_iter()))
}
@ -263,7 +262,7 @@ pub async fn bank_interaction<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -273,42 +272,24 @@ where
let other_clients_in_area = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
let bank_action_pkts = match bank_interaction.action {
BANK_ACTION_DEPOSIT => {
let character_meseta = item_manager.get_character_meseta(&client.character.id)?;
let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?;
if bank_interaction.item_id == 0xFFFFFFFF {
if character_meseta.0 >= bank_interaction.meseta_amount && (bank_interaction.meseta_amount + bank_meseta.0) <= BANK_MESETA_CAPACITY {
let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?;
character_meseta.0 -= bank_interaction.meseta_amount;
bank_meseta.0 += bank_interaction.meseta_amount;
entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?;
// TODO: BankName
entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?;
}
deposit_meseta(item_state, entity_gateway, &client.character, bank_interaction.meseta_amount).await?;
Vec::new()
}
else {
item_manager.player_deposits_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?;
deposit_item(item_state, entity_gateway, &client.character, &ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32).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 => {
let character_meseta = item_manager.get_character_meseta(&client.character.id)?;
let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?;
if bank_interaction.item_id == 0xFFFFFFFF {
if (bank_meseta.0 >= bank_interaction.meseta_amount) && (character_meseta.0 + bank_interaction.meseta_amount <= INVENTORY_MESETA_CAPACITY) {
let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?;
character_meseta.0 += bank_interaction.meseta_amount;
bank_meseta.0 -= bank_interaction.meseta_amount;
entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?;
// TODO: BankName
entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?;
}
withdraw_meseta(item_state, entity_gateway, &client.character, bank_interaction.meseta_amount).await?;
Vec::new()
}
else {
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)?;
let item_added_to_inventory = withdraw_item(item_state, entity_gateway, &client.character, &ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32).await?;
let item_created = builder::message::create_withdrawn_inventory_item2(area_client, &item_added_to_inventory)?;
vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(item_created)))]
}
},

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

@ -7,7 +7,6 @@ use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationEr
//use crate::ship::items::;
use crate::ship::packet;
use crate::ship::items::state::ItemState;
use crate::ship::items::ItemManager;
use crate::entity::gateway::EntityGateway;
// this function needs a better home
@ -52,12 +51,12 @@ pub fn send_player_to_lobby(id: ClientId,
_pkt: &CharData,
client_location: &mut ClientLocation,
clients: &Clients,
item_manager: &ItemManager,
item_state: &ItemState,
level_table: &CharacterLevelTable)
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).map_err(|_| ShipError::TooManyClients)?;
let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?;
let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?;
let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, level_table)?;
let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, level_table)?;
let neighbors = client_location.get_client_neighbors(id).unwrap();
Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))]
.into_iter()
@ -70,7 +69,7 @@ pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
requested_lobby: u32,
client_location: &mut ClientLocation,
clients: &Clients,
item_manager: &mut ItemManager,
item_state: &mut ItemState,
level_table: &CharacterLevelTable,
ship_rooms: &mut Rooms,
entity_gateway: &mut EG)
@ -87,7 +86,7 @@ pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
if client_location.get_client_neighbors(id)?.is_empty() {
ship_rooms[old_room.0] = None;
}
item_manager.remove_character_from_room(&client.character);
item_state.remove_character_from_room(&client.character);
},
}
let leave_lobby = packet::builder::lobby::remove_from_lobby(id, client_location)?;
@ -104,9 +103,9 @@ pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
}
}
}
item_manager.load_character(entity_gateway, &client.character).await?;
let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?;
let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?;
item_state.load_character(entity_gateway, &client.character).await?;
let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, level_table)?;
let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, level_table)?;
let neighbors = client_location.get_client_neighbors(id).unwrap();
Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))]
.into_iter()

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

@ -6,14 +6,14 @@ use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients};
use crate::ship::location::{ClientLocation, RoomId, RoomLobby, ClientLocationError};
use crate::ship::packet::builder;
use crate::ship::room;
use crate::ship::items::ItemManager;
use crate::ship::items::state::ItemState;
use std::convert::{TryFrom};
pub fn create_room(id: ClientId,
create_room: &CreateRoom,
client_location: &mut ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
item_state: &mut ItemState,
level_table: &CharacterLevelTable,
rooms: &mut Rooms)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
@ -45,7 +45,7 @@ pub fn create_room(id: ClientId,
let mut room = room::RoomState::from_create_room(create_room, client.character.section_id).unwrap();
room.bursting = true;
item_manager.add_character_to_room(room_id, &client.character, area_client);
item_state.add_character_to_room(room_id, &client.character, area_client);
let join_room = builder::room::join_room(id, clients, client_location, room_id, &room)?;
rooms[room_id.0] = Some(room);
@ -80,7 +80,7 @@ pub fn join_room(id: ClientId,
pkt: &MenuSelect,
client_location: &mut ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
item_state: &mut ItemState,
level_table: &CharacterLevelTable,
rooms: &mut Rooms)
-> Result<Box<dyn Iterator<Item=(ClientId, SendShipPacket)> + Send>, ShipError> {
@ -119,11 +119,11 @@ pub fn join_room(id: ClientId,
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
item_manager.add_character_to_room(room_id, &client.character, area_client);
item_state.add_character_to_room(room_id, &client.character, area_client);
let leader = client_location.get_room_leader(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
let join_room = builder::room::join_room(id, clients, client_location, room_id, room)?;
let add_to = builder::room::add_to_room(id, client, &area_client, &leader, item_manager, level_table, room_id)?;
let add_to = builder::room::add_to_room(id, client, &area_client, &leader, item_state, level_table, room_id)?;
let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap();
room.bursting = true;

18
src/ship/ship.rs

@ -555,10 +555,10 @@ impl<EG: EntityGateway> ShipServerState<EG> {
handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await?
},
GameMessage::BankRequest(_bank_request) => {
handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_manager).await?
handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_state).await?
},
GameMessage::BankInteraction(bank_interaction) => {
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await?
},
GameMessage::ShopRequest(shop_request) => {
handler::direct_message::shop_request(id, shop_request, &block.client_location, &mut self.clients, &block.rooms, &self.level_table, &mut self.shops).await?
@ -611,7 +611,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
Ok(match pkt {
RecvShipPacket::Login(login) => {
Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.shipgate_sender, &self.name, self.blocks.0.len())
Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_state, &self.shipgate_sender, &self.name, self.blocks.0.len())
.await?.into_iter().map(move |pkt| (id, pkt)))
},
RecvShipPacket::QuestDetailRequest(questdetailrequest) => {
@ -634,7 +634,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
let select_block = handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_state, &self.level_table)?.into_iter();
Box::new(leave_lobby.chain(select_block))
}
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?,
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)?,
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &mut block.rooms)?,
_ => unreachable!(),
}
@ -656,7 +656,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
menu: room_password_req.menu,
item: room_password_req.item,
};
handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?
handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)?
}
else {
Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))].into_iter())
@ -664,7 +664,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
RecvShipPacket::CharData(chardata) => {
let block = self.blocks.with_client(id, &self.clients)?;
Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_manager, &self.level_table)?.into_iter())
Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state, &self.level_table)?.into_iter())
},
RecvShipPacket::Message(msg) => {
self.message(id, msg).await?
@ -678,7 +678,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
RecvShipPacket::CreateRoom(create_room) => {
let block = self.blocks.with_client(id, &self.clients)?;
handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?
handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)?
},
RecvShipPacket::RoomNameRequest(_req) => {
let block = self.blocks.with_client(id, &self.clients)?;
@ -716,7 +716,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
RecvShipPacket::LobbySelect(pkt) => {
let block = self.blocks.with_client(id, &self.clients)?;
Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter())
Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter())
},
RecvShipPacket::RequestQuestList(rql) => {
let block = self.blocks.with_client(id, &self.clients)?;
@ -788,7 +788,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
}
block.client_location.remove_client_from_area(id);
self.item_manager.remove_character_from_room(&client.character);
self.item_state.remove_character_from_room(&client.character);
if let Some(mut client) = self.clients.remove(&id) {
client.user.at_ship = false;

28
tests/test_bank.rs

@ -854,7 +854,7 @@ async fn test_deposit_too_much_meseta() {
unknown: 0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -862,7 +862,9 @@ async fn test_deposit_too_much_meseta() {
item_amount: 0,
meseta_amount: 23,
unknown: 0,
})))).await.unwrap().for_each(drop);
})))).await;
assert!(packets.is_err());
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap();
@ -891,7 +893,7 @@ async fn test_deposit_meseta_when_bank_is_maxed() {
unknown: 0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -899,7 +901,9 @@ async fn test_deposit_meseta_when_bank_is_maxed() {
item_amount: 0,
meseta_amount: 23,
unknown: 0,
})))).await.unwrap().for_each(drop);
})))).await;
assert!(packets.is_err());
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap();
@ -1020,7 +1024,7 @@ async fn test_withdraw_stacked_item() {
assert!(packets.len() == 2);
assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)}))
if create_item.item_id == 0x10002
if create_item.item_id == 0x20000
));
let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
@ -1157,7 +1161,7 @@ async fn test_withdraw_stacked_item_with_stack_already_in_inventory() {
assert!(packets.len() == 2);
assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)}))
if create_item.item_id == 0x10000
if create_item.item_id == 0x20000
));
let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap();
@ -1534,7 +1538,7 @@ async fn test_withdraw_too_much_meseta() {
unknown: 0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
let packet = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -1542,7 +1546,9 @@ async fn test_withdraw_too_much_meseta() {
item_amount: 0,
meseta_amount: 23,
unknown: 0,
})))).await.unwrap().for_each(drop);
})))).await;
assert!(packet.is_err());
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap();
@ -1571,7 +1577,7 @@ async fn test_withdraw_meseta_inventory_is_maxed() {
unknown: 0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
let packet = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction {
client: 0,
target: 0,
item_id: 0xFFFFFFFF,
@ -1579,7 +1585,9 @@ async fn test_withdraw_meseta_inventory_is_maxed() {
item_amount: 0,
meseta_amount: 23,
unknown: 0,
})))).await.unwrap().for_each(drop);
})))).await;
assert!(packet.is_err());
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap();

Loading…
Cancel
Save