|
@ -1,10 +1,10 @@ |
|
|
use crate::ship::items::ClientItemId;
|
|
|
use crate::ship::items::ClientItemId;
|
|
|
use std::collections::HashMap;
|
|
|
use std::collections::HashMap;
|
|
|
use thiserror::Error;
|
|
|
use thiserror::Error;
|
|
|
use crate::entity::gateway::EntityGateway;
|
|
|
|
|
|
|
|
|
use crate::entity::gateway::{EntityGateway, GatewayError};
|
|
|
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
|
|
|
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
|
|
|
use crate::entity::item::{ItemDetail, ItemLocation, BankName};
|
|
|
use crate::entity::item::{ItemDetail, ItemLocation, BankName};
|
|
|
use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity};
|
|
|
|
|
|
|
|
|
use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, ItemEntityId, InventoryItemEntity, BankItemEntity};
|
|
|
use crate::entity::item::tool::{Tool, ToolType};
|
|
|
use crate::entity::item::tool::{Tool, ToolType};
|
|
|
use crate::entity::item::weapon;
|
|
|
use crate::entity::item::weapon;
|
|
|
use crate::ship::map::MapArea;
|
|
|
use crate::ship::map::MapArea;
|
|
@ -17,6 +17,7 @@ use crate::ship::items::bank::*; |
|
|
use crate::ship::items::floor::*;
|
|
|
use crate::ship::items::floor::*;
|
|
|
use crate::ship::items::inventory::*;
|
|
|
use crate::ship::items::inventory::*;
|
|
|
use crate::ship::items::use_tool;
|
|
|
use crate::ship::items::use_tool;
|
|
|
|
|
|
use crate::ship::items::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError};
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
pub enum FloorType {
|
|
|
pub enum FloorType {
|
|
@ -35,6 +36,7 @@ pub enum ItemManagerError { |
|
|
EntityGatewayError,
|
|
|
EntityGatewayError,
|
|
|
NoSuchItemId(ClientItemId),
|
|
|
NoSuchItemId(ClientItemId),
|
|
|
NoCharacter(CharacterEntityId),
|
|
|
NoCharacter(CharacterEntityId),
|
|
|
|
|
|
NoRoom(RoomId),
|
|
|
CouldNotAddToInventory(ClientItemId),
|
|
|
CouldNotAddToInventory(ClientItemId),
|
|
|
//ItemBelongsToOtherPlayer,
|
|
|
//ItemBelongsToOtherPlayer,
|
|
|
Idunnoman,
|
|
|
Idunnoman,
|
|
@ -53,10 +55,36 @@ pub enum ItemManagerError { |
|
|
CannotGetIndividualItem,
|
|
|
CannotGetIndividualItem,
|
|
|
InvalidSlot(u8, u8), // slots available, slot attempted
|
|
|
InvalidSlot(u8, u8), // slots available, slot attempted
|
|
|
NoArmorEquipped,
|
|
|
NoArmorEquipped,
|
|
|
GatewayError(#[from] crate::entity::gateway::GatewayError),
|
|
|
|
|
|
|
|
|
GatewayError(#[from] GatewayError),
|
|
|
StackedItemError(Vec<ItemEntity>),
|
|
|
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 struct ItemManager {
|
|
|
pub(super) id_counter: u32,
|
|
|
pub(super) id_counter: u32,
|
|
|
|
|
|
|
|
@ -225,80 +253,88 @@ impl ItemManager { |
|
|
|
|
|
|
|
|
pub async fn character_picks_up_item<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId)
|
|
|
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> {
|
|
|
-> Result<TriggerCreateItem, anyhow::Error> {
|
|
|
let local_floor = self.character_floor.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
|
|
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
|
|
let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
|
|
let shared_floor = self.room_floor.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
|
|
|
|
|
|
|
|
|
|
|
let floor_item = local_floor.get_item_handle_by_id(item_id)
|
|
|
|
|
|
.or_else(|| {
|
|
|
|
|
|
shared_floor.get_item_handle_by_id(item_id)
|
|
|
|
|
|
})
|
|
|
|
|
|
.ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
|
|
|
|
|
|
|
|
|
|
|
let trigger_create_item = match floor_item.item() {
|
|
|
|
|
|
Some(FloorItem::Individual(individual_floor_item)) => {
|
|
|
|
|
|
let new_inventory_item = inventory.pick_up_individual_floor_item(individual_floor_item);
|
|
|
|
|
|
match new_inventory_item {
|
|
|
|
|
|
Some((new_inventory_item, _slot)) => {
|
|
|
|
|
|
entity_gateway.change_item_location(
|
|
|
|
|
|
&new_inventory_item.entity_id,
|
|
|
|
|
|
ItemLocation::Inventory {
|
|
|
|
|
|
character_id: character.id,
|
|
|
|
|
|
}
|
|
|
|
|
|
).await?;
|
|
|
|
|
|
if new_inventory_item.mag().is_some() {
|
|
|
|
|
|
entity_gateway.change_mag_owner(&new_inventory_item.entity_id, character).await?;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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 => {
|
|
|
None => {
|
|
|
return Err(ItemManagerError::CouldNotAddToInventory(item_id).into());
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
TriggerCreateItem::Yes
|
|
|
|
|
|
},
|
|
|
|
|
|
Some(FloorItem::Stacked(stacked_floor_item)) => {
|
|
|
|
|
|
let new_inventory_item = inventory.pick_up_stacked_floor_item(stacked_floor_item);
|
|
|
|
|
|
|
|
|
|
|
|
match new_inventory_item {
|
|
|
|
|
|
Some((new_inventory_item, _slot)) => {
|
|
|
|
|
|
for entity_id in &new_inventory_item.entity_ids {
|
|
|
|
|
|
entity_gateway.change_item_location(
|
|
|
|
|
|
entity_id,
|
|
|
|
|
|
ItemLocation::Inventory {
|
|
|
|
|
|
character_id: character.id,
|
|
|
|
|
|
}
|
|
|
|
|
|
).await?;
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
if stacked_floor_item.count() != new_inventory_item.count() {
|
|
|
|
|
|
TriggerCreateItem::No
|
|
|
|
|
|
|
|
|
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 {
|
|
|
else {
|
|
|
TriggerCreateItem::Yes
|
|
|
|
|
|
|
|
|
return Err(ItemManagerError::CouldNotAddToInventory(*item_id).into());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
TriggerCreateItem::Yes
|
|
|
},
|
|
|
},
|
|
|
None => {
|
|
|
|
|
|
return Err(ItemManagerError::CouldNotAddToInventory(item_id).into());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
Some(FloorItem::Meseta(meseta_floor_item)) => {
|
|
|
|
|
|
if character.meseta >= 999999 {
|
|
|
|
|
|
return Err(ItemManagerError::CouldNotAddToInventory(item_id).into());
|
|
|
|
|
|
}
|
|
|
|
|
|
character.meseta = std::cmp::min(character.meseta + meseta_floor_item.meseta.0, 999999);
|
|
|
|
|
|
entity_gateway.save_character(character).await?;
|
|
|
|
|
|
TriggerCreateItem::No
|
|
|
|
|
|
},
|
|
|
|
|
|
None => {
|
|
|
|
|
|
return Err(ItemManagerError::CouldNotAddToInventory(item_id).into());
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
|
|
|
|
|
floor_item.remove_from_floor();
|
|
|
|
|
|
Ok(trigger_create_item)
|
|
|
|
|
|
|
|
|
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> {
|
|
|
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> {
|
|
@ -924,14 +960,93 @@ impl ItemManager { |
|
|
Ok(weapon)
|
|
|
Ok(weapon)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
pub async fn send_items_to_other_player<EG: EntityGateway>(&mut self,
|
|
|
|
|
|
entity_gateway: &mut EG,
|
|
|
|
|
|
source_character: &CharacterEntity,
|
|
|
|
|
|
dest_character: &CharacterEntity,
|
|
|
|
|
|
items: &Vec<TradeItem>)
|
|
|
|
|
|
-> Result<(), anyhow::Error> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.change_item_location(
|
|
|
|
|
|
&self.item.entity_id,
|
|
|
|
|
|
ItemLocation::Inventory {
|
|
|
|
|
|
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(())
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|