1462 lines
74 KiB
Rust
1462 lines
74 KiB
Rust
use crate::ship::items::ClientItemId;
|
|
use std::collections::HashMap;
|
|
use std::cmp::Ordering;
|
|
use std::cell::RefCell;
|
|
use thiserror::Error;
|
|
use crate::entity::gateway::{EntityGateway, GatewayError};
|
|
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
|
|
use crate::entity::item::{ItemDetail, ItemNote, BankName};
|
|
use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity, EquippedEntity, ItemEntityId};
|
|
use crate::entity::item::tool::{Tool, ToolType};
|
|
use crate::entity::item::weapon::{Weapon, WeaponModifier};
|
|
use crate::entity::item::unit::UnitModifier;
|
|
use crate::ship::map::MapArea;
|
|
use crate::ship::ship::ItemDropLocation;
|
|
use crate::ship::trade::TradeItem;
|
|
use crate::ship::drops::{ItemDrop, ItemDropType};
|
|
use crate::ship::location::{AreaClient, RoomId};
|
|
use crate::ship::shops::ShopItem;
|
|
use crate::ship::packet::handler::trade::{TradeError, OTHER_MESETA_ITEM_ID};
|
|
|
|
use crate::ship::items::bank::*;
|
|
use crate::ship::items::floor::*;
|
|
use crate::ship::items::inventory::*;
|
|
use crate::ship::items::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError};
|
|
use crate::ship::monster::MonsterType;
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
pub enum FloorType {
|
|
Local,
|
|
Shared,
|
|
}
|
|
|
|
pub enum TriggerCreateItem {
|
|
Yes,
|
|
No
|
|
}
|
|
|
|
#[derive(Error, Debug)]
|
|
#[error("itemmanager")]
|
|
pub enum ItemManagerError {
|
|
#[error("gateway")]
|
|
EntityGatewayError,
|
|
#[error("no such item id {0}")]
|
|
NoSuchItemId(ClientItemId),
|
|
NoCharacter(CharacterEntityId),
|
|
NoRoom(RoomId),
|
|
CouldNotAddToInventory(ClientItemId),
|
|
//ItemBelongsToOtherPlayer,
|
|
#[error("shrug")]
|
|
Idunnoman,
|
|
CouldNotSplitItem(ClientItemId),
|
|
#[error("could not drop meseta")]
|
|
CouldNotDropMeseta,
|
|
InvalidBankName(BankName),
|
|
#[error("not enough tools")]
|
|
NotEnoughTools(Tool, usize, usize), // have, expected
|
|
InventoryItemConsumeError(#[from] InventoryItemConsumeError),
|
|
#[error("bank full")]
|
|
BankFull,
|
|
WrongItemType(ClientItemId),
|
|
UseItemError(#[from] use_tool::UseItemError),
|
|
#[error("could not buy item")]
|
|
CouldNotBuyItem,
|
|
#[error("could not add bought item to inventory")]
|
|
CouldNotAddBoughtItemToInventory,
|
|
ItemIdNotInInventory(ClientItemId),
|
|
#[error("cannot get mut item")]
|
|
CannotGetMutItem,
|
|
#[error("cannot get individual item")]
|
|
CannotGetIndividualItem,
|
|
InvalidSlot(u8, u8), // slots available, slot attempted
|
|
#[error("no armor equipped")]
|
|
NoArmorEquipped,
|
|
GatewayError(#[from] GatewayError),
|
|
#[error("stacked item")]
|
|
StackedItemError(Vec<ItemEntity>),
|
|
#[error("item not sellable")]
|
|
ItemNotSellable(InventoryItem),
|
|
#[error("wallet full")]
|
|
WalletFull,
|
|
#[error("invalid sale")]
|
|
InvalidSale,
|
|
ItemTransactionAction(Box<dyn std::error::Error + Send + Sync>),
|
|
#[error("invalid trade")]
|
|
InvalidTrade,
|
|
EntityIdNotInInventory(ItemEntityId),
|
|
WeaponCannotCombine,
|
|
NotEnoughKills(u16),
|
|
}
|
|
|
|
impl<E> std::convert::From<TransactionError<E>> for ItemManagerError
|
|
where
|
|
E: std::fmt::Debug + std::marker::Send + std::marker::Sync + std::error::Error + 'static,
|
|
{
|
|
fn from(other: TransactionError<E>) -> ItemManagerError {
|
|
match other {
|
|
TransactionError::Action(err) => {
|
|
ItemManagerError::ItemTransactionAction(Box::new(err))
|
|
},
|
|
TransactionError::Commit(err) => {
|
|
match err {
|
|
TransactionCommitError::Gateway(gw) => {
|
|
ItemManagerError::GatewayError(gw)
|
|
},
|
|
TransactionCommitError::ItemManager(im) => {
|
|
im
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub struct ItemManager {
|
|
pub(super) id_counter: u32,
|
|
|
|
pub(self) character_inventory: HashMap<CharacterEntityId, CharacterInventory>,
|
|
pub(self) character_meseta: HashMap<CharacterEntityId, Meseta>,
|
|
pub(self) bank_meseta: HashMap<CharacterEntityId, Meseta>,
|
|
//character_bank: HashMap<CharacterEntityId, BTreeMap<BankName, CharacterBank>>,
|
|
pub(self) character_bank: HashMap<CharacterEntityId, CharacterBank>,
|
|
pub(self) character_floor: HashMap<CharacterEntityId, RoomFloorItems>,
|
|
|
|
pub(self) character_room: HashMap<CharacterEntityId, RoomId>,
|
|
pub(self) room_floor: HashMap<RoomId, RoomFloorItems>,
|
|
pub(self) room_item_id_counter: RefCell<HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>>,
|
|
}
|
|
|
|
impl Default for ItemManager {
|
|
fn default() -> ItemManager {
|
|
ItemManager {
|
|
id_counter: 0,
|
|
character_inventory: HashMap::new(),
|
|
character_meseta: HashMap::new(),
|
|
bank_meseta: HashMap::new(),
|
|
character_bank: HashMap::new(),
|
|
character_floor: HashMap::new(),
|
|
character_room: HashMap::new(),
|
|
room_floor: HashMap::new(),
|
|
room_item_id_counter: RefCell::new(HashMap::new()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ItemManager {
|
|
pub fn next_global_item_id(&mut self) -> ClientItemId {
|
|
self.id_counter += 1;
|
|
ClientItemId(self.id_counter)
|
|
}
|
|
|
|
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> {
|
|
let inventory = entity_gateway.get_character_inventory(&character.id).await?;
|
|
let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?;
|
|
let equipped = entity_gateway.get_character_equips(&character.id).await?;
|
|
|
|
let inventory_items = inventory.items.into_iter()
|
|
.map(|item| -> Result<InventoryItem, ItemManagerError> {
|
|
Ok(match item {
|
|
InventoryItemEntity::Individual(item) => {
|
|
InventoryItem::Individual(IndividualInventoryItem {
|
|
entity_id: item.id,
|
|
item_id: self.next_global_item_id(),
|
|
item: item.item,
|
|
})
|
|
},
|
|
InventoryItemEntity::Stacked(items) => {
|
|
InventoryItem::Stacked(StackedInventoryItem {
|
|
entity_ids: items.iter().map(|i| i.id).collect(),
|
|
item_id: self.next_global_item_id(),
|
|
tool: items.get(0)
|
|
.ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))?
|
|
.item
|
|
.clone()
|
|
.as_tool()
|
|
.ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))?
|
|
})
|
|
},
|
|
})
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
let character_inventory = CharacterInventory::new(inventory_items, &equipped);
|
|
|
|
let bank_items = bank.items.into_iter()
|
|
.map(|item| -> Result<BankItem, ItemManagerError> {
|
|
Ok(match item {
|
|
BankItemEntity::Individual(item) => {
|
|
BankItem::Individual(IndividualBankItem {
|
|
entity_id: item.id,
|
|
item_id: self.next_global_item_id(),
|
|
item: item.item,
|
|
})
|
|
},
|
|
BankItemEntity::Stacked(items) => {
|
|
BankItem::Stacked(StackedBankItem {
|
|
entity_ids: items.iter().map(|i| i.id).collect(),
|
|
item_id: self.next_global_item_id(),
|
|
tool: items.get(0)
|
|
.ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))?
|
|
.item
|
|
.clone()
|
|
.as_tool()
|
|
.ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))?
|
|
})
|
|
},
|
|
})
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
let character_bank = CharacterBank::new(bank_items);
|
|
|
|
let character_meseta = entity_gateway.get_character_meseta(&character.id).await?;
|
|
let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?;
|
|
|
|
self.character_inventory.insert(character.id, character_inventory);
|
|
self.character_bank.insert(character.id, character_bank);
|
|
self.character_meseta.insert(character.id, character_meseta);
|
|
self.bank_meseta.insert(character.id, bank_meseta);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) {
|
|
let base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000;
|
|
let inventory = self.character_inventory.get_mut(&character.id).unwrap();
|
|
inventory.initialize_item_ids(base_inventory_id);
|
|
let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000;
|
|
let default_bank = self.character_bank.get_mut(&character.id);
|
|
if let Some(default_bank ) = default_bank {
|
|
default_bank.initialize_item_ids(base_bank_id);
|
|
}
|
|
self.character_room.insert(character.id, room_id);
|
|
self.character_floor.insert(character.id, RoomFloorItems::default());
|
|
self.room_floor.entry(room_id).or_insert_with(RoomFloorItems::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 get_character_inventory(&self, character: &CharacterEntity) -> Result<&CharacterInventory, anyhow::Error> {
|
|
Ok(self.character_inventory.get(&character.id)
|
|
.ok_or(ItemManagerError::NoCharacter(character.id))?)
|
|
}
|
|
|
|
pub fn get_character_inventory_mut<'a>(&'a mut self, character: &CharacterEntity) -> Result<&'a mut CharacterInventory, anyhow::Error> {
|
|
Ok(self.character_inventory.get_mut(&character.id)
|
|
.ok_or(ItemManagerError::NoCharacter(character.id))?)
|
|
}
|
|
|
|
pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&CharacterBank, anyhow::Error> {
|
|
self.character_bank
|
|
.get(&character.id)
|
|
.ok_or_else(|| ItemManagerError::NoCharacter(character.id).into())
|
|
}
|
|
|
|
pub fn get_character_meseta(&self, character_id: &CharacterEntityId) -> Result<&Meseta, ItemManagerError> {
|
|
self.character_meseta.get(character_id)
|
|
.ok_or(ItemManagerError::NoCharacter(*character_id))
|
|
}
|
|
|
|
pub fn get_character_meseta_mut<'a>(&'a mut self, character_id: &CharacterEntityId) -> Result<&'a mut Meseta, ItemManagerError> {
|
|
self.character_meseta.get_mut(character_id)
|
|
.ok_or(ItemManagerError::NoCharacter(*character_id))
|
|
}
|
|
|
|
pub fn get_bank_meseta(&self, character_id: &CharacterEntityId) -> Result<&Meseta, ItemManagerError> {
|
|
self.bank_meseta.get(character_id)
|
|
.ok_or(ItemManagerError::NoCharacter(*character_id))
|
|
}
|
|
|
|
pub fn get_bank_meseta_mut<'a>(&'a mut self, character_id: &CharacterEntityId) -> Result<&'a mut Meseta, ItemManagerError> {
|
|
self.bank_meseta.get_mut(character_id)
|
|
.ok_or(ItemManagerError::NoCharacter(*character_id))
|
|
}
|
|
|
|
pub fn get_character_and_bank_meseta_mut<'a>(&'a mut self, character_id: &CharacterEntityId) -> Result<(&'a mut Meseta, &'a mut Meseta), ItemManagerError> {
|
|
Ok((
|
|
self.character_meseta.get_mut(character_id)
|
|
.ok_or(ItemManagerError::NoCharacter(*character_id))?,
|
|
self.bank_meseta.get_mut(character_id)
|
|
.ok_or(ItemManagerError::NoCharacter(*character_id))?
|
|
))
|
|
}
|
|
|
|
pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
|
|
self.character_inventory.remove(&character.id);
|
|
self.character_floor.remove(&character.id);
|
|
if let Some(room) = self.character_room.remove(&character.id).as_ref() {
|
|
if self.character_room.iter().any(|(_, r)| r == room) {
|
|
self.room_floor.remove(room);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_floor_item_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result<(&FloorItem, FloorType), anyhow::Error> {
|
|
let local_floor = self.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let room = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let shared_floor = self.room_floor.get(room).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
local_floor.get_item_by_id(item_id).map(|item| (item, FloorType::Local))
|
|
.or_else(|| {
|
|
shared_floor.get_item_by_id(item_id).map(|item| (item, FloorType::Shared))
|
|
})
|
|
.ok_or_else(|| ItemManagerError::NoSuchItemId(item_id).into())
|
|
}
|
|
|
|
pub async fn character_picks_up_item<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId)
|
|
-> Result<TriggerCreateItem, anyhow::Error> {
|
|
let it = ItemTransaction::new(self, (character, item_id))
|
|
.act(|it, (character, item_id)| -> Result<_, ItemManagerError> {
|
|
let local_floor = it.manager.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let inventory = it.manager.character_inventory.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let room_id = it.manager.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let shared_floor = it.manager.room_floor.get(room_id).ok_or(ItemManagerError::NoRoom(*room_id))?;
|
|
|
|
let floor_item = match local_floor.get_item_by_id(*item_id) {
|
|
Some(floor_item) => {
|
|
it.action(Box::new(RemoveFromLocalFloor {
|
|
character_id: character.id,
|
|
item_id: *item_id
|
|
}));
|
|
floor_item
|
|
},
|
|
None => {
|
|
match shared_floor.get_item_by_id(*item_id) {
|
|
Some(floor_item) => {
|
|
it.action(Box::new(RemoveFromSharedFloor {
|
|
room_id: *room_id,
|
|
item_id: *item_id
|
|
}));
|
|
floor_item
|
|
},
|
|
None => {
|
|
return Err(ItemManagerError::NoSuchItemId(*item_id))
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
let create_trigger = match floor_item {
|
|
FloorItem::Individual(individual_floor_item) => {
|
|
if inventory.space_for_individual_item() {
|
|
it.action(Box::new(AddIndividualFloorItemToInventory {
|
|
character: (**character).clone(),
|
|
item: individual_floor_item.clone()
|
|
}))
|
|
}
|
|
else {
|
|
return Err(ItemManagerError::CouldNotAddToInventory(*item_id));
|
|
}
|
|
TriggerCreateItem::Yes
|
|
},
|
|
FloorItem::Stacked(stacked_floor_item) => {
|
|
match inventory.space_for_stacked_item(&stacked_floor_item.tool, stacked_floor_item.entity_ids.len()) {
|
|
SpaceForStack::Yes(YesThereIsSpace::NewStack) => {
|
|
it.action(Box::new(AddStackedFloorItemToInventory {
|
|
character_id: character.id,
|
|
item: stacked_floor_item.clone()
|
|
}));
|
|
TriggerCreateItem::Yes
|
|
},
|
|
SpaceForStack::Yes(YesThereIsSpace::ExistingStack) => {
|
|
it.action(Box::new(AddStackedFloorItemToInventory {
|
|
character_id: character.id,
|
|
item: stacked_floor_item.clone()
|
|
}));
|
|
TriggerCreateItem::No
|
|
},
|
|
SpaceForStack::No(_) => {
|
|
return Err(ItemManagerError::CouldNotAddToInventory(*item_id));
|
|
},
|
|
}
|
|
},
|
|
FloorItem::Meseta(meseta_floor_item) => {
|
|
let character_meseta = it.manager.character_meseta.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
if character_meseta.0 >= 999999 {
|
|
return Err(ItemManagerError::CouldNotAddToInventory(*item_id));
|
|
}
|
|
it.action(Box::new(AddMesetaFloorItemToInventory {
|
|
character_id: character.id,
|
|
item: meseta_floor_item.clone()
|
|
}));
|
|
|
|
TriggerCreateItem::No
|
|
},
|
|
};
|
|
Ok(create_trigger)
|
|
});
|
|
it.commit(self, entity_gateway)
|
|
.await
|
|
.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: &'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 {
|
|
Individual(ItemDetail),
|
|
Stacked(Tool),
|
|
Meseta(Meseta)
|
|
}
|
|
|
|
let item = match item_drop.item {
|
|
ItemDropType::Weapon(w) => ItemOrMeseta::Individual(ItemDetail::Weapon(w)),
|
|
ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)),
|
|
ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)),
|
|
ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)),
|
|
ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)),
|
|
ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)),
|
|
//ItemDropType::IndividualTool(t) => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
|
|
//ItemDropType::StackedTool(t, _) => ItemOrMeseta::Stacked(t),
|
|
ItemDropType::Tool(t) if t.tool.is_stackable() => ItemOrMeseta::Stacked(t),
|
|
ItemDropType::Tool(t) if !t.tool.is_stackable() => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
|
|
ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)),
|
|
_ => unreachable!() // rust isnt smart enough to see that the conditional on tool catches everything
|
|
};
|
|
|
|
let item_id = self.room_item_id_counter.borrow_mut().get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
|
|
let floor_item = match item {
|
|
ItemOrMeseta::Individual(item_detail) => {
|
|
let entity = entity_gateway.create_item(NewItemEntity {
|
|
item: item_detail.clone(),
|
|
}).await?;
|
|
entity_gateway.add_item_note(&entity.id, ItemNote::EnemyDrop {
|
|
character_id: character.id,
|
|
map_area: item_drop.map_area,
|
|
x: item_drop.x,
|
|
y: item_drop.y,
|
|
z: item_drop.z,
|
|
}).await?;
|
|
FloorItem::Individual(IndividualFloorItem {
|
|
entity_id: entity.id,
|
|
item_id,
|
|
item: item_detail,
|
|
map_area: item_drop.map_area,
|
|
x: item_drop.x,
|
|
y: item_drop.y,
|
|
z: item_drop.z,
|
|
})
|
|
},
|
|
ItemOrMeseta::Stacked(tool) => {
|
|
let entity = entity_gateway.create_item(NewItemEntity {
|
|
item: ItemDetail::Tool(tool),
|
|
}).await?;
|
|
entity_gateway.add_item_note(&entity.id, ItemNote::EnemyDrop {
|
|
character_id: character.id,
|
|
map_area: item_drop.map_area,
|
|
x: item_drop.x,
|
|
y: item_drop.y,
|
|
z: item_drop.z,
|
|
}).await?;
|
|
FloorItem::Stacked(StackedFloorItem {
|
|
entity_ids: vec![entity.id],
|
|
item_id,
|
|
tool,
|
|
map_area: item_drop.map_area,
|
|
x: item_drop.x,
|
|
y: item_drop.y,
|
|
z: item_drop.z,
|
|
})
|
|
},
|
|
ItemOrMeseta::Meseta(meseta) => {
|
|
FloorItem::Meseta(MesetaFloorItem {
|
|
item_id,
|
|
meseta,
|
|
map_area: item_drop.map_area,
|
|
x: item_drop.x,
|
|
y: item_drop.y,
|
|
z: item_drop.z,
|
|
})
|
|
},
|
|
};
|
|
|
|
self.character_floor.entry(character.id).or_insert_with(RoomFloorItems::default).add_item(floor_item);
|
|
// TODO: make these real errors
|
|
self.character_floor.get(&character.id).ok_or(ItemManagerError::Idunnoman)?.get_item_by_id(item_id).ok_or_else(|| ItemManagerError::Idunnoman.into())
|
|
}
|
|
|
|
pub async fn player_drop_item_on_shared_floor<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
//inventory_item: InventoryItem,
|
|
item_id: ClientItemId,
|
|
item_drop_location: (MapArea, f32, f32, f32))
|
|
-> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let shared_floor = self.room_floor.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
let dropped_inventory_item = inventory.take_item_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
|
|
match dropped_inventory_item {
|
|
InventoryItem::Individual(individual_inventory_item) => {
|
|
let individual_floor_item = shared_floor.drop_individual_inventory_item(individual_inventory_item, item_drop_location);
|
|
entity_gateway.add_item_note(
|
|
&individual_floor_item.entity_id,
|
|
ItemNote::PlayerDrop {
|
|
character_id: character.id,
|
|
map_area: item_drop_location.0,
|
|
x: item_drop_location.1,
|
|
y: item_drop_location.2,
|
|
z: item_drop_location.3,
|
|
}
|
|
).await?;
|
|
},
|
|
InventoryItem::Stacked(stacked_inventory_item) => {
|
|
let stacked_floor_item = shared_floor.drop_stacked_inventory_item(stacked_inventory_item, item_drop_location);
|
|
for entity_id in &stacked_floor_item.entity_ids {
|
|
entity_gateway.add_item_note(
|
|
entity_id,
|
|
ItemNote::PlayerDrop {
|
|
character_id: character.id,
|
|
map_area: item_drop_location.0,
|
|
x: item_drop_location.1,
|
|
y: item_drop_location.2,
|
|
z: item_drop_location.3,
|
|
}
|
|
).await?;
|
|
}
|
|
},
|
|
}
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn player_drops_meseta_on_shared_floor<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &mut CharacterEntity,
|
|
drop_location: ItemDropLocation,
|
|
amount: u32)
|
|
-> Result<FloorItem, anyhow::Error> {
|
|
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let shared_floor = self.room_floor.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let character_meseta = self.character_meseta.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
if character_meseta.0 < amount {
|
|
return Err(ItemManagerError::CouldNotDropMeseta.into())
|
|
}
|
|
character_meseta.0 -= amount;
|
|
entity_gateway.set_character_meseta(&character.id, *character_meseta).await?;
|
|
|
|
let item_id = self.room_item_id_counter.borrow_mut().get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
|
|
let floor_item = FloorItem::Meseta(MesetaFloorItem {
|
|
item_id,
|
|
meseta: Meseta(amount),
|
|
map_area: drop_location.map_area,
|
|
x: drop_location.x,
|
|
y: 0.0,
|
|
z: drop_location.z,
|
|
});
|
|
|
|
shared_floor.add_item(floor_item.clone());
|
|
Ok(floor_item)
|
|
}
|
|
|
|
pub async fn player_drops_partial_stack_on_shared_floor<'a, EG: EntityGateway>(&'a mut self,
|
|
entity_gateway: &'a mut EG,
|
|
character: &'a CharacterEntity,
|
|
//inventory_item: InventoryItem,
|
|
item_id: ClientItemId,
|
|
drop_location: ItemDropLocation,
|
|
amount: usize)
|
|
-> Result<&'a StackedFloorItem, anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let shared_floor = self.room_floor.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
let item_to_split = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
|
|
let new_item_id = self.room_item_id_counter.borrow_mut().get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
|
|
let stacked_floor_item = shared_floor.drop_partial_stacked_inventory_item(item_to_split, amount, new_item_id, (drop_location.map_area, drop_location.x, 0.0, drop_location.z))
|
|
.ok_or(ItemManagerError::CouldNotSplitItem(item_id))?;
|
|
|
|
for entity_id in &stacked_floor_item.entity_ids {
|
|
entity_gateway.add_item_note(
|
|
entity_id,
|
|
ItemNote::PlayerDrop {
|
|
character_id: character.id,
|
|
map_area: drop_location.map_area,
|
|
x: drop_location.x,
|
|
y: 0.0,
|
|
z: drop_location.z,
|
|
}
|
|
).await?;
|
|
}
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(stacked_floor_item)
|
|
}
|
|
|
|
pub async fn player_consumes_tool<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &mut CharacterEntity,
|
|
item_id: ClientItemId,
|
|
amount: usize)
|
|
-> Result<ConsumedItem, anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let used_item = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
let consumed_item = used_item.consume(amount)?;
|
|
|
|
if let ItemDetail::TechniqueDisk(tech_disk) = consumed_item.item() {
|
|
// TODO: validate tech level in packet is in bounds [1..30]
|
|
character.techs.set_tech(tech_disk.tech, TechLevel(tech_disk.level as u8));
|
|
entity_gateway.save_character(character).await?;
|
|
};
|
|
|
|
for entity_id in consumed_item.entity_ids() {
|
|
entity_gateway.add_item_note(&entity_id,
|
|
ItemNote::Consumed).await?;
|
|
}
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(consumed_item)
|
|
}
|
|
|
|
pub async fn player_deposits_item<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
item_id: ClientItemId,
|
|
amount: usize)
|
|
-> Result<(), anyhow::Error> {
|
|
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)?;
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), &BankName("".into())).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn player_withdraws_item<'a, EG: EntityGateway>(&'a mut self,
|
|
entity_gateway: &'a mut EG,
|
|
character: &'a CharacterEntity,
|
|
item_id: ClientItemId,
|
|
amount: usize)
|
|
-> Result<&'a InventoryItem, anyhow::Error> {
|
|
|
|
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_slot = {
|
|
let inventory_item = inventory.withdraw_item(item_to_withdraw, amount).ok_or(ItemManagerError::Idunnoman)?;
|
|
inventory_item.1
|
|
};
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), &BankName("".into())).await?;
|
|
inventory.slot(inventory_item_slot).ok_or_else(|| ItemManagerError::Idunnoman.into())
|
|
}
|
|
|
|
pub async fn player_feeds_mag_item<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
mag_id: ClientItemId,
|
|
tool_id: ClientItemId)
|
|
-> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let consumed_tool = {
|
|
let item_to_feed = inventory.get_item_handle_by_id(tool_id).ok_or(ItemManagerError::NoSuchItemId(tool_id))?;
|
|
item_to_feed.consume(1)?
|
|
};
|
|
let mut mag_handle = inventory.get_item_handle_by_id(mag_id).ok_or(ItemManagerError::NoSuchItemId(mag_id))?;
|
|
|
|
let individual_item = mag_handle.item_mut()
|
|
.ok_or(ItemManagerError::NoSuchItemId(mag_id))?
|
|
.individual_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(mag_id))?;
|
|
let mag = individual_item
|
|
.mag_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(mag_id))?;
|
|
|
|
let consumed_tool_type = match &consumed_tool {
|
|
ConsumedItem::Stacked(stacked_consumed_item) => stacked_consumed_item.tool.tool,
|
|
_ => return Err(ItemManagerError::WrongItemType(tool_id).into())
|
|
};
|
|
mag.feed(consumed_tool_type);
|
|
|
|
for entity_id in consumed_tool.entity_ids() {
|
|
entity_gateway.feed_mag(&individual_item.entity_id, &entity_id).await?;
|
|
entity_gateway.add_item_note(&entity_id, ItemNote::FedToMag {
|
|
mag: individual_item.entity_id,
|
|
}).await?;
|
|
}
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn use_item<EG: EntityGateway>(&mut self,
|
|
used_item: ConsumedItem,
|
|
entity_gateway: &mut EG,
|
|
character: &mut CharacterEntity) -> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
match &used_item.item() {
|
|
ItemDetail::Weapon(_w) => {
|
|
// something like when items are used to combine/transform them?
|
|
},
|
|
ItemDetail::Tool(t) => {
|
|
match t.tool {
|
|
ToolType::PowerMaterial => {
|
|
use_tool::power_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::MindMaterial => {
|
|
use_tool::mind_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::EvadeMaterial => {
|
|
use_tool::evade_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::DefMaterial => {
|
|
use_tool::def_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::LuckMaterial => {
|
|
use_tool::luck_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::HpMaterial => {
|
|
use_tool::hp_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::TpMaterial => {
|
|
use_tool::tp_material(entity_gateway, character).await;
|
|
},
|
|
ToolType::CellOfMag502 => {
|
|
use_tool::cell_of_mag_502(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::CellOfMag213 => {
|
|
use_tool::cell_of_mag_213(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::PartsOfRobochao => {
|
|
use_tool::parts_of_robochao(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::HeartOfOpaOpa => {
|
|
use_tool::heart_of_opaopa(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::HeartOfPian => {
|
|
use_tool::heart_of_pian(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::HeartOfChao=> {
|
|
use_tool::heart_of_chao(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::HeartOfAngel => {
|
|
use_tool::heart_of_angel(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::KitOfHamburger => {
|
|
use_tool::kit_of_hamburger(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::PanthersSpirit => {
|
|
use_tool::panthers_spirit(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::KitOfMark3 => {
|
|
use_tool::kit_of_mark3(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::KitOfMasterSystem=> {
|
|
use_tool::kit_of_master_system(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::KitOfGenesis => {
|
|
use_tool::kit_of_genesis(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::KitOfSegaSaturn => {
|
|
use_tool::kit_of_sega_saturn(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::KitOfDreamcast => {
|
|
use_tool::kit_of_dreamcast(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::Tablet => {
|
|
use_tool::tablet(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::DragonScale => {
|
|
use_tool::dragon_scale(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::HeavenStrikerCoat => {
|
|
use_tool::heaven_striker_coat(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::PioneerParts => {
|
|
use_tool::pioneer_parts(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::AmitiesMemo => {
|
|
use_tool::amities_memo(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::HeartOfMorolian => {
|
|
use_tool::heart_of_morolian(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::RappysBeak => {
|
|
use_tool::rappys_beak(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::YahoosEngine => {
|
|
use_tool::yahoos_engine(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::DPhotonCore => {
|
|
use_tool::d_photon_core(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
ToolType::LibertaKit => {
|
|
use_tool::liberta_kit(entity_gateway, &used_item, inventory).await?;
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn player_buys_item<'a, EG: EntityGateway>(&'a mut self,
|
|
entity_gateway: &'a mut EG,
|
|
character: &'a CharacterEntity,
|
|
shop_item: &'a (dyn ShopItem + Send + Sync),
|
|
item_id: ClientItemId,
|
|
amount: usize)
|
|
-> Result<&'a InventoryItem, anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
let item_detail = shop_item.as_item();
|
|
let inventory_item = match item_detail {
|
|
ItemDetail::Tool(tool) => {
|
|
if tool.is_stackable() {
|
|
let mut item_entities = Vec::new();
|
|
for _ in 0..amount {
|
|
let item_entity = entity_gateway.create_item(NewItemEntity {
|
|
item: ItemDetail::Tool(tool),
|
|
}).await?;
|
|
entity_gateway.add_item_note(&item_entity.id, ItemNote::BoughtAtShop {
|
|
character_id: character.id,
|
|
}).await?;
|
|
item_entities.push(item_entity);
|
|
}
|
|
let floor_item = StackedFloorItem {
|
|
entity_ids: item_entities.into_iter().map(|i| i.id).collect(),
|
|
item_id,
|
|
tool,
|
|
// TODO: this is gonna choke if I ever require the item being near the player for pickup
|
|
map_area: MapArea::Pioneer2Ep1,
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
};
|
|
let item_id = {
|
|
let (picked_up_item, _slot) = inventory.pick_up_stacked_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?;
|
|
picked_up_item.item_id
|
|
};
|
|
inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))?
|
|
}
|
|
else {
|
|
let item_entity = entity_gateway.create_item(NewItemEntity {
|
|
item: ItemDetail::Tool(tool),
|
|
}).await?;
|
|
entity_gateway.add_item_note(&item_entity.id, ItemNote::BoughtAtShop {
|
|
character_id: character.id,
|
|
}).await?;
|
|
|
|
let floor_item = IndividualFloorItem {
|
|
entity_id: item_entity.id,
|
|
item_id,
|
|
item: ItemDetail::Tool(tool),
|
|
// TODO: this is gonna choke if I ever require the item being near the player for pickup
|
|
map_area: MapArea::Pioneer2Ep1,
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
};
|
|
let item_id = {
|
|
let (picked_up_item, _slot) = inventory.pick_up_individual_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?;
|
|
picked_up_item.item_id
|
|
};
|
|
inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))?
|
|
}
|
|
},
|
|
item_detail => {
|
|
let item_entity = entity_gateway.create_item(NewItemEntity {
|
|
item: item_detail.clone(),
|
|
}).await?;
|
|
entity_gateway.add_item_note(&item_entity.id, ItemNote::BoughtAtShop {
|
|
character_id: character.id,
|
|
}).await?;
|
|
let floor_item = IndividualFloorItem {
|
|
entity_id: item_entity.id,
|
|
item_id,
|
|
item: item_detail,
|
|
// TODO: this is gonna choke if I ever require the item being near the player for pickup
|
|
map_area: MapArea::Pioneer2Ep1,
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
};
|
|
let item_id = {
|
|
let (picked_up_item, _slot) = inventory.pick_up_individual_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?;
|
|
picked_up_item.item_id
|
|
};
|
|
inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))?
|
|
},
|
|
};
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(inventory_item)
|
|
}
|
|
|
|
pub async fn player_sells_item<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &mut CharacterEntity,
|
|
item_id: ClientItemId,
|
|
amount: usize)
|
|
-> Result<(), anyhow::Error> {
|
|
let character_meseta = self.get_character_meseta(&character.id)?.0;
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let sold_item_handle = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
if let Some(item_sold) = sold_item_handle.item() {
|
|
let unit_price = item_sold.get_sell_price()?; {
|
|
let total_sale = unit_price * amount as u32;
|
|
if character_meseta + total_sale <= 999999 {
|
|
match item_sold {
|
|
InventoryItem::Individual(i) => {
|
|
entity_gateway.add_item_note(&i.entity_id, ItemNote::SoldToShop).await?;
|
|
inventory.remove_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
},
|
|
InventoryItem::Stacked(s) => {
|
|
match amount.cmp(&s.count()) {
|
|
Ordering::Less | Ordering::Equal => {
|
|
sold_item_handle.consume(amount)?;
|
|
},
|
|
Ordering::Greater => return Err(ItemManagerError::InvalidSale.into()),
|
|
};
|
|
},
|
|
}
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
let character_meseta = self.get_character_meseta_mut(&character.id)?;
|
|
character_meseta.0 += total_sale;
|
|
entity_gateway.set_character_meseta(&character.id, *character_meseta).await?;
|
|
}
|
|
else {
|
|
return Err(ItemManagerError::WalletFull.into())
|
|
}
|
|
}
|
|
} else {
|
|
return Err(ItemManagerError::ItemIdNotInInventory(item_id).into())
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// TODO: check if slot exists before putting units into it
|
|
pub async fn player_equips_item<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
item_id: ClientItemId,
|
|
equip_slot: u8)
|
|
-> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
inventory.equip(&item_id, equip_slot);
|
|
entity_gateway.set_character_equips(&character.id, &inventory.as_equipped_entity()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn player_unequips_item<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
item_id: ClientItemId)
|
|
-> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
inventory.unequip(&item_id);
|
|
entity_gateway.set_character_equips(&character.id, &inventory.as_equipped_entity()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn player_sorts_items<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
item_ids: [u32; 30])
|
|
-> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
let sorted_inventory_items: Vec<InventoryItem> = item_ids.iter()
|
|
.filter(|&client_item_id| *client_item_id < 0xFFFFFFFF)
|
|
.map(|&client_item_id| inventory.get_item_by_id(ClientItemId(client_item_id)))
|
|
.filter(|&x| x.is_some())
|
|
.map(|x| x.cloned().unwrap())
|
|
.collect();
|
|
|
|
inventory.set_items(sorted_inventory_items);
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn replace_item_with_tekked<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
item_id: ClientItemId,
|
|
tek: WeaponModifier)
|
|
-> Result<Weapon, anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
let item = inventory.remove_by_id(item_id)
|
|
.ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
let individual = item
|
|
.individual()
|
|
.ok_or(ItemManagerError::WrongItemType(item_id))?;
|
|
|
|
let entity_id = individual.entity_id;
|
|
let mut weapon = *individual
|
|
.weapon()
|
|
.ok_or(ItemManagerError::WrongItemType(item_id))?;
|
|
|
|
weapon.apply_modifier(&tek);
|
|
entity_gateway.add_weapon_modifier(&entity_id, tek).await?;
|
|
|
|
inventory.add_item(InventoryItem::Individual(IndividualInventoryItem {
|
|
entity_id,
|
|
item_id,
|
|
item: ItemDetail::Weapon(weapon),
|
|
}));
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
|
|
Ok(weapon)
|
|
}
|
|
|
|
pub async fn trade_items<EG: EntityGateway>(&mut self,
|
|
entity_gateway: &mut EG,
|
|
room_id: RoomId,
|
|
p1: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, usize),
|
|
p2: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, usize))
|
|
-> Result<Vec<ItemToTrade>, anyhow::Error> {
|
|
let it = ItemTransaction::new(self, (p1, p2, room_id))
|
|
.act(|it, (p1, p2, room_id)| -> Result<_, anyhow::Error> {
|
|
let p1_inventory = it.manager.get_character_inventory(p1.1)?;
|
|
let p2_inventory = it.manager.get_character_inventory(p2.1)?;
|
|
|
|
[(p2_inventory, p1_inventory, p2.2, p1.2), (p1_inventory, p2_inventory, p1.2, p2.2)].iter()
|
|
.map(|(src_inventory, dest_inventory, trade_recv, trade_send)| {
|
|
let item_slots_lost_to_trade = trade_send
|
|
.iter()
|
|
.fold(0, |acc, item| {
|
|
match item {
|
|
TradeItem::Individual(..) => {
|
|
acc + 1
|
|
},
|
|
TradeItem::Stacked(item_id, amount) => {
|
|
let stacked_inventory_item = try {
|
|
src_inventory
|
|
.get_item_by_id(*item_id)?
|
|
.stacked()
|
|
};
|
|
if let Some(Some(item)) = stacked_inventory_item {
|
|
if item.count() == *amount {
|
|
acc + 1
|
|
}
|
|
else {
|
|
acc
|
|
}
|
|
}
|
|
else {
|
|
acc
|
|
}
|
|
}
|
|
}
|
|
});
|
|
trade_recv
|
|
.iter()
|
|
.try_fold(dest_inventory.count(), |acc, item| {
|
|
match item {
|
|
TradeItem::Individual(..) => {
|
|
if acc >= (30 + item_slots_lost_to_trade) {
|
|
Err(TradeError::NoInventorySpace)
|
|
}
|
|
else {
|
|
Ok(acc + 1)
|
|
}
|
|
},
|
|
TradeItem::Stacked(item_id, amount) => {
|
|
let stacked_inventory_item = src_inventory
|
|
.get_item_by_id(*item_id)
|
|
.ok_or(TradeError::InvalidItemId(*item_id))?
|
|
.stacked()
|
|
.ok_or(TradeError::InvalidItemId(*item_id))?;
|
|
match dest_inventory.space_for_stacked_item(&stacked_inventory_item.tool, *amount) {
|
|
SpaceForStack::Yes(YesThereIsSpace::ExistingStack) => {
|
|
Ok(acc)
|
|
},
|
|
SpaceForStack::Yes(YesThereIsSpace::NewStack) => {
|
|
Ok(acc + 1)
|
|
},
|
|
SpaceForStack::No(NoThereIsNotSpace::FullStack) => {
|
|
Err(TradeError::NoStackSpace)
|
|
},
|
|
SpaceForStack::No(NoThereIsNotSpace::FullInventory) => {
|
|
if acc >= (30 + item_slots_lost_to_trade) {
|
|
Err(TradeError::NoInventorySpace)
|
|
}
|
|
else {
|
|
Ok(acc + 1)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
let trade_items = [(p1, p2, p1_inventory), (p2, p1, p2_inventory)]
|
|
.map(|(src_client, dest_client, src_inventory)| {
|
|
src_client.2.iter()
|
|
.map(|item| -> Option<(Option<ItemToTrade>, Box<dyn ItemAction<EG>>)> {
|
|
match item {
|
|
TradeItem::Individual(item_id) => {
|
|
let item = src_inventory.get_item_by_id(*item_id)?.individual()?;
|
|
let new_item_id = it.manager.room_item_id_counter.borrow_mut().get_mut(room_id)?();
|
|
Some((
|
|
Some(ItemToTrade {
|
|
add_to: *dest_client.0,
|
|
remove_from: *src_client.0,
|
|
current_item_id: *item_id,
|
|
new_item_id,
|
|
item_detail: ItemToTradeDetail::Individual(item.item.clone())
|
|
}),
|
|
Box::new(TradeIndividualItem {
|
|
src_character_id: src_client.1.id,
|
|
dest_character_id: dest_client.1.id,
|
|
current_item_id: *item_id,
|
|
new_item_id,
|
|
}),
|
|
))
|
|
},
|
|
TradeItem::Stacked(item_id, amount) => {
|
|
let item = src_inventory.get_item_by_id(*item_id)?.stacked()?;
|
|
if item.count() < *amount {
|
|
None
|
|
}
|
|
else {
|
|
let new_item_id = it.manager.room_item_id_counter.borrow_mut().get_mut(room_id)?();
|
|
Some((
|
|
Some(ItemToTrade {
|
|
add_to: *dest_client.0,
|
|
remove_from: *src_client.0,
|
|
current_item_id: *item_id,
|
|
new_item_id,
|
|
item_detail: ItemToTradeDetail::Stacked(item.tool, *amount)
|
|
}),
|
|
Box::new(TradeStackedItem {
|
|
src_character_id: src_client.1.id,
|
|
dest_character_id: dest_client.1.id,
|
|
//item_ids: item.entity_ids.iter().cloned().take(*amount).collect(),
|
|
current_item_id: *item_id,
|
|
new_item_id,
|
|
amount: *amount,
|
|
}),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.chain(
|
|
if src_client.3 > 0 {
|
|
Box::new(std::iter::once(Some(
|
|
(Some(ItemToTrade {
|
|
add_to: *dest_client.0,
|
|
remove_from: *src_client.0,
|
|
current_item_id: OTHER_MESETA_ITEM_ID,
|
|
new_item_id: OTHER_MESETA_ITEM_ID,
|
|
item_detail: ItemToTradeDetail::Meseta(src_client.3)
|
|
}),
|
|
Box::new(TradeMeseta {
|
|
src_character_id: src_client.1.id,
|
|
dest_character_id: dest_client.1.id,
|
|
amount: src_client.3,
|
|
}) as Box<dyn ItemAction<EG>>)))) as Box<dyn Iterator<Item = _>>
|
|
}
|
|
else {
|
|
Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _>>
|
|
})
|
|
.collect::<Option<Vec<_>>>()
|
|
});
|
|
|
|
|
|
if let [Some(p1_trades), Some(p2_trades)] = trade_items {
|
|
let (p1_item_trades, p1_item_actions): (Vec<_>, Vec<_>) = p1_trades.into_iter().unzip();
|
|
let (p2_item_trades, p2_item_actions): (Vec<_>, Vec<_>) = p2_trades.into_iter().unzip();
|
|
|
|
let item_trades = p1_item_trades.into_iter().flatten().chain(p2_item_trades.into_iter().flatten());
|
|
let item_actions = p1_item_actions.into_iter().chain(p2_item_actions.into_iter());
|
|
|
|
for action in item_actions {
|
|
it.action(action);
|
|
}
|
|
|
|
Ok(item_trades.collect())
|
|
}
|
|
else {
|
|
Err(ItemManagerError::InvalidTrade.into())
|
|
}
|
|
|
|
});
|
|
it.commit(self, entity_gateway)
|
|
.await
|
|
.map_err(|err| err.into())
|
|
}
|
|
|
|
pub async fn increase_kill_counters<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, equipped_items: &EquippedEntity, monstertype: MonsterType) -> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
if let Some(weapon_entity) = equipped_items.weapon {
|
|
let weapon_id = inventory.get_item_by_entity_id(weapon_entity).ok_or(ItemManagerError::EntityIdNotInInventory(weapon_entity))?.item_id();
|
|
let mut weapon_handle = inventory.get_item_handle_by_id(weapon_id).ok_or(ItemManagerError::NoSuchItemId(weapon_id))?;
|
|
let individual_item_w = weapon_handle.item_mut()
|
|
.ok_or(ItemManagerError::NoSuchItemId(weapon_id))?
|
|
.individual_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
|
|
let weapon = individual_item_w
|
|
.weapon_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
|
|
|
|
let wmodifier = WeaponModifier::AddKill { enemy: monstertype };
|
|
weapon.apply_modifier(&wmodifier);
|
|
entity_gateway.add_weapon_modifier(&weapon_entity, wmodifier).await?;
|
|
}
|
|
for units in equipped_items.unit.iter().flatten() {
|
|
let unit_id = inventory.get_item_by_entity_id(*units).ok_or(ItemManagerError::EntityIdNotInInventory(*units))?.item_id();
|
|
let mut unit_handle = inventory.get_item_handle_by_id(unit_id).ok_or(ItemManagerError::NoSuchItemId(unit_id))?;
|
|
let individual_item_u = unit_handle.item_mut()
|
|
.ok_or(ItemManagerError::NoSuchItemId(unit_id))?
|
|
.individual_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
|
|
let unit = individual_item_u
|
|
.unit_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
|
|
|
|
let umodifier = UnitModifier::AddKill { enemy: monstertype };
|
|
unit.apply_modifier(&umodifier);
|
|
entity_gateway.add_unit_modifier(units, umodifier).await?;
|
|
}
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ItemToTradeDetail {
|
|
Individual(ItemDetail),
|
|
Stacked(Tool, usize),
|
|
Meseta(usize),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ItemToTrade {
|
|
pub add_to: AreaClient,
|
|
pub remove_from: AreaClient,
|
|
pub current_item_id: ClientItemId,
|
|
pub new_item_id: ClientItemId,
|
|
pub item_detail: ItemToTradeDetail,
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
struct RemoveFromLocalFloor {
|
|
character_id: CharacterEntityId,
|
|
item_id: ClientItemId,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for RemoveFromLocalFloor {
|
|
async fn commit(&self, item_manager: &mut ItemManager, _entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let local_floor = item_manager.character_floor.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?;
|
|
local_floor.remove_item(&self.item_id);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
struct RemoveFromSharedFloor {
|
|
room_id: RoomId,
|
|
item_id: ClientItemId,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for RemoveFromSharedFloor {
|
|
async fn commit(&self, item_manager: &mut ItemManager, _entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let shared_floor = item_manager.room_floor.get_mut(&self.room_id).ok_or(ItemManagerError::NoRoom(self.room_id))?;
|
|
shared_floor.remove_item(&self.item_id);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
struct AddIndividualFloorItemToInventory{
|
|
character: CharacterEntity,
|
|
item: IndividualFloorItem,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for AddIndividualFloorItemToInventory {
|
|
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let inventory = item_manager.character_inventory.get_mut(&self.character.id).ok_or(ItemManagerError::NoCharacter(self.character.id))?;
|
|
let inv_item = inventory.add_individual_floor_item(&self.item);
|
|
|
|
entity_gateway.add_item_note(
|
|
&self.item.entity_id,
|
|
ItemNote::Pickup {
|
|
character_id: self.character.id,
|
|
}
|
|
).await?;
|
|
|
|
if inv_item.mag().is_some() {
|
|
entity_gateway.change_mag_owner(&self.item.entity_id, &self.character).await?;
|
|
}
|
|
|
|
entity_gateway.set_character_inventory(&self.character.id, &inventory.as_inventory_entity(&self.character.id)).await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
struct AddStackedFloorItemToInventory{
|
|
character_id: CharacterEntityId,
|
|
item: StackedFloorItem,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for AddStackedFloorItemToInventory {
|
|
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let inventory = item_manager.character_inventory.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?;
|
|
inventory.add_stacked_floor_item(&self.item);
|
|
|
|
entity_gateway.set_character_inventory(&self.character_id, &inventory.as_inventory_entity(&self.character_id)).await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
struct AddMesetaFloorItemToInventory{
|
|
character_id: CharacterEntityId,
|
|
item: MesetaFloorItem,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for AddMesetaFloorItemToInventory {
|
|
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let character_meseta = item_manager.character_meseta.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?;
|
|
character_meseta.0 = std::cmp::min(character_meseta.0 + self.item.meseta.0, 999999);
|
|
entity_gateway.set_character_meseta(&self.character_id, *character_meseta).await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
struct TradeIndividualItem {
|
|
src_character_id: CharacterEntityId,
|
|
dest_character_id: CharacterEntityId,
|
|
current_item_id: ClientItemId,
|
|
new_item_id: ClientItemId,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for TradeIndividualItem {
|
|
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let src_inventory = item_manager.character_inventory.get_mut(&self.src_character_id).ok_or(ItemManagerError::NoCharacter(self.src_character_id))?;
|
|
let inventory_item = src_inventory.take_item_by_id(self.current_item_id).ok_or(ItemManagerError::NoSuchItemId(self.current_item_id))?;
|
|
entity_gateway.set_character_inventory(&self.src_character_id, &src_inventory.as_inventory_entity(&self.src_character_id)).await?;
|
|
|
|
let dest_inventory = item_manager.character_inventory.get_mut(&self.dest_character_id).ok_or(ItemManagerError::NoCharacter(self.dest_character_id))?;
|
|
dest_inventory.add_item_with_new_item_id(inventory_item, self.new_item_id);
|
|
entity_gateway.set_character_inventory(&self.dest_character_id, &dest_inventory.as_inventory_entity(&self.dest_character_id)).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TradeStackedItem {
|
|
src_character_id: CharacterEntityId,
|
|
dest_character_id: CharacterEntityId,
|
|
current_item_id: ClientItemId,
|
|
new_item_id: ClientItemId,
|
|
amount: usize,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for TradeStackedItem {
|
|
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
let src_inventory = item_manager.character_inventory.get_mut(&self.src_character_id).ok_or(ItemManagerError::NoCharacter(self.src_character_id))?;
|
|
let inventory_item = src_inventory.take_stacked_item_by_id(self.current_item_id, self.amount).ok_or(ItemManagerError::NoSuchItemId(self.current_item_id))?;
|
|
entity_gateway.set_character_inventory(&self.src_character_id, &src_inventory.as_inventory_entity(&self.src_character_id)).await?;
|
|
|
|
let dest_inventory = item_manager.character_inventory.get_mut(&self.dest_character_id).ok_or(ItemManagerError::NoCharacter(self.dest_character_id))?;
|
|
dest_inventory.add_item_with_new_item_id(InventoryItem::Stacked(inventory_item), self.new_item_id);
|
|
entity_gateway.set_character_inventory(&self.dest_character_id, &dest_inventory.as_inventory_entity(&self.dest_character_id)).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TradeMeseta {
|
|
src_character_id: CharacterEntityId,
|
|
dest_character_id: CharacterEntityId,
|
|
amount: usize,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ItemAction<EG> for TradeMeseta {
|
|
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
|
|
{
|
|
let src_meseta = item_manager.get_character_meseta_mut(&self.src_character_id)?;
|
|
src_meseta.0 -= self.amount as u32;
|
|
entity_gateway.set_character_meseta(&self.src_character_id, *src_meseta).await?;
|
|
}
|
|
{
|
|
let dest_meseta = item_manager.get_character_meseta_mut(&self.dest_character_id)?;
|
|
dest_meseta.0 += self.amount as u32;
|
|
entity_gateway.set_character_meseta(&self.dest_character_id, *dest_meseta).await?;
|
|
}
|
|
}
|
|
|
|
pub async fn increase_kill_counters<EG: EntityGateway>( &mut self,
|
|
entity_gateway: &mut EG,
|
|
character: &CharacterEntity,
|
|
equipped_items: &EquippedEntity)
|
|
-> Result<(), anyhow::Error> {
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
// weapon
|
|
if let Some(weapon_entity) = equipped_items.weapon {
|
|
let weapon_id = inventory.get_item_by_entity_id(weapon_entity).ok_or(ItemManagerError::EntityIdNotInInventory(weapon_entity))?.item_id();
|
|
let mut weapon_handle = inventory.get_item_handle_by_id(weapon_id).ok_or(ItemManagerError::NoSuchItemId(weapon_id))?;
|
|
let individual_item_w = weapon_handle.item_mut()
|
|
.ok_or(ItemManagerError::NoSuchItemId(weapon_id))?
|
|
.individual_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
|
|
let weapon = individual_item_w
|
|
.weapon_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
|
|
|
|
weapon.increment_kill_counter();
|
|
entity_gateway.increment_kill_counter(&weapon_entity).await?;
|
|
}
|
|
// limiter
|
|
for units in equipped_items.unit {
|
|
if let Some(unit_entity) = units {
|
|
let unit_id = inventory.get_item_by_entity_id(unit_entity).ok_or(ItemManagerError::EntityIdNotInInventory(unit_entity))?.item_id();
|
|
let mut unit_handle = inventory.get_item_handle_by_id(unit_id).ok_or(ItemManagerError::NoSuchItemId(unit_id))?;
|
|
let individual_item_u = unit_handle.item_mut()
|
|
.ok_or(ItemManagerError::NoSuchItemId(unit_id))?
|
|
.individual_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
|
|
let unit = individual_item_u
|
|
.unit_mut()
|
|
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
|
|
|
|
unit.increment_kill_counter();
|
|
entity_gateway.increment_kill_counter(&unit_entity).await?;
|
|
}
|
|
}
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
Ok(())
|
|
}
|
|
}
|