elseware/src/ship/items/manager.rs
jake 82ef5ba2ea
Some checks failed
continuous-integration/drone/push Build is failing
RIP ItemLocation
fun while it lasted

ItemLocation ceased to be the canonical place to store an item's
location. replaced with ItemNote which basically covers the actual use
case but without the enforcing of a location.
2021-11-12 10:42:33 -07:00

1204 lines
58 KiB
Rust

use crate::ship::items::ClientItemId;
use std::collections::HashMap;
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, ItemEntityId, InventoryItemEntity, BankItemEntity};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::weapon;
use crate::ship::map::MapArea;
use crate::ship::ship::{TradeItem, ItemDropLocation};
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::location::{AreaClient, RoomId};
use crate::ship::shops::ShopItem;
use crate::ship::items::bank::*;
use crate::ship::items::floor::*;
use crate::ship::items::inventory::*;
use crate::ship::items::use_tool;
use crate::ship::items::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError};
#[derive(PartialEq, Eq)]
pub enum FloorType {
Local,
Shared,
}
pub enum TriggerCreateItem {
Yes,
No
}
#[derive(Error, Debug)]
#[error("")]
pub enum ItemManagerError {
EntityGatewayError,
NoSuchItemId(ClientItemId),
NoCharacter(CharacterEntityId),
NoRoom(RoomId),
CouldNotAddToInventory(ClientItemId),
//ItemBelongsToOtherPlayer,
Idunnoman,
CouldNotSplitItem(ClientItemId),
CouldNotDropMeseta,
InvalidBankName(BankName),
NotEnoughTools(Tool, usize, usize), // have, expected
InventoryItemConsumeError(#[from] InventoryItemConsumeError),
BankFull,
WrongItemType(ClientItemId),
UseItemError(#[from] use_tool::UseItemError),
CouldNotBuyItem,
CouldNotAddBoughtItemToInventory,
ItemIdNotInInventory(ClientItemId),
CannotGetMutItem,
CannotGetIndividualItem,
InvalidSlot(u8, u8), // slots available, slot attempted
NoArmorEquipped,
GatewayError(#[from] GatewayError),
StackedItemError(Vec<ItemEntity>),
ItemTransactionAction(Box<dyn std::error::Error + Send + Sync>),
InvalidTrade,
}
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>,
//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: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>,
}
impl Default for ItemManager {
fn default() -> ItemManager {
ItemManager {
id_counter: 0,
character_inventory: HashMap::new(),
character_bank: HashMap::new(),
character_floor: HashMap::new(),
character_room: HashMap::new(),
room_floor: HashMap::new(),
room_item_id_counter: HashMap::new(),
}
}
}
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);
self.character_inventory.insert(character.id, character_inventory);
self.character_bank.insert(character.id, character_bank);
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.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> {
Ok(self.character_bank
.get(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?)
//.get(&BankName("".to_string()))
//.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?)
}
/*pub fn get_character_bank_mut(&mut self, character: &CharacterEntity) -> Result<&CharacterBank, ItemManagerError> {
Ok(self.character_bank
.get_mut(&character.id)
.ok_or(ItemManagerError::NoCharacter(character.id))?
.entry(BankName("".to_string()))
.or_insert(CharacterBank::new(Vec::new())))
//.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?)
}*/
pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
self.character_inventory.remove(&character.id);
self.character_floor.remove(&character.id);
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.clone()
}));
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.clone()
}));
floor_item
},
None => {
return Err(ItemManagerError::NoSuchItemId(item_id.clone())).into()
}
}
}
};
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).into());
}
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).into());
},
}
},
FloorItem::Meseta(meseta_floor_item) => {
if character.meseta >= 999999 {
return Err(ItemManagerError::CouldNotAddToInventory(*item_id).into());
}
it.action(Box::new(AddMesetaFloorItemToInventory {
character: (**character).clone(),
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<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&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.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 {
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 {
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))?;
if character.meseta < amount {
return Err(ItemManagerError::CouldNotDropMeseta.into())
}
character.meseta -= amount;
entity_gateway.save_character(character).await?;
let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?();
let floor_item = FloorItem::Meseta(MesetaFloorItem {
item_id,
meseta: Meseta(amount),
map_area: drop_location.map_area,
x: drop_location.x,
y: 0.0,
z: drop_location.z,
});
shared_floor.add_item(floor_item.clone());
Ok(floor_item)
}
pub async fn player_drops_partial_stack_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
//inventory_item: InventoryItem,
item_id: ClientItemId,
drop_location: ItemDropLocation,
amount: usize)
-> Result<&StackedFloorItem, 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.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 {
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<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: ClientItemId,
amount: usize)
-> Result<&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<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
shop_item: &(dyn ShopItem + Send + Sync),
item_id: ClientItemId,
amount: usize)
-> Result<&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)
}
// 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: weapon::WeaponModifier)
-> Result<weapon::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))?
.clone();
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.clone()),
}))?;
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,
p1: (&AreaClient, &CharacterEntity, &Vec<TradeItem>),
p2: (&AreaClient, &CharacterEntity, &Vec<TradeItem>))
-> Result<Vec<ItemToTrade>, anyhow::Error> {
let it = ItemTransaction::new(&self, (p1, p2))
.act(|it, (p1, p2)| -> Result<_, anyhow::Error> {
let p1_inventory = it.manager.get_character_inventory(p1.1)?;
let p2_inventory = it.manager.get_character_inventory(p2.1)?;
//TODO: inv-selftrade+othertrade <= 30
//if p1_inventory
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>, Vec<Box<dyn ItemAction<EG>>>)> {
match item {
TradeItem::Individual(item_id) => {
let item = src_inventory.get_item_by_id(*item_id)?.individual()?;
Some((
Some(ItemToTrade {
add_to: *dest_client.0,
remove_from: *src_client.0,
item_id: *item_id,
item_detail: ItemToTradeDetail::Individual(item.item.clone())
}),
vec![
Box::new(AddIndividualItemToInventory {
character_id: dest_client.1.id,
item_id: item.entity_id,
}),
Box::new(RemoveIndividualItemFromInventory {
character_id: src_client.1.id,
item_id: item.entity_id,
})
]
))
},
TradeItem::Stacked(item_id, amount) => {
let item = src_inventory.get_item_by_id(*item_id)?.stacked()?;
if item.count() < *amount {
None
}
else {
Some((
Some(ItemToTrade {
add_to: *dest_client.0,
remove_from: *src_client.0,
item_id: *item_id,
item_detail: ItemToTradeDetail::Stacked(item.tool, *amount)
}),
vec![
Box::new(AddStackedItemToInventory {
character_id: dest_client.1.id,
item_ids: item.entity_ids.iter().cloned().take(*amount).collect(),
}),
Box::new(RemoveStackedItemFromInventory {
character_id: src_client.1.id,
item_ids: item.entity_ids.iter().cloned().take(*amount).collect(),
}),
]
))
}
},
TradeItem::Meseta(amount) => {
Some((None,
vec![
Box::new(AddMesetaToInventory {
character_id: dest_client.1.id,
amount: *amount,
}),
Box::new(RemoveMesetaFromInventory {
character_id: src_client.1.id,
amount: *amount,
}),
]
))
}
}
})
.collect::<Option<Vec<_>>>()
});
if let [Some(p1_trades), Some(p2_trades)] = trade_items {
let (p1_item_trades, p1_item_actions): (Vec<Option<ItemToTrade>>, Vec<Vec<Box<dyn ItemAction<EG>>>>) = p1_trades.into_iter().unzip();
let (p2_item_trades, p2_item_actions): (Vec<Option<ItemToTrade>>, Vec<Vec<Box<dyn ItemAction<EG>>>>) = 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().flatten().chain(p2_item_actions.into_iter().flatten());
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 enum ItemToTradeDetail {
Individual(ItemDetail),
Stacked(Tool, usize),
}
pub struct ItemToTrade {
pub add_to: AreaClient,
pub remove_from: AreaClient,
pub item_id: ClientItemId,
pub item_detail: ItemToTradeDetail,
}
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(())
}
}
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(())
}
}
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(())
}
}
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(())
}
}
struct AddMesetaFloorItemToInventory{
character: CharacterEntity,
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 mut nchar = self.character.clone();
nchar.meseta = std::cmp::min(self.character.meseta + self.item.meseta.0, 999999);
entity_gateway.save_character(&nchar).await?;
Ok(())
}
}
struct AddIndividualItemToInventory {
character_id: CharacterEntityId,
item_id: ItemEntityId,
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for AddIndividualItemToInventory {
async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
Ok(())
}
}
struct AddStackedItemToInventory {
character_id: CharacterEntityId,
item_ids: Vec<ItemEntityId>,
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for AddStackedItemToInventory {
async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
Ok(())
}
}
struct RemoveIndividualItemFromInventory {
character_id: CharacterEntityId,
item_id: ItemEntityId,
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for RemoveIndividualItemFromInventory {
async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
Ok(())
}
}
struct RemoveStackedItemFromInventory {
character_id: CharacterEntityId,
item_ids: Vec<ItemEntityId>,
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for RemoveStackedItemFromInventory {
async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
Ok(())
}
}
struct AddMesetaToInventory {
character_id: CharacterEntityId,
amount: usize,
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for AddMesetaToInventory {
async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
Ok(())
}
}
struct RemoveMesetaFromInventory {
character_id: CharacterEntityId,
amount: usize,
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for RemoveMesetaFromInventory {
async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
Ok(())
}
}