Compare commits

...

42 Commits

Author SHA1 Message Date
26a2ef6492 Merge pull request 'TRADING YEAH LETS GO' (#80) from trading into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #80
2021-12-28 13:05:26 -05:00
a6e50555de appease the clip
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-12-28 01:37:24 -07:00
22f630f562 properly set item_id after a trade serverside
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-12-28 01:08:44 -07:00
81916d1f57 TRADING JUST IN TIME TO BARELY MISS XMAS
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-12-27 00:48:42 -07:00
ecf1f23c6c fix tests
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-27 00:43:25 -07:00
9c34baeb26 meseta refactor
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-26 23:31:12 -07:00
72d72801e1 trade meseta
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-12 22:55:08 -07:00
3a10de502a tell other players you don't have meseta when you drop it
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-12 22:33:58 -07:00
872794e45f improve error messages
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-12 15:55:59 -07:00
6bd341cd01 check inventory has enough space to accept traded items 2021-12-12 15:55:24 -07:00
6d2753d082 improve error display a bit
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-10 23:41:17 -07:00
3b7dcd0295 remove printlns 2021-12-10 13:43:11 -07:00
71ba3c3c3d forgot to add this migration
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-10 13:25:27 -07:00
b3b6dad6ad trades! 2021-12-10 13:24:59 -07:00
5d410f88f3 PartialEq LocalClientId u8 2021-12-10 13:20:20 -07:00
3865caba8d refcell room id counter 2021-12-10 13:19:22 -07:00
d85ada945e add some functions to Inventory 2021-12-10 13:16:18 -07:00
070735edbf ItemDetail::tool 2021-12-10 13:15:33 -07:00
d4fc915164 add debug to characterentity 2021-12-10 13:14:50 -07:00
c90c7c1fd3 update anyhow 2021-12-10 13:07:39 -07:00
8b87bd8d7b fix mag item data
Some checks failed
continuous-integration/drone/push Build is failing
2021-12-02 01:57:30 -07:00
82ef5ba2ea RIP ItemLocation
Some checks failed
continuous-integration/drone/push Build is failing
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
b80f30ef9d initial actual trading logic
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-15 12:17:37 -06:00
98260308e8 stray spaces 2021-10-14 00:30:53 -06:00
8e82d6c4b4 convert item pickup to new itemtransaction model
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-14 00:28:31 -06:00
d80d0fb01d add some functions to Inventory 2021-10-14 00:22:23 -06:00
ddff55d09e add another item pickup test
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-14 00:20:59 -06:00
ac423fa6ba change trade structure in clientstate 2021-10-14 00:20:19 -06:00
8a29a75397 remove TODO 2021-10-14 00:17:36 -06:00
6ed0d838d2 add gateway function set_character_meseta
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-14 00:16:22 -06:00
296d1cc0ea InventoryItem::{stacked, mag} 2021-10-13 23:58:32 -06:00
dbb1105741 cleanup 2021-10-13 23:57:38 -06:00
21df9a9340 itemtransaction action takes boxed value
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-13 23:56:42 -06:00
42eb875f8f split create_item into individual and stacked variants 2021-10-09 01:33:40 -06:00
b374b63cba nth(0) -> next() 2021-09-27 23:47:19 -06:00
70a6764360 make these a bit more public for tests 2021-09-27 23:46:03 -06:00
55f82b9fce trace -> info 2021-09-27 23:14:37 -06:00
143ed7ed78 add ItemTransaction struct as begining of item refactor v3 2021-09-27 23:07:21 -06:00
9b78d7ed80 add some Defaults 2021-09-27 23:06:21 -06:00
91d381a2e2 this fixed a thing probably 2021-09-27 23:05:11 -06:00
0d30df8da6 start of trading 2021-07-30 23:19:47 -06:00
571bab2534 print send buffers 2021-07-30 23:19:47 -06:00
48 changed files with 6990 additions and 969 deletions

View File

@ -24,12 +24,12 @@ enum-utils = "0.1.2"
derive_more = { version = "0.99.3", features = ["display"]}
thiserror = "1.0.15"
ages-prs = "0.1"
async-trait = "0.1.41"
async-trait = "0.1.51"
lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] }
refinery = { version = "0.5.0", features = ["postgres"] }
sqlx = { version = "0.4.0", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
strum = "0.19.5"
strum_macros = "0.19"
anyhow = "1.0.33"
anyhow = { version = "1.0.47", features = ["backtrace"] }

View File

@ -9,7 +9,7 @@ use elseware::entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
#[allow(unused_imports)]
use elseware::entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
use elseware::entity::character::NewCharacterEntity;
use elseware::entity::item::{NewItemEntity, ItemDetail, ItemLocation};
use elseware::entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
use elseware::common::interserver::AuthToken;
use elseware::entity::item;
@ -67,13 +67,16 @@ fn main() {
entity_gateway.create_user_settings(NewUserSettingsEntity::new(fake_user.id)).await.unwrap();
let mut character = NewCharacterEntity::new(fake_user.id);
character.name = format!("Test Char {}", i*2);
entity_gateway.create_character(character).await.unwrap();
let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap();
let mut character = NewCharacterEntity::new(fake_user.id);
character.slot = 2;
character.name = "ItemRefactor".into();
character.exp = 80000000;
character.meseta = 999999;
let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap();
for _ in 0..3 {
entity_gateway.create_item(
@ -87,10 +90,6 @@ fn main() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: character.id,
name: item::BankName("".to_string())
}
}).await.unwrap();
}
@ -102,10 +101,6 @@ fn main() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: character.id,
name: item::BankName("".to_string())
}
}).await.unwrap();
}
@ -122,9 +117,6 @@ fn main() {
tekked: false,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
let item1 = entity_gateway.create_item(
NewItemEntity {
@ -139,9 +131,6 @@ fn main() {
tekked: true,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
let item2_w = entity_gateway.create_item(
NewItemEntity {
@ -156,9 +145,6 @@ fn main() {
tekked: true,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
let item3 = entity_gateway.create_item(
NewItemEntity {
@ -173,34 +159,25 @@ fn main() {
tekked: true,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
let item4 = entity_gateway.create_item(
NewItemEntity {
item: ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::DarkFlow,
grind: 5,
special: Some(item::weapon::WeaponSpecial::Charge),
grind: 0,
special: None,
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
let item5_m = entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Mag(item::mag::Mag::baby_mag(0)),
location: item::ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
for _ in 0..10usize {
@ -211,9 +188,6 @@ fn main() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::FedToMag {
mag: item5_m.id,
}
}).await.unwrap();
entity_gateway.feed_mag(&item5_m.id, &fed_tool.id).await.unwrap();
}
@ -226,9 +200,6 @@ fn main() {
tool: item::tool::ToolType::CellOfMag502,
}
),
location: item::ItemLocation::Inventory {
character_id: character.id,
}
}).await.unwrap();
let cell = entity_gateway.create_item(
item::NewItemEntity {
@ -237,7 +208,6 @@ fn main() {
tool: item::tool::ToolType::CellOfMag502,
}
),
location: item::ItemLocation::Consumed,
}).await.unwrap();
entity_gateway.use_mag_cell(&item5_m.id, &cell.id).await.unwrap();
@ -254,10 +224,6 @@ fn main() {
tekked: false,
}
),
location: ItemLocation::Bank {
character_id: character.id,
name: item::BankName("".to_string()),
}
}).await.unwrap();
let item7_a = entity_gateway.create_item(
NewItemEntity {
@ -269,9 +235,6 @@ fn main() {
slots: 4,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let item8_s = entity_gateway.create_item(
@ -283,9 +246,6 @@ fn main() {
evp: 0,
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let item9_u0 = entity_gateway.create_item(
@ -296,9 +256,6 @@ fn main() {
modifier: Some(item::unit::UnitModifier::Minus),
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let item10_u1 = entity_gateway.create_item(
@ -309,9 +266,6 @@ fn main() {
modifier: Some(item::unit::UnitModifier::Minus),
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let item11_u2 = entity_gateway.create_item(
@ -322,9 +276,6 @@ fn main() {
modifier: Some(item::unit::UnitModifier::Minus),
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let item12_u3 = entity_gateway.create_item(
@ -335,9 +286,6 @@ fn main() {
modifier: Some(item::unit::UnitModifier::Minus),
}
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let item13 = entity_gateway.create_item(
@ -345,12 +293,23 @@ fn main() {
item: ItemDetail::Mag(
item::mag::Mag::baby_mag(5)
),
location: ItemLocation::Inventory {
character_id: character.id,
}
}
).await.unwrap();
let monomates = futures::future::join_all((0..6).map(|_| {
let mut entity_gateway = entity_gateway.clone();
async move {
entity_gateway.create_item(
NewItemEntity {
item: ItemDetail::Tool (
item::tool::Tool {
tool: item::tool::ToolType::Monomate,
}
),
}).await.unwrap()
}
})).await;
let equipped = item::EquippedEntity {
weapon: Some(item2_w.id),
armor: Some(item7_a.id),
@ -360,7 +319,7 @@ fn main() {
};
entity_gateway.set_character_equips(&character.id, &equipped).await.unwrap();
let inventory = item::InventoryEntity::new(vec![item0, item1, item2_w, item3, item4, item5_m, item6, item7_a, item8_s, item9_u0, item10_u1, item11_u2, item12_u3, item13]);
let inventory = item::InventoryEntity::new(vec![InventoryItemEntity::from(item0), item1.into(), item2_w.into(), item3.into(), item4.into(), item5_m.into(), item6.into(), item7_a.into(), item8_s.into(), item9_u0.into(), item10_u1.into(), item11_u2.into(), item12_u3.into(), item13.into(), monomates.into()]);
entity_gateway.set_character_inventory(&character.id, &inventory).await.unwrap();
entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), item::BankName("".into())).await.unwrap();
}

View File

@ -119,7 +119,7 @@ async fn send_pkt<S: SendServerPacket + Send + std::fmt::Debug>(socket: Arc<asyn
-> Result<(), NetworkError>
{
let buf = pkt.as_bytes();
//println!("sndbuf: {:?}", buf);
trace!("[send buf] {:?}", buf);
let cbuf = cipher.lock().await.encrypt(&buf)?;
let mut ssock = &*socket;
ssock.write_all(&cbuf).await?;
@ -156,7 +156,7 @@ where
match pkt_receiver.recv_pkts().await {
Ok(pkts) => {
for pkt in pkts {
trace!("[recv from {:?}] {:?}", client_id, pkt);
info!("[recv from {:?}] {:?}", client_id, pkt);
server_sender.send(ClientAction::Packet(client_id, pkt)).await.unwrap();
}
},
@ -194,7 +194,7 @@ where
*cipher_out.lock().await = outc;
}
ServerStateAction::Packet(pkt) => {
trace!("[send to {:?}] {:?}", client_id, pkt);
info!("[send to {:?}] {:?}", client_id, pkt);
if let Err(err) = send_pkt(socket.clone(), cipher_out.clone(), pkt).await {
warn!("[client {:?} send error ] {:?}", client_id, err);
}

View File

@ -1,7 +1,7 @@
use libpso::PacketParseError;
use libpso::crypto::PSOCipher;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub struct ClientId(pub usize);
pub enum OnConnect<S: SendServerPacket> {

View File

@ -5,7 +5,7 @@ use libpso::character::guildcard;
pub const USERFLAG_NEWCHAR: u32 = 0x00000001;
pub const USERFLAG_DRESSINGROOM: u32 = 0x00000002;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub struct UserAccountId(pub u32);
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct UserSettingsId(pub u32);
@ -59,6 +59,26 @@ pub struct UserAccountEntity {
pub at_ship: bool,
}
impl Default for UserAccountEntity {
fn default() -> UserAccountEntity {
UserAccountEntity {
id: UserAccountId(0),
username: "".into(),
password: "".into(),
guildcard: 0xFFFFFFFF,
team_id: None,
banned_until: None,
muted_until: None,
created_at: chrono::Utc::now(),
flags: 0,
activated: false,
at_login: false,
at_character: false,
at_ship: false,
}
}
}
impl UserAccountEntity {
pub fn is_currently_online(&self) -> bool {
self.at_login | self.at_character | self.at_ship

View File

@ -7,8 +7,9 @@ use libpso::character::character::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU};
use crate::entity::item::tech::Technique;
use crate::entity::account::UserAccountId;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize, Default)]
pub enum CharacterClass {
#[default]
HUmar,
HUnewearl,
HUcast,
@ -90,8 +91,9 @@ impl CharacterClass {
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize, Default)]
pub enum SectionID {
#[default]
Viridia,
Greenill,
Skyly,
@ -158,18 +160,12 @@ pub struct CharacterAppearance {
#[derive(Clone, Debug)]
pub struct TechLevel(pub u8);
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub struct CharacterTechniques {
pub techs: HashMap<Technique, TechLevel>
}
impl CharacterTechniques {
fn new() -> CharacterTechniques {
CharacterTechniques {
techs: HashMap::new(),
}
}
pub fn set_tech(&mut self, tech: Technique, level: TechLevel) {
self.techs.insert(tech, TechLevel(level.0 - 1));
}
@ -187,18 +183,20 @@ impl CharacterTechniques {
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct CharacterConfig {
pub raw_data: [u8; 0xE8],
}
impl CharacterConfig {
fn new() -> CharacterConfig {
impl Default for CharacterConfig {
fn default() -> CharacterConfig {
CharacterConfig {
raw_data: DEFAULT_PALETTE_CONFIG,
}
}
}
impl CharacterConfig {
pub fn update(&mut self, new_config: &UpdateConfig) {
self.raw_data = new_config.config;
}
@ -208,18 +206,20 @@ impl CharacterConfig {
}
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct CharacterInfoboard {
pub board: [u16; 172],
}
impl CharacterInfoboard {
fn new() -> CharacterInfoboard {
impl Default for CharacterInfoboard {
fn default() -> CharacterInfoboard {
CharacterInfoboard {
board: [0; 172]
}
}
}
impl CharacterInfoboard {
pub fn as_bytes(&self) -> [u16; 172] {
self.board
}
@ -229,29 +229,31 @@ impl CharacterInfoboard {
}
}
#[derive(Clone, Default)]
#[derive(Debug, Clone, Default)]
pub struct CharacterGuildCard {
pub description: String,
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct CharacterTechMenu {
pub tech_menu: [u8; 40],
}
impl CharacterTechMenu {
fn new() -> CharacterTechMenu {
impl Default for CharacterTechMenu {
fn default() -> CharacterTechMenu {
CharacterTechMenu {
tech_menu: DEFAULT_TECH_MENU,
}
}
}
impl CharacterTechMenu {
pub fn as_bytes(&self) -> [u8; 40] {
self.tech_menu
}
}
#[derive(Clone, Default)]
#[derive(Clone, Default, Debug)]
pub struct CharacterMaterials {
pub power: u32,
pub mind: u32,
@ -262,7 +264,7 @@ pub struct CharacterMaterials {
pub tp: u32,
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)]
pub struct CharacterEntityId(pub u32);
#[derive(Clone)]
@ -284,8 +286,6 @@ pub struct NewCharacterEntity {
pub materials: CharacterMaterials,
pub tech_menu: CharacterTechMenu,
pub meseta: u32,
pub bank_meseta: u32,
pub option_flags: u32,
}
@ -299,20 +299,18 @@ impl NewCharacterEntity {
char_class: CharacterClass::HUmar,
section_id: SectionID::Viridia,
appearance: CharacterAppearance::default(),
techs: CharacterTechniques::new(),
config: CharacterConfig::new(),
info_board: CharacterInfoboard::new(),
techs: CharacterTechniques::default(),
config: CharacterConfig::default(),
info_board: CharacterInfoboard::default(),
guildcard: CharacterGuildCard::default(),
materials: CharacterMaterials::default(),
tech_menu: CharacterTechMenu::new(),
meseta: 0,
bank_meseta: 0,
tech_menu: CharacterTechMenu::default(),
option_flags: 0,
}
}
}
#[derive(Clone)]
#[derive(Clone, Default, Debug)]
pub struct CharacterEntity {
pub id: CharacterEntityId,
pub user_id: UserAccountId,
@ -332,8 +330,5 @@ pub struct CharacterEntity {
pub materials: CharacterMaterials,
pub tech_menu: CharacterTechMenu,
pub meseta: u32,
// TODO: this should not be tied to the character
pub bank_meseta: u32,
pub option_flags: u32,
}

View File

@ -65,7 +65,7 @@ pub trait EntityGateway: Send + Sync + Clone {
unimplemented!();
}
async fn change_item_location(&mut self, _item_id: &ItemEntityId, _item_location: ItemLocation) -> Result<(), GatewayError> {
async fn add_item_note(&mut self, _item_id: &ItemEntityId, _item_note: ItemNote) -> Result<(), GatewayError> {
unimplemented!();
}
@ -115,4 +115,20 @@ pub trait EntityGateway: Send + Sync + Clone {
async fn set_character_equips(&mut self, _char_id: &CharacterEntityId, _equips: &EquippedEntity) -> Result<(), GatewayError> {
unimplemented!();
}
async fn get_character_meseta(&mut self, _char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
unimplemented!();
}
async fn set_character_meseta(&mut self, _char_id: &CharacterEntityId, _amount: Meseta) -> Result<(), GatewayError> {
unimplemented!();
}
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: BankName) -> Result<Meseta, GatewayError> {
unimplemented!();
}
async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: BankName, _amount: Meseta) -> Result<(), GatewayError> {
unimplemented!();
}
}

View File

@ -13,6 +13,8 @@ pub struct InMemoryGateway {
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
bank_meseta: Arc<Mutex<BTreeMap<(CharacterEntityId, BankName), Meseta>>>,
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
@ -27,6 +29,8 @@ impl Default for InMemoryGateway {
users: Arc::new(Mutex::new(BTreeMap::new())),
user_settings: Arc::new(Mutex::new(BTreeMap::new())),
characters: Arc::new(Mutex::new(BTreeMap::new())),
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
items: Arc::new(Mutex::new(BTreeMap::new())),
inventories: Arc::new(Mutex::new(BTreeMap::new())),
banks: Arc::new(Mutex::new(BTreeMap::new())),
@ -197,8 +201,6 @@ impl EntityGateway for InMemoryGateway {
guildcard: character.guildcard,
materials: character.materials,
tech_menu: character.tech_menu,
meseta: character.meseta,
bank_meseta: character.bank_meseta,
option_flags: character.option_flags,
};
characters.insert(new_character.id, new_character.clone());
@ -223,17 +225,13 @@ impl EntityGateway for InMemoryGateway {
+ 1;
let new_item = ItemEntity {
id: ItemEntityId(id),
location: item.location,
item: item.item,
};
items.insert(ItemEntityId(id), new_item.clone());
Ok(new_item)
}
async fn change_item_location(&mut self, item_id: &ItemEntityId, item_location: ItemLocation) -> Result<(), GatewayError> {
if let Some(item_entity) = self.items.lock().unwrap().get_mut(item_id) {
item_entity.location = item_location
}
async fn add_item_note(&mut self, _item_id: &ItemEntityId, _item_note: ItemNote) -> Result<(), GatewayError> {
Ok(())
}
@ -272,7 +270,6 @@ impl EntityGateway for InMemoryGateway {
}
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
println!("getting inv");
let inventories = self.inventories.lock().unwrap();
Ok(inventories
.iter()
@ -318,4 +315,36 @@ impl EntityGateway for InMemoryGateway {
equips.insert(*char_id, equipped.clone());
Ok(())
}
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
let mut character_meseta = self.character_meseta.lock().unwrap();
character_meseta.insert(*char_id, meseta);
Ok(())
}
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
let mut character_meseta = self.character_meseta.lock().unwrap();
if let Some(meseta) = character_meseta.get_mut(char_id) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName, meseta: Meseta) -> Result<(), GatewayError> {
let mut bank_meseta = self.bank_meseta.lock().unwrap();
bank_meseta.insert((*char_id, bank), meseta);
Ok(())
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName) -> Result<Meseta, GatewayError> {
let mut bank_meseta = self.bank_meseta.lock().unwrap();
if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank)) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
}
}

View File

@ -0,0 +1,7 @@
drop table item_location;
create table item_note (
item integer references item (id) not null,
note jsonb not null,
created_at timestamptz default current_timestamp not null
);

View File

@ -0,0 +1,15 @@
create table character_meseta (
pchar integer references character (id) not null unique,
meseta integer not null,
);
create table bank_meseta (
pchar integer references character (id) not null,
bank varchar(128) not null,
meseta integer not null,
unique (pchar, bank)
);
alter table player_character
drop column meseta, bank_meseta;

View File

@ -1,3 +1,4 @@
#![allow(dead_code)]
use std::collections::HashMap;
use std::convert::Into;
use serde::{Serialize, Deserialize};
@ -216,8 +217,6 @@ pub struct PgCharacter {
tp: i16,
tech_menu: Vec<u8>,
meseta: i32,
bank_meseta: i32,
}
impl From<PgCharacter> for CharacterEntity {
@ -267,8 +266,6 @@ impl From<PgCharacter> for CharacterEntity {
tech_menu: CharacterTechMenu {
tech_menu: vec_to_array(other.tech_menu)
},
meseta: other.meseta as u32,
bank_meseta: other.bank_meseta as u32,
}
}
}
@ -571,22 +568,21 @@ pub struct PgItem {
#[derive(Debug, Serialize, Deserialize)]
pub enum PgItemLocationDetail {
Inventory {
pub enum PgItemNoteDetail {
CharacterCreation {
character_id: u32,
},
Bank {
character_id: u32,
name: String,
},
LocalFloor {
EnemyDrop {
character_id: u32,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
SharedFloor {
Pickup {
character_id: u32,
},
PlayerDrop {
map_area: MapArea,
x: f32,
y: f32,
@ -596,42 +592,92 @@ pub enum PgItemLocationDetail {
FedToMag {
mag: u32,
},
Shop,
BoughtAtShop {
character_id: u32,
},
SoldToShop,
Trade {
id: u32,
character_to: u32,
character_from: u32,
},
}
impl From<ItemLocation> for PgItemLocationDetail {
fn from(other: ItemLocation) -> PgItemLocationDetail {
impl From<ItemNote> for PgItemNoteDetail {
fn from(other: ItemNote) -> PgItemNoteDetail {
match other {
ItemLocation::Inventory{character_id} => PgItemLocationDetail::Inventory{character_id: character_id.0},
ItemLocation::Bank{character_id, name} => PgItemLocationDetail::Bank{character_id: character_id.0, name: name.0},
ItemLocation::LocalFloor{character_id, map_area, x,y,z} => PgItemLocationDetail::LocalFloor{character_id: character_id.0, map_area, x,y,z},
ItemLocation::SharedFloor{map_area, x,y,z} => PgItemLocationDetail::SharedFloor{map_area, x,y,z},
ItemLocation::Consumed => PgItemLocationDetail::Consumed,
ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0},
ItemLocation::Shop => PgItemLocationDetail::Shop,
ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation {
character_id: character_id.0,
},
ItemNote::EnemyDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
character_id: character_id.0,
map_area,
x,y,z,
},
ItemNote::Pickup{character_id} => PgItemNoteDetail::Pickup {
character_id: character_id.0,
},
ItemNote::PlayerDrop{map_area, x, y, z} => PgItemNoteDetail::PlayerDrop {
map_area,
x,y,z,
},
ItemNote::Consumed => PgItemNoteDetail::Consumed,
ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
mag: mag.0
},
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
character_id: character_id.0,
},
ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop,
ItemNote::Trade{id, character_to, character_from} => PgItemNoteDetail::Trade {
id: id.0,
character_to: character_to.0,
character_from: character_from.0,
}
}
}
}
impl From<PgItemLocationDetail> for ItemLocation {
fn from(other: PgItemLocationDetail) -> ItemLocation {
match other{
PgItemLocationDetail::Inventory{character_id} => ItemLocation::Inventory{character_id: CharacterEntityId(character_id)},
PgItemLocationDetail::Bank{character_id, name} => ItemLocation::Bank{character_id: CharacterEntityId(character_id), name: BankName(name)},
PgItemLocationDetail::LocalFloor{character_id, map_area, x,y,z} => ItemLocation::LocalFloor{character_id: CharacterEntityId(character_id), map_area, x,y,z},
PgItemLocationDetail::SharedFloor{map_area, x,y,z} => ItemLocation::SharedFloor{map_area, x,y,z},
PgItemLocationDetail::Consumed => ItemLocation::Consumed,
PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)},
PgItemLocationDetail::Shop => ItemLocation::Shop,
impl From<PgItemNoteDetail> for ItemNote {
fn from(other: PgItemNoteDetail) -> ItemNote {
match other {
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
character_id: CharacterEntityId(character_id as u32),
},
PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
character_id: CharacterEntityId(character_id as u32),
map_area,
x,y,z,
},
PgItemNoteDetail::Pickup{character_id} => ItemNote::Pickup {
character_id: CharacterEntityId(character_id as u32),
},
PgItemNoteDetail::PlayerDrop{map_area, x, y, z} => ItemNote::PlayerDrop {
map_area,
x,y,z,
},
PgItemNoteDetail::Consumed => ItemNote::Consumed,
PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
mag: ItemEntityId(mag)
},
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop,
PgItemNoteDetail::Trade {id, character_to, character_from} => ItemNote::Trade {
id: TradeId(id as u32),
character_to: CharacterEntityId(character_to as u32),
character_from: CharacterEntityId(character_from as u32),
}
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgItemLocation {
pub struct PgItemNote {
//pub id: i32,
pub location: sqlx::types::Json<PgItemLocationDetail>,
pub note: sqlx::types::Json<PgItemNoteDetail>,
created_at: chrono::DateTime<chrono::Utc>,
}
@ -680,19 +726,20 @@ pub struct PgItemEntity {
pub item: sqlx::types::Json<PgItemDetail>,
}
/*
#[derive(Debug, sqlx::FromRow)]
pub struct PgItemWithLocation {
pub id: i32,
pub item: sqlx::types::Json<PgItemDetail>,
pub location: sqlx::types::Json<PgItemLocationDetail>,
}
*/
impl From<PgItemWithLocation> for ItemEntity {
fn from(other: PgItemWithLocation) -> ItemEntity {
impl From<PgItemEntity> for ItemEntity {
fn from(other: PgItemEntity) -> ItemEntity {
ItemEntity {
id: ItemEntityId(other.id as u32),
item: other.item.0.into(),
location: other.location.0.into(),
}
}
}

View File

@ -44,7 +44,7 @@ impl PostgresGateway {
}
async fn apply_item_modifications(&self, item: ItemEntity) -> ItemEntity {
let ItemEntity {id, item, location} = item;
let ItemEntity {id, item} = item;
let item = match item {
ItemDetail::Weapon(mut weapon) => {
@ -101,7 +101,6 @@ impl PostgresGateway {
ItemEntity {
id,
item,
location
}
}
}
@ -183,7 +182,7 @@ impl EntityGateway for PostgresGateway {
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
let q = r#"insert into player_character
(user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, meseta, bank_meseta, option_flags)
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags)
values
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)
returning *;"#;
@ -216,8 +215,6 @@ impl EntityGateway for PostgresGateway {
.bind(char.materials.hp as i16)
.bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec())
.bind(char.meseta as i32)
.bind(char.bank_meseta as i32)
.bind(char.option_flags as i32)
.fetch_one(&self.pool).await?;
@ -242,7 +239,7 @@ impl EntityGateway for PostgresGateway {
let q = r#"update player_character set
user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12,
hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23,
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, meseta=$29, bank_meseta=$30, option_flags=$31
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29
where id=$32;"#;
sqlx::query(q)
.bind(char.user_id.0)
@ -273,8 +270,6 @@ impl EntityGateway for PostgresGateway {
.bind(char.materials.hp as i16)
.bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec())
.bind(char.meseta as i32)
.bind(char.bank_meseta as i32)
.bind(char.option_flags as i32)
.bind(char.id.0 as i32)
.execute(&self.pool).await?;
@ -294,64 +289,18 @@ impl EntityGateway for PostgresGateway {
let new_item = sqlx::query_as::<_, PgItem>("insert into item (item) values ($1) returning *;")
.bind(sqlx::types::Json(PgItemDetail::from(item.item)))
.fetch_one(&mut tx).await?;
let location = sqlx::query_as::<_, PgItemLocation>("insert into item_location (item, location) values ($1, $2) returning *")
.bind(new_item.id)
.bind(sqlx::types::Json(PgItemLocationDetail::from(item.location)))
.fetch_one(&mut tx).await?;
tx.commit().await?;
Ok(ItemEntity {
id: ItemEntityId(new_item.id as u32),
item: new_item.item.0.into(),
location: location.location.0.into(),
})
/*
let mut tx = self.pool.begin().await?;
let new_item = sqlx::query_as::<_, PgItem>("insert into item (item) values ($1) returning *;")
.bind(sqlx::types::Json(PgItemDetail::from(item.item)))
.fetch_one(&mut tx).await?;
let location = if let ItemLocation::Inventory{slot, ..} = &item.location {
sqlx::query("insert into item_location (item, location) values ($1, $2)")
.bind(new_item.id)
.bind(sqlx::types::Json(PgItemLocationDetail::from(item.location.clone())))
.execute(&mut tx).await?;
sqlx::query("insert into inventory_slot (item, slot) values ($1, $2)")
.bind(new_item.id)
.bind(*slot as i32)
.execute(&mut tx).await?;
sqlx::query_as::<_, PgItemLocation>(r#"select
item_location.item,
jsonb_set(item_location.location, '{Inventory,slot}', inventory_slot.slot::text::jsonb) as location,
item_location.created_at
from item_location
join item on item.id = item_location.item
join inventory_slot on inventory_slot.item = item.id
where item.id = $1
order by item_location.created_at
limit 1"#)
.bind(new_item.id)
.fetch_one(&mut tx).await?
}
else {
sqlx::query_as::<_, PgItemLocation>("insert into item_location (item, location) values ($1, $2) returning *")
.bind(new_item.id)
.bind(sqlx::types::Json(PgItemLocationDetail::from(item.location)))
.fetch_one(&mut tx).await?
};
tx.commit().await?;
Ok(ItemEntity {
id: ItemEntityId(new_item.id as u32),
item: new_item.item.0.into(),
location: location.location.0.into(),
})
*/
}
async fn change_item_location(&mut self, item_id: &ItemEntityId, item_location: ItemLocation) -> Result<(), GatewayError> {
sqlx::query("insert into item_location (item, location) values ($1, $2)")
async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> {
sqlx::query("insert into item_note(item, note) values ($1, $2)")
.bind(item_id.0)
.bind(sqlx::types::Json(PgItemLocationDetail::from(item_location)))
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
.execute(&self.pool).await?;
Ok(())
@ -465,7 +414,7 @@ impl EntityGateway for PostgresGateway {
for inv_item in inventory.items.0.into_iter() {
match inv_item {
PgInventoryItemEntity::Individual(item) => {
let entity = sqlx::query_as::<_, PgItemWithLocation>("select item.id, item.item, item_location.location from item join item_location on item.id = item_location.item where id = $1")
let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(item)
.fetch_one(&self.pool).await
.map(|item| item.into())
@ -476,7 +425,7 @@ impl EntityGateway for PostgresGateway {
PgInventoryItemEntity::Stacked(items) => {
let mut stacked_item = Vec::new();
for s_item in items {
stacked_item.push(sqlx::query_as::<_, PgItemWithLocation>("select item.id, item.item, item_location.location from item join item_location on item.id = item_location.item where id = $1")
stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(s_item)
.fetch_one(&self.pool).await
.map(|item| item.into())
@ -501,7 +450,7 @@ impl EntityGateway for PostgresGateway {
for bank_item in bank.items.0.into_iter() {
match bank_item {
PgInventoryItemEntity::Individual(item) => {
let entity = sqlx::query_as::<_, PgItemWithLocation>("select item.id, item.item, item_location.location from item join item_location on item.id = item_location.item where id = $1")
let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(item)
.fetch_one(&self.pool).await
.map(|item| item.into())
@ -512,7 +461,7 @@ impl EntityGateway for PostgresGateway {
PgInventoryItemEntity::Stacked(items) => {
let mut stacked_item = Vec::new();
for s_item in items {
stacked_item.push(sqlx::query_as::<_, PgItemWithLocation>("select item.id, item.item, item_location.location from item join item_location on item.id = item_location.item where id = $1")
stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(s_item)
.fetch_one(&self.pool).await
.map(|item| item.into())
@ -597,4 +546,44 @@ impl EntityGateway for PostgresGateway {
.await?;
Ok(())
}
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2")
.bind(char_id.0)
.bind(meseta.0 as i32)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
#[derive(sqlx::FromRow)]
struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#)
.bind(char_id.0)
.fetch_one(&self.pool)
.await?;
Ok(Meseta(meseta.0 as u32))
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName, meseta: Meseta) -> Result<(), GatewayError> {
sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2")
.bind(char_id.0)
.bind(meseta.0 as i32)
.bind(bank.0)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName) -> Result<Meseta, GatewayError> {
#[derive(sqlx::FromRow)]
struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1 and bank = $2"#)
.bind(char_id.0)
.bind(bank.0)
.fetch_one(&self.pool)
.await?;
Ok(Meseta(meseta.0 as u32))
}
}

View File

@ -567,6 +567,7 @@ impl Mag {
pub fn as_bytes(&self) -> [u8; 16] {
let mut result = [0; 16];
result[0..3].copy_from_slice(&self.mag.value());
result[2] = self.level() as u8;
result[3] = self.photon_blast_value();
result[4..6].copy_from_slice(&self.def.to_le_bytes());
result[6..8].copy_from_slice(&self.pow.to_le_bytes());

View File

@ -17,26 +17,29 @@ use crate::ship::drops::ItemDropType;
pub struct ItemEntityId(pub u32);
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
pub struct ItemId(u32);
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, derive_more::Display)]
pub struct BankName(pub String);
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TradeId(pub u32);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ItemLocation {
Inventory {
pub enum ItemNote {
CharacterCreation {
character_id: CharacterEntityId,
},
Bank {
character_id: CharacterEntityId,
name: BankName,
},
LocalFloor {
EnemyDrop {
character_id: CharacterEntityId,
//monster_type: MonsterType,
//droprate: f32,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
SharedFloor {
Pickup {
character_id: CharacterEntityId,
},
PlayerDrop {
map_area: MapArea,
x: f32,
y: f32,
@ -46,18 +49,18 @@ pub enum ItemLocation {
FedToMag {
mag: ItemEntityId,
},
Shop,
/*Destroyed {
// marks an item that has been consumed in some way
BoughtAtShop {
character_id: CharacterEntityId,
},
SoldToShop,
Trade {
id: TradeId,
character_to: CharacterEntityId,
character_from: CharacterEntityId,
},
Transformed {
item_id,
change_event
}
*/
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Meseta(pub u32);
impl Meseta {
@ -159,18 +162,23 @@ impl ItemDetail {
_ => None,
}
}
pub fn tool(&self) -> Option<&tool::Tool> {
match self {
ItemDetail::Tool(tool) => Some(tool),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct NewItemEntity {
pub location: ItemLocation,
pub item: ItemDetail,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ItemEntity {
pub id: ItemEntityId,
pub location: ItemLocation,
pub item: ItemDetail,
}
@ -215,6 +223,13 @@ impl InventoryItemEntity {
_ => None,
}
}
pub fn individual(&self) -> Option<&ItemEntity> {
match self {
InventoryItemEntity::Individual(i) => Some(i),
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]

View File

@ -2,7 +2,8 @@
#![feature(maybe_uninit_extra)]
#![feature(inline_const)]
#![feature(drain_filter)]
#![feature(derive_default_enum)]
#![feature(try_blocks)]

View File

@ -20,7 +20,7 @@ use libpso::{utf8_to_array, utf8_to_utf16_array};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
use crate::entity::item::{NewItemEntity, ItemDetail, ItemLocation, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity};
use crate::entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity, Meseta};
use crate::entity::item::weapon::Weapon;
use crate::entity::item::armor::Armor;
use crate::entity::item::tech::Technique;
@ -201,8 +201,8 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
_ => {}
}
character.meseta = 300;
let character = entity_gateway.create_character(character).await?;
entity_gateway.set_character_meseta(&character.id, Meseta(300)).await?;
let new_weapon = match character.char_class {
CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber,
@ -220,10 +220,11 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
special: None,
attrs: [None; 3],
tekked: true,
}),
location: ItemLocation::Inventory {
character_id: character.id,
}}).await?;
})}).await?;
entity_gateway.add_item_note(&weapon.id, ItemNote::CharacterCreation {
character_id: character.id,
}).await?;
let armor = entity_gateway.create_item(
NewItemEntity {
@ -233,10 +234,11 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
dfp: 0,
evp: 0,
slots: 0,
}),
location: ItemLocation::Inventory {
character_id: character.id,
}}).await?;
})}).await?;
entity_gateway.add_item_note(&armor.id, ItemNote::CharacterCreation {
character_id: character.id,
}).await?;
let mut mag = {
if character.char_class.is_android() {
@ -249,35 +251,40 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
let mag = entity_gateway.create_item(
NewItemEntity {
item: ItemDetail::Mag(mag),
location: ItemLocation::Inventory {
character_id: character.id,
}}).await?;
}).await?;
let mut monomates = Vec::new();
for _ in 0..4usize {
monomates.push(entity_gateway.create_item(
NewItemEntity {
item: ItemDetail::Tool (
Tool {
tool: item::tool::ToolType::Monomate,
}),
location: ItemLocation::Inventory {
character_id: character.id,
}}).await?)
}
entity_gateway.add_item_note(&mag.id, ItemNote::CharacterCreation {
character_id: character.id,
}).await?;
let mut monofluids = Vec::new();
for _ in 0..4usize {
monofluids.push(entity_gateway.create_item(
NewItemEntity {
item: ItemDetail::Tool (
Tool {
tool: item::tool::ToolType::Monofluid,
}),
location: ItemLocation::Inventory {
character_id: character.id,
}}).await?)
}
let (monomates, monofluids) = futures::future::join_all((0..4usize).map(|_| {
let mut eg = entity_gateway.clone();
let character_id = character.id;
async move {
let monomate = eg.create_item(
NewItemEntity {
item: ItemDetail::Tool (
Tool {
tool: item::tool::ToolType::Monomate,
})}).await?;
eg.add_item_note(&monomate.id, ItemNote::CharacterCreation {
character_id
}).await?;
let monofluid = eg.create_item(
NewItemEntity {
item: ItemDetail::Tool (
Tool {
tool: item::tool::ToolType::Monofluid,
})}).await?;
eg.add_item_note(&monofluid.id, ItemNote::CharacterCreation {
character_id
}).await?;
Ok((monomate, monofluid))
}})).await.into_iter().collect::<Result<Vec<_>, GatewayError>>()?.into_iter().unzip();
let inventory = InventoryEntity {
items: vec![InventoryItemEntity::Individual(weapon.clone()), InventoryItemEntity::Individual(armor.clone()), InventoryItemEntity::Individual(mag.clone()),

View File

@ -2,24 +2,17 @@ use libpso::character::character;
use crate::common::leveltable::CharacterStats;
use crate::entity::character::CharacterEntity;
use crate::ship::items::{CharacterInventory, CharacterBank};
use crate::entity::item::Meseta;
#[derive(Default)]
pub struct CharacterBytesBuilder<'a> {
character: Option<&'a CharacterEntity>,
stats: Option<&'a CharacterStats>,
level: Option<u32>,
meseta: Option<Meseta>,
}
impl<'a> Default for CharacterBytesBuilder<'a> {
fn default() -> CharacterBytesBuilder<'a> {
CharacterBytesBuilder {
character: None,
stats: None,
level: None,
}
}
}
impl<'a> CharacterBytesBuilder<'a> {
pub fn character(self, character: &'a CharacterEntity) -> CharacterBytesBuilder<'a> {
CharacterBytesBuilder {
@ -42,10 +35,18 @@ impl<'a> CharacterBytesBuilder<'a> {
}
}
pub fn meseta(self, meseta: Meseta) -> CharacterBytesBuilder<'a> {
CharacterBytesBuilder {
meseta: Some(meseta),
..self
}
}
pub fn build(self) -> character::Character {
let character = self.character.unwrap();
let stats = self.stats.unwrap();
let level = self.level.unwrap();
let meseta = self.meseta.unwrap();
character::Character {
name: libpso::utf8_to_utf16_array!(character.name, 16),
hp: stats.hp,
@ -70,7 +71,7 @@ impl<'a> CharacterBytesBuilder<'a> {
prop_y: character.appearance.prop_y,
config: character.config.as_bytes(),
techniques: character.techs.as_bytes(),
meseta: character.meseta,
meseta: meseta.0 as u32,
exp: character.exp,
..character::Character::default()
}
@ -78,10 +79,12 @@ impl<'a> CharacterBytesBuilder<'a> {
}
#[derive(Default)]
pub struct FullCharacterBytesBuilder<'a> {
character: Option<&'a CharacterEntity>,
stats: Option<&'a CharacterStats>,
level: Option<u32>,
meseta: Option<Meseta>,
inventory: Option<&'a CharacterInventory>,
bank: Option<&'a CharacterBank>,
key_config: Option<&'a [u8; 0x16C]>,
@ -91,24 +94,6 @@ pub struct FullCharacterBytesBuilder<'a> {
option_flags: Option<u32>,
}
impl<'a> Default for FullCharacterBytesBuilder<'a> {
fn default() -> FullCharacterBytesBuilder<'a> {
FullCharacterBytesBuilder {
character: None,
stats: None,
level: None,
inventory: None,
bank: None,
key_config: None,
joystick_config: None,
symbol_chat: None,
tech_menu: None,
option_flags: None,
}
}
}
impl<'a> FullCharacterBytesBuilder<'a> {
pub fn character(self, character: &'a CharacterEntity) -> FullCharacterBytesBuilder<'a> {
FullCharacterBytesBuilder {
@ -131,6 +116,13 @@ impl<'a> FullCharacterBytesBuilder<'a> {
}
}
pub fn meseta(self, meseta: Meseta) -> FullCharacterBytesBuilder<'a> {
FullCharacterBytesBuilder {
meseta: Some(meseta),
..self
}
}
pub fn inventory(self, inventory: &'a CharacterInventory) -> FullCharacterBytesBuilder<'a> {
FullCharacterBytesBuilder {
inventory: Some(inventory),
@ -184,6 +176,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
let character = self.character.unwrap();
let stats = self.stats.unwrap();
let level = self.level.unwrap();
let meseta = self.meseta.unwrap();
let inventory = self.inventory.unwrap();
let bank = self.bank.unwrap();
let key_config = self.key_config.unwrap();
@ -204,6 +197,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
.character(character)
.stats(stats)
.level(level - 1)
.meseta(meseta)
.build(),
inventory: character::Inventory {
item_count: inventory.count() as u8,

View File

@ -1,6 +1,6 @@
use crate::ship::items::ClientItemId;
use libpso::character::character;//::InventoryItem;
use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, ItemLocation, BankEntity, BankItemEntity, BankName};
use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, BankEntity, BankItemEntity, BankName};
use crate::entity::character::CharacterEntityId;
use crate::entity::item::tool::Tool;
use crate::ship::items::inventory::{InventoryItemHandle, InventoryItem};
@ -293,7 +293,7 @@ impl CharacterBank {
self.items.last()
}
pub fn as_bank_entity(&self, character_id: &CharacterEntityId, bank_name: &BankName) -> BankEntity {
pub fn as_bank_entity(&self, _character_id: &CharacterEntityId, _bank_name: &BankName) -> BankEntity {
BankEntity {
items: self.items.iter()
.map(|item| {
@ -301,26 +301,18 @@ impl CharacterBank {
BankItem::Individual(item) => {
BankItemEntity::Individual(ItemEntity {
id: item.entity_id,
location: ItemLocation::Bank {
character_id: *character_id,
name: bank_name.clone(),
},
item: item.item.clone(),
})
},
BankItem::Stacked(items) => {
BankItemEntity::Stacked(items.entity_ids.iter()
.map(|id| {
ItemEntity {
id: *id,
location: ItemLocation::Bank {
character_id: *character_id,
name: bank_name.clone(),
},
item: ItemDetail::Tool(items.tool)
}
})
.collect())
.map(|id| {
ItemEntity {
id: *id,
item: ItemDetail::Tool(items.tool)
}
})
.collect())
},
}
})

View File

@ -159,24 +159,24 @@ impl<'a> FloorItemHandle<'a> {
}
// TODO: floors should keep track of their own item_ids
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct RoomFloorItems(Vec<FloorItem>);
impl Default for RoomFloorItems {
fn default() -> RoomFloorItems {
RoomFloorItems(Vec::new())
}
}
impl RoomFloorItems {
pub fn add_item(&mut self, item: FloorItem) {
self.0.push(item);
}
pub fn remove_item(&mut self, item_id: &ClientItemId) {
self.0.retain(|item| item.item_id() != *item_id);
}
// TODO: &ClientItemId
pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&FloorItem> {
self.0.iter().find(|item| item.item_id() == item_id)
}
// TODO: &ClientItemId
pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option<FloorItemHandle> {
let index = self.0.iter().position(|item| item.item_id() == item_id)?;
Some(FloorItemHandle {

View File

@ -2,7 +2,7 @@ use std::cmp::Ordering;
use thiserror::Error;
use libpso::character::character;//::InventoryItem;
use crate::entity::character::CharacterEntityId;
use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, ItemLocation, InventoryEntity, InventoryItemEntity, EquippedEntity};
use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, InventoryEntity, InventoryItemEntity, EquippedEntity};
use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag;
use crate::entity::item::weapon::Weapon;
@ -87,6 +87,24 @@ pub enum InventoryItemAddToError {
pub enum InventoryAddError {
}
#[derive(Debug, Clone)]
pub enum YesThereIsSpace {
NewStack,
ExistingStack,
}
#[derive(Debug, Clone)]
pub enum NoThereIsNotSpace {
FullStack,
FullInventory,
}
#[derive(Debug, Clone)]
pub enum SpaceForStack {
Yes(YesThereIsSpace),
No(NoThereIsNotSpace),
}
impl InventoryItem {
pub fn entity_ids(&self) -> Vec<ItemEntityId> {
match self {
@ -220,6 +238,27 @@ impl InventoryItem {
_ => None
}
}
pub fn stacked(&self) -> Option<&StackedInventoryItem> {
match self {
InventoryItem::Stacked(ref stacked_inventory_item) => Some(stacked_inventory_item),
_ => None
}
}
pub fn stacked_mut(&mut self) -> Option<&mut StackedInventoryItem> {
match self {
InventoryItem::Stacked(ref mut stacked_inventory_item) => Some(stacked_inventory_item),
_ => None
}
}
pub fn mag(&self) -> Option<&Mag> {
match self {
InventoryItem::Individual(individual_inventory_item) => individual_inventory_item.mag(),
_ => None
}
}
}
@ -413,6 +452,62 @@ impl CharacterInventory {
self.items.len()
}
pub fn space_for_individual_item(&self) -> bool {
self.count() < INVENTORY_CAPACITY
}
pub fn space_for_stacked_item(&self, tool: &Tool, amount: usize) -> SpaceForStack {
let existing_item = self.items.iter()
.filter_map(|item| {
match item {
InventoryItem::Stacked(s_item) => {
Some(s_item)
},
_ => None
}
})
.find(|s_item| {
s_item.tool == *tool
});
match existing_item {
Some(item) => {
if item.count() + amount <= tool.tool.max_stack() {
SpaceForStack::Yes(YesThereIsSpace::ExistingStack)
}
else {
SpaceForStack::No(NoThereIsNotSpace::FullStack)
}
}
None => {
if self.count() < INVENTORY_CAPACITY {
SpaceForStack::Yes(YesThereIsSpace::NewStack)
}
else {
SpaceForStack::No(NoThereIsNotSpace::FullInventory)
}
}
}
}
pub fn stack_item_id(&self, tool: &Tool) -> Option<ClientItemId> {
self.items.iter()
.filter_map(|item| {
match item {
InventoryItem::Stacked(s_item) => {
Some(s_item)
},
_ => None
}
})
.find(|s_item| {
s_item.tool == *tool
})
.map(|item| {
item.item_id
})
}
pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option<InventoryItemHandle> {
let (slot, _) = self.items.iter()
.enumerate()
@ -506,10 +601,80 @@ impl CharacterInventory {
.next()
}
pub fn add_item(&mut self, item: InventoryItem) -> Result<(), InventoryAddError> { // TODO: errors
// TODO: check slot conflict?
pub fn take_stacked_item_by_id(&mut self, item_id: ClientItemId, amount: usize) -> Option<StackedInventoryItem> {
let idx = self.items
.iter_mut()
.position(|i| i.item_id() == item_id)?;
let item: &mut StackedInventoryItem = self.items.get_mut(idx)?.stacked_mut()?;
match item.entity_ids.len().cmp(&amount) {
Ordering::Equal => {
let item = self.items.remove(idx);
item.stacked().cloned()
},
Ordering::Greater => {
let entity_ids = item.entity_ids.drain(..amount).collect();
Some(StackedInventoryItem {
entity_ids,
tool: item.tool,
item_id: item.item_id,
})
},
Ordering::Less => {
None
}
}
}
pub fn add_item(&mut self, item: InventoryItem) {
self.items.push(item);
Ok(())
}
pub fn add_stacked_item(&mut self, mut item: StackedInventoryItem) {
let existing_item = self.items
.iter_mut()
.filter_map(|i| {
match i {
InventoryItem::Stacked(stacked) => {
Some(stacked)
},
_ => None
}
})
.find(|i| {
i.tool == item.tool
});
match existing_item {
Some(existing_item) => {
existing_item.entity_ids.append(&mut item.entity_ids)
},
None => {
self.items.push(InventoryItem::Stacked(item))
}
}
}
pub fn add_item_with_new_item_id(&mut self, item: InventoryItem, item_id: ClientItemId) {
match item {
InventoryItem::Individual(mut individual_inventory_item) => {
individual_inventory_item.item_id = item_id;
self.add_item(InventoryItem::Individual(individual_inventory_item));
},
InventoryItem::Stacked(mut stacked_inventory_item) => {
stacked_inventory_item.item_id = item_id;
self.add_stacked_item(stacked_inventory_item)
}
}
}
pub fn add_individual_floor_item(&mut self, floor_item: &IndividualFloorItem) -> &InventoryItem {
self.items.push(InventoryItem::Individual(IndividualInventoryItem {
entity_id: floor_item.entity_id,
item_id: floor_item.item_id,
item: floor_item.item.clone(),
}));
self.items.last().unwrap()
}
// TODO: should these pick up functions take floor_item as mut and remove the ids?
@ -532,6 +697,33 @@ impl CharacterInventory {
}
}
pub fn add_stacked_floor_item(&mut self, floor_item: &StackedFloorItem) {
let existing_item = self.items.iter_mut()
.filter_map(|item| {
match item {
InventoryItem::Stacked(s_item) => Some(s_item),
_ => None,
}
})
.find(|item| {
item.tool == floor_item.tool
});
match existing_item {
Some(item) => {
item.entity_ids.append(&mut floor_item.entity_ids.clone())
},
None => {
self.items.push(InventoryItem::Stacked(StackedInventoryItem {
entity_ids: floor_item.entity_ids.clone(),
item_id: floor_item.item_id,
tool: floor_item.tool,
}));
}
}
}
// TODO: can be simplified using find instead of position
pub fn pick_up_stacked_floor_item(&mut self, floor_item: &StackedFloorItem) -> Option<(&StackedInventoryItem, InventorySlot)> {
let existing_stack_position = self.items.iter()
@ -705,7 +897,7 @@ impl CharacterInventory {
}
}
pub fn as_inventory_entity(&self, character_id: &CharacterEntityId) -> InventoryEntity {
pub fn as_inventory_entity(&self, _character_id: &CharacterEntityId) -> InventoryEntity {
InventoryEntity {
items: self.items.iter()
.map(|item| {
@ -713,9 +905,6 @@ impl CharacterInventory {
InventoryItem::Individual(item) => {
InventoryItemEntity::Individual(ItemEntity {
id: item.entity_id,
location: ItemLocation::Inventory {
character_id: *character_id,
},
item: item.item.clone(),
})
},
@ -724,9 +913,6 @@ impl CharacterInventory {
.map(|id| {
ItemEntity {
id: *id,
location: ItemLocation::Inventory {
character_id: *character_id,
},
item: ItemDetail::Tool(items.tool)
}
})

File diff suppressed because it is too large Load Diff

View File

@ -2,10 +2,11 @@ mod bank;
mod floor;
pub mod inventory;
mod manager;
pub mod transaction;
pub mod use_tool;
use serde::{Serialize, Deserialize};
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, derive_more::Display)]
pub struct ClientItemId(pub u32);
// TODO: remove these and fix use statements in the rest of the codebase

View File

@ -0,0 +1,337 @@
use crate::entity::gateway::EntityGateway;
use thiserror::Error;
use crate::ship::items::manager::{ItemManager, ItemManagerError};
use crate::entity::gateway::GatewayError;
#[derive(Error, Debug)]
pub enum TransactionCommitError {
#[error("transaction commit gateway error {0}")]
Gateway(#[from] GatewayError),
#[error("transaction commit itemmanager error {0}")]
ItemManager(#[from] ItemManagerError),
}
#[async_trait::async_trait]
pub trait ItemAction<EG: EntityGateway>: std::marker::Send + std::marker::Sync + std::fmt::Debug {
async fn commit(&self, manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError>;
}
pub struct ItemTransactionActions<'a, EG: EntityGateway> {
action_queue: Vec<Box<dyn ItemAction<EG>>>,
pub manager: &'a ItemManager,
}
impl<'a, EG: EntityGateway> ItemTransactionActions<'a, EG> {
fn new(manager: &'a ItemManager) -> ItemTransactionActions<'a, EG> {
ItemTransactionActions {
action_queue: Vec::new(),
manager
}
}
pub fn action(&mut self, action: Box<dyn ItemAction<EG>>) {
self.action_queue.push(action)
}
}
pub struct ItemTransaction<'a, T, EG: EntityGateway> {
data: T,
actions: ItemTransactionActions<'a, EG>,
}
impl<'a, T, EG: EntityGateway> ItemTransaction<'a, T, EG> {
pub fn new(manager: &'a ItemManager, arg: T) -> ItemTransaction<'a, T, EG> {
ItemTransaction {
data: arg,
actions: ItemTransactionActions::new(manager),
}
}
pub fn act<E: std::fmt::Debug, U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> FinalizedItemTransaction<U, E, EG> {
match action(&mut self.actions, &self.data) {
Ok(k) => {
FinalizedItemTransaction {
value: Ok(k),
action_queue: self.actions.action_queue,
}
},
Err(err) => {
FinalizedItemTransaction {
value: Err(err),
action_queue: Vec::new(),
}
}
}
}
}
#[derive(Error, Debug)]
pub enum TransactionError<E: std::fmt::Debug> {
#[error("transaction action error {0:?}")]
Action(E),
#[error("transaction commit error {0}")]
Commit(#[from] TransactionCommitError),
}
// this only exists to drop the ItemManager borrow of ItemTransaction so a mutable ItemTransaction can be passed in later
pub struct FinalizedItemTransaction<T, E: std::fmt::Debug, EG: EntityGateway> {
value: Result<T, E>,
action_queue: Vec<Box<dyn ItemAction<EG>>>,
}
impl<T, E: std::fmt::Debug, EG: EntityGateway> FinalizedItemTransaction<T, E, EG> {
pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<T, TransactionError<E>> {
match self.value {
Ok(value) => {
for action in self.action_queue.into_iter() {
// TODO: better handle rolling back if this ever errors out
action.commit(item_manager, entity_gateway).await.map_err(|err| TransactionError::Commit(err))?;
}
Ok(value)
},
Err(err) => Err(TransactionError::Action(err)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::entity::account::{UserAccountId, NewUserAccountEntity, UserAccountEntity};
use crate::entity::character::{NewCharacterEntity, CharacterEntity};
use crate::entity::gateway::GatewayError;
use thiserror::Error;
#[async_std::test]
async fn test_item_transaction() {
#[derive(Debug)]
struct DummyAction1 {
name: String,
}
#[derive(Debug)]
struct DummyAction2 {
value: u32,
}
#[derive(Error, Debug)]
#[error("")]
enum DummyError {
Error
}
#[derive(Default, Clone)]
struct DummyGateway {
d1_set: String,
d2_inc: u32,
}
#[async_trait::async_trait]
impl EntityGateway for DummyGateway {
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
self.d1_set = user.username;
Ok(UserAccountEntity::default())
}
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
self.d2_inc += char.slot;
Ok(CharacterEntity::default())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 {
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
item_manager.id_counter = 55555;
entity_gateway.create_user(NewUserAccountEntity {
username: self.name.clone(),
..NewUserAccountEntity::default()
})
.await?;
Ok(())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 {
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
item_manager.id_counter += self.value;
entity_gateway.create_character(NewCharacterEntity {
slot: self.value,
..NewCharacterEntity::new(UserAccountId(0))
})
.await?;
Ok(())
}
}
let mut item_manager = ItemManager::default();
let mut entity_gateway = DummyGateway::default();
let result = ItemTransaction::new(&item_manager, 12)
.act(|it, k| {
it.action(Box::new(DummyAction1 {name: "asdf".into()}));
it.action(Box::new(DummyAction2 {value: 11}));
it.action(Box::new(DummyAction2 {value: *k}));
if *k == 99 {
return Err(DummyError::Error)
}
Ok(String::from("hello"))
})
.commit(&mut item_manager, &mut entity_gateway)
.await;
assert!(entity_gateway.d1_set == "asdf");
assert!(entity_gateway.d2_inc == 23);
assert!(item_manager.id_counter == 55578);
assert!(result.unwrap() == "hello");
}
#[async_std::test]
async fn test_item_transaction_with_action_error() {
#[derive(Debug)]
struct DummyAction1 {
}
#[derive(Debug)]
struct DummyAction2 {
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("")]
enum DummyError {
Error
}
#[derive(Default, Clone)]
struct DummyGateway {
d1_set: String,
d2_inc: u32,
}
#[async_trait::async_trait]
impl EntityGateway for DummyGateway {
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
self.d2_inc += char.slot;
Ok(CharacterEntity::default())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 {
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
entity_gateway.create_character(NewCharacterEntity {
slot: 1,
..NewCharacterEntity::new(UserAccountId(0))
})
.await?;
Ok(())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 {
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
entity_gateway.create_character(NewCharacterEntity {
slot: 1,
..NewCharacterEntity::new(UserAccountId(0))
})
.await?;
Ok(())
}
}
let mut item_manager = ItemManager::default();
let mut entity_gateway = DummyGateway::default();
let result = ItemTransaction::new(&item_manager, 12)
.act(|it, _| -> Result<(), _> {
it.action(Box::new(DummyAction1 {}));
it.action(Box::new(DummyAction2 {}));
it.action(Box::new(DummyAction2 {}));
Err(DummyError::Error)
})
.commit(&mut item_manager, &mut entity_gateway)
.await;
assert!(entity_gateway.d2_inc == 0);
assert!(matches!(result, Err(TransactionError::Action(DummyError::Error))));
}
#[async_std::test]
async fn test_item_transaction_with_commit_error() {
#[derive(Debug)]
struct DummyAction1 {
}
#[derive(Debug)]
struct DummyAction2 {
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("")]
enum DummyError {
}
#[derive(Default, Clone)]
struct DummyGateway {
d1_set: String,
d2_inc: u32,
}
#[async_trait::async_trait]
impl EntityGateway for DummyGateway {
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
self.d2_inc += char.slot;
Ok(CharacterEntity::default())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 {
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
entity_gateway.create_character(NewCharacterEntity {
slot: 1,
..NewCharacterEntity::new(UserAccountId(0))
})
.await?;
Err(GatewayError::Error.into())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 {
async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> {
entity_gateway.create_character(NewCharacterEntity {
slot: 1,
..NewCharacterEntity::new(UserAccountId(0))
})
.await?;
Ok(())
}
}
let mut item_manager = ItemManager::default();
let mut entity_gateway = DummyGateway::default();
let result = ItemTransaction::new(&item_manager, 12)
.act(|it, _| -> Result<_, DummyError> {
it.action(Box::new(DummyAction1 {}));
it.action(Box::new(DummyAction2 {}));
it.action(Box::new(DummyAction2 {}));
Ok(())
})
.commit(&mut item_manager, &mut entity_gateway)
.await;
// in an ideal world this would be 0 as rollbacks would occur
assert!(entity_gateway.d2_inc == 1);
assert!(matches!(result, Err(TransactionError::Commit(TransactionCommitError::Gateway(GatewayError::Error)))));
}
}

View File

@ -15,7 +15,7 @@ pub enum AreaType {
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct LobbyId(pub usize);
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub struct RoomId(pub usize);
impl LobbyId {
@ -26,7 +26,7 @@ impl LobbyId {
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("create room")]
pub enum CreateRoomError {
NoOpenSlots,
ClientInAreaAlready,
@ -34,7 +34,7 @@ pub enum CreateRoomError {
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("join room")]
pub enum JoinRoomError {
RoomDoesNotExist,
RoomFull,
@ -42,7 +42,7 @@ pub enum JoinRoomError {
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("join lobby")]
pub enum JoinLobbyError {
LobbyDoesNotExist,
LobbyFull,
@ -50,7 +50,7 @@ pub enum JoinLobbyError {
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("get area")]
pub enum GetAreaError {
NotInRoom,
NotInLobby,
@ -58,28 +58,28 @@ pub enum GetAreaError {
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("client removal")]
pub enum ClientRemovalError {
ClientNotInArea,
InvalidArea,
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("get clients")]
pub enum GetClientsError {
InvalidClient,
InvalidArea,
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("get neighbor")]
pub enum GetNeighborError {
InvalidClient,
InvalidArea,
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("get leader")]
pub enum GetLeaderError {
InvalidClient,
InvalidArea,
@ -87,7 +87,7 @@ pub enum GetLeaderError {
}
#[derive(Error, Debug, PartialEq)]
#[error("")]
#[error("clientlocation")]
pub enum ClientLocationError {
CreateRoomError(#[from] CreateRoomError),
JoinRoomError(#[from] JoinRoomError),
@ -109,6 +109,12 @@ impl LocalClientId {
}
}
impl PartialEq<u8> for LocalClientId {
fn eq(&self, other: &u8) -> bool {
self.0 == *other as usize
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct AreaClient {
pub client: ClientId,

View File

@ -287,18 +287,11 @@ impl MapAreaLookup {
}
#[derive(Default)]
pub struct MapAreaLookupBuilder {
map_areas: HashMap<u16, MapArea>,
}
impl Default for MapAreaLookupBuilder {
fn default() -> MapAreaLookupBuilder {
MapAreaLookupBuilder {
map_areas: HashMap::new()
}
}
}
impl MapAreaLookupBuilder {
pub fn add(mut self, value: u16, map_area: MapArea) -> MapAreaLookupBuilder {
self.map_areas.insert(value, map_area);

View File

@ -14,17 +14,17 @@ pub struct RawMapEnemy {
id: u32,
_unknown1: u16,
pub children: u16,
map_area: u16,
_map_area: u16,
_unknown4: u16,
section: u16,
wave_idd: u16,
wave_id: u32,
x: f32,
y: f32,
z: f32,
xrot: u32,
yrot: u32,
zrot: u32,
_section: u16,
_wave_idd: u16,
_wave_id: u32,
_x: f32,
_y: f32,
_z: f32,
_xrot: u32,
_yrot: u32,
_zrot: u32,
_field1: u32,
field2: u32,
_field3: u32,
@ -40,17 +40,17 @@ impl RawMapEnemy {
id: cursor.read_u32::<LittleEndian>()?,
_unknown1: cursor.read_u16::<LittleEndian>()?,
children: cursor.read_u16::<LittleEndian>()?,
map_area: cursor.read_u16::<LittleEndian>()?,
_map_area: cursor.read_u16::<LittleEndian>()?,
_unknown4: cursor.read_u16::<LittleEndian>()?,
section: cursor.read_u16::<LittleEndian>()?,
wave_idd: cursor.read_u16::<LittleEndian>()?,
wave_id: cursor.read_u32::<LittleEndian>()?,
x: cursor.read_f32::<LittleEndian>()?,
y: cursor.read_f32::<LittleEndian>()?,
z: cursor.read_f32::<LittleEndian>()?,
xrot: cursor.read_u32::<LittleEndian>()?,
yrot: cursor.read_u32::<LittleEndian>()?,
zrot: cursor.read_u32::<LittleEndian>()?,
_section: cursor.read_u16::<LittleEndian>()?,
_wave_idd: cursor.read_u16::<LittleEndian>()?,
_wave_id: cursor.read_u32::<LittleEndian>()?,
_x: cursor.read_f32::<LittleEndian>()?,
_y: cursor.read_f32::<LittleEndian>()?,
_z: cursor.read_f32::<LittleEndian>()?,
_xrot: cursor.read_u32::<LittleEndian>()?,
_yrot: cursor.read_u32::<LittleEndian>()?,
_zrot: cursor.read_u32::<LittleEndian>()?,
_field1: cursor.read_u32::<LittleEndian>()?,
field2: cursor.read_u32::<LittleEndian>()?,
_field3: cursor.read_u32::<LittleEndian>()?,
@ -75,7 +75,7 @@ pub enum MapEnemyError {
pub struct MapEnemy {
pub monster: MonsterType,
pub map_area: MapArea,
hp: u32,
_hp: u32,
// TODO: other stats from battleparam
pub player_hit: [bool; 4],
pub dropped_item: bool,
@ -254,7 +254,7 @@ impl MapEnemy {
Ok(MapEnemy {
monster,
map_area: *map_area,
hp: 0,
_hp: 0,
dropped_item: false,
gave_exp: false,
player_hit: [false; 4],
@ -265,7 +265,7 @@ impl MapEnemy {
MapEnemy {
monster,
map_area,
hp: 0,
_hp: 0,
dropped_item: false,
gave_exp: false,
player_hit: [false; 4],

View File

@ -13,50 +13,50 @@ use crate::ship::map::*;
#[derive(Debug, Copy, Clone)]
pub struct RawMapObject {
otype: u16,
unknown1: u16,
unknown2: u32,
id: u16,
group: u16,
section: u16,
unknown3: u16,
x: f32,
y: f32,
z: f32,
xrot: u32,
yrot: u32,
zrot: u32,
_unknown1: u16,
_unknown2: u32,
_id: u16,
_group: u16,
_section: u16,
_unknown3: u16,
_x: f32,
_y: f32,
_z: f32,
_xrot: u32,
_yrot: u32,
_zrot: u32,
field1: f32,
field2: f32,
field3: f32,
field4: u32,
field5: u32,
field6: u32,
field7: u32,
_field5: u32,
_field6: u32,
_field7: u32,
}
impl RawMapObject {
pub fn from_byte_stream<R: Read>(cursor: &mut R) -> Result<RawMapObject, std::io::Error> {
Ok(RawMapObject {
otype: cursor.read_u16::<LittleEndian>()?,
unknown1: cursor.read_u16::<LittleEndian>()?,
unknown2: cursor.read_u32::<LittleEndian>()?,
id: cursor.read_u16::<LittleEndian>()?,
group: cursor.read_u16::<LittleEndian>()?,
section: cursor.read_u16::<LittleEndian>()?,
unknown3: cursor.read_u16::<LittleEndian>()?,
x: cursor.read_f32::<LittleEndian>()?,
y: cursor.read_f32::<LittleEndian>()?,
z: cursor.read_f32::<LittleEndian>()?,
xrot: cursor.read_u32::<LittleEndian>()?,
yrot: cursor.read_u32::<LittleEndian>()?,
zrot: cursor.read_u32::<LittleEndian>()?,
_unknown1: cursor.read_u16::<LittleEndian>()?,
_unknown2: cursor.read_u32::<LittleEndian>()?,
_id: cursor.read_u16::<LittleEndian>()?,
_group: cursor.read_u16::<LittleEndian>()?,
_section: cursor.read_u16::<LittleEndian>()?,
_unknown3: cursor.read_u16::<LittleEndian>()?,
_x: cursor.read_f32::<LittleEndian>()?,
_y: cursor.read_f32::<LittleEndian>()?,
_z: cursor.read_f32::<LittleEndian>()?,
_xrot: cursor.read_u32::<LittleEndian>()?,
_yrot: cursor.read_u32::<LittleEndian>()?,
_zrot: cursor.read_u32::<LittleEndian>()?,
field1: cursor.read_f32::<LittleEndian>()?,
field2: cursor.read_f32::<LittleEndian>()?,
field3: cursor.read_f32::<LittleEndian>()?,
field4: cursor.read_u32::<LittleEndian>()?,
field5: cursor.read_u32::<LittleEndian>()?,
field6: cursor.read_u32::<LittleEndian>()?,
field7: cursor.read_u32::<LittleEndian>()?,
_field5: cursor.read_u32::<LittleEndian>()?,
_field6: cursor.read_u32::<LittleEndian>()?,
_field7: cursor.read_u32::<LittleEndian>()?,
})
}
}

View File

@ -11,3 +11,4 @@ pub mod drops;
pub mod packet;
pub mod quests;
pub mod shops;
pub mod trade;

View File

@ -27,7 +27,8 @@ pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result<ItemDr
})
}
pub fn create_item(area_client: AreaClient, item_id: ClientItemId, item: &item::ItemDetail) -> Result<CreateItem, ShipError> {
// TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed
pub fn create_individual_item(area_client: AreaClient, item_id: ClientItemId, item: &item::ItemDetail) -> Result<CreateItem, ShipError> {
let bytes = item.as_client_bytes();
Ok(CreateItem {
client: area_client.local_client.id(),
@ -39,6 +40,31 @@ pub fn create_item(area_client: AreaClient, item_id: ClientItemId, item: &item::
})
}
// TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed
pub fn create_stacked_item(area_client: AreaClient, item_id: ClientItemId, tool: &item::tool::Tool, amount: usize) -> Result<CreateItem, ShipError> {
let bytes = tool.as_stacked_bytes(amount);
Ok(CreateItem {
client: area_client.local_client.id(),
target: 0,
item_data: bytes[0..12].try_into()?,
item_id: item_id.0,
item_data2: bytes[12..16].try_into()?,
unknown: 0,
})
}
pub fn create_meseta(area_client: AreaClient, amount: usize) -> CreateItem {
let bytes: [u8; 12] = [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
CreateItem {
client: area_client.local_client.id(),
target: 0,
item_data: bytes,
item_id: 0xFFFFFFFF,
item_data2: u32::to_le_bytes(amount as u32),
unknown: 0,
}
}
pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &InventoryItem) -> Result<CreateItem, ShipError> {
let bytes = item.as_client_bytes();
Ok(CreateItem {
@ -142,6 +168,15 @@ pub fn player_no_longer_has_item(area_client: AreaClient, item_id: ClientItemId,
}
}
pub fn player_no_longer_has_meseta(area_client: AreaClient, amount: u32) -> PlayerNoLongerHasItem {
PlayerNoLongerHasItem {
client: area_client.local_client.id(),
target: 0,
item_id: 0xFFFFFFFF,
amount,
}
}
pub fn shop_list<I: ShopItem>(shop_type: u8, items: &[I]) -> ShopList {
let items = items.iter()
.enumerate()

View File

@ -26,10 +26,12 @@ pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -
pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_manager: &ItemManager, level_table: &CharacterLevelTable) -> PlayerInfo {
let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp);
let inventory = item_manager.get_character_inventory(&client.character).unwrap();
let meseta = item_manager.get_character_meseta(&client.character.id).unwrap();
let character = CharacterBytesBuilder::default()
.character(&client.character)
.stats(&stats)
.level(level - 1)
.meseta(*meseta)
.build();
PlayerInfo {
header: player_header(tag, client, area_client),

View File

View File

@ -137,8 +137,8 @@ where
let (item, floor_type) = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?;
let remove_item = builder::message::remove_item_from_floor(area_client, item)?;
let create_item = match item {
FloorItem::Individual(individual_floor_item) => Some(builder::message::create_item(area_client, item.item_id(), &individual_floor_item.item)?),
FloorItem::Stacked(stacked_floor_item) => Some(builder::message::create_item(area_client, item.item_id(), &item::ItemDetail::Tool(stacked_floor_item.tool))?),
FloorItem::Individual(individual_floor_item) => Some(builder::message::create_individual_item(area_client, item.item_id(), &individual_floor_item.item)?),
FloorItem::Stacked(stacked_floor_item) => Some(builder::message::create_stacked_item(area_client, item.item_id(), &stacked_floor_item.tool, stacked_floor_item.count())?),
FloorItem::Meseta(_) => None,
//_ => Some(builder::message::create_item(area_client, &item)?),
};
@ -232,8 +232,8 @@ pub async fn send_bank_list(id: ClientId,
{
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
let bank_items = item_manager.get_character_bank(&client.character)?;
let bank_items_pkt = builder::message::bank_item_list(bank_items, client.character.bank_meseta);
let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?;
let bank_items_pkt = builder::message::bank_item_list(bank_items, bank_meseta.0);
Ok(Box::new(vec![(id, SendShipPacket::BankItemList(bank_items_pkt))].into_iter()))
}
@ -252,11 +252,16 @@ where
let other_clients_in_area = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
let bank_action_pkts = match bank_interaction.action {
BANK_ACTION_DEPOSIT => {
let character_meseta = item_manager.get_character_meseta(&client.character.id)?;
let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?;
if bank_interaction.item_id == 0xFFFFFFFF {
if client.character.meseta >= bank_interaction.meseta_amount && (bank_interaction.meseta_amount + client.character.bank_meseta) <= BANK_MESETA_CAPACITY {
client.character.meseta -= bank_interaction.meseta_amount;
client.character.bank_meseta += bank_interaction.meseta_amount;
entity_gateway.save_character(&client.character).await?;
if character_meseta.0 >= bank_interaction.meseta_amount && (bank_interaction.meseta_amount + bank_meseta.0) <= BANK_MESETA_CAPACITY {
let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?;
character_meseta.0 -= bank_interaction.meseta_amount;
bank_meseta.0 += bank_interaction.meseta_amount;
entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?;
// TODO: BankName
entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?;
}
Vec::new()
}
@ -267,11 +272,16 @@ where
}
},
BANK_ACTION_WITHDRAW => {
let character_meseta = item_manager.get_character_meseta(&client.character.id)?;
let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?;
if bank_interaction.item_id == 0xFFFFFFFF {
if client.character.meseta + bank_interaction.meseta_amount <= INVENTORY_MESETA_CAPACITY {
client.character.meseta += bank_interaction.meseta_amount;
client.character.bank_meseta -= bank_interaction.meseta_amount;
entity_gateway.save_character(&client.character).await?;
if (bank_meseta.0 >= bank_interaction.meseta_amount) && (character_meseta.0 + bank_interaction.meseta_amount <= INVENTORY_MESETA_CAPACITY) {
let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?;
character_meseta.0 += bank_interaction.meseta_amount;
bank_meseta.0 -= bank_interaction.meseta_amount;
entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?;
// TODO: BankName
entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?;
}
Vec::new()
}
@ -370,12 +380,13 @@ where
}
};
if client.character.meseta < item.price() as u32 {
let character_meseta = item_manager.get_character_meseta_mut(&client.character.id)?;
if character_meseta.0 < item.price() as u32 {
return Err(ShipError::ShopError.into())
}
client.character.meseta -= item.price() as u32;
entity_gateway.save_character(&client.character).await?;
character_meseta.0 -= item.price() as u32;
entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?;
let inventory_item = item_manager.player_buys_item(entity_gateway, &client.character, item, ClientItemId(buy_item.item_id), buy_item.amount as usize).await?;
let create = builder::message::create_withdrawn_inventory_item(area_client, inventory_item)?;
@ -447,8 +458,9 @@ where
grind: grind_mod,
});
client.character.meseta -= 100;
entity_gateway.save_character(&client.character).await?;
let character_meseta = item_manager.get_character_meseta_mut(&client.character.id)?;
character_meseta.0 -= 100;
entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?;
let preview_pkt = builder::message::tek_preview(ClientItemId(tek_request.item_id), &weapon)?;
@ -480,7 +492,7 @@ where
};
let weapon = item_manager.replace_item_with_tekked(entity_gateway, &client.character, item_id, modifier).await?;
let create_item_pkt = builder::message::create_item(area_client, item_id, &item::ItemDetail::Weapon(weapon))?;
let create_item_pkt = builder::message::create_individual_item(area_client, item_id, &item::ItemDetail::Weapon(weapon))?;
let neighbors = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?;
Ok(Box::new(neighbors.into_iter()

View File

@ -22,12 +22,14 @@ pub fn block_selected(id: ClientId,
let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp);
let inventory = item_manager.get_character_inventory(&client.character).unwrap();
let meseta = item_manager.get_character_meseta(&client.character.id).unwrap();
let bank = item_manager.get_character_bank(&client.character).unwrap();
let fc = FullCharacterBytesBuilder::default()
.character(&client.character)
.stats(&stats)
.level(level)
.meseta(*meseta)
.inventory(inventory)
.bank(bank)
.key_config(&client.settings.settings.key_config)

View File

@ -135,13 +135,26 @@ where
let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, no_longer_has_item.amount as u32).await?;
let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?;
let no_longer_has_meseta_pkt = builder::message::player_no_longer_has_meseta(area_client, no_longer_has_item.amount as u32);
client.item_drop_location = None;
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
Ok(Box::new(clients_in_area.into_iter()
.map(move |c| {
(c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_meseta_pkt.clone()))))
})))
std::iter::once((c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_meseta_pkt.clone())))))
.chain(
if c.client != id {
Box::new(std::iter::once(
(c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(no_longer_has_meseta_pkt.clone()))))
)) as Box<dyn Iterator<Item = _> + Send>
}
else {
Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>
}
)
})
.flatten()
))
}
else {
let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, no_longer_has_item.amount as usize).await?;
@ -181,7 +194,7 @@ pub fn update_player_position(id: ClientId,
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
.as_ref()
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
match &message.msg {
GameMessage::PlayerChangedMap(p) => {
client.x = p.x;
@ -245,18 +258,22 @@ pub fn update_player_position(id: ClientId,
pub async fn charge_attack<EG>(id: ClientId,
charge: &ChargeAttack,
clients: &mut Clients,
entity_gateway: &mut EG)
entity_gateway: &mut EG,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
{
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
if client.character.meseta >= charge.meseta {
client.character.meseta -= charge.meseta;
entity_gateway.save_character(&client.character).await?;
let meseta = item_manager.get_character_meseta_mut(&client.character.id)?;
if meseta.0 >= charge.meseta {
meseta.0 -= charge.meseta;
entity_gateway.set_character_meseta(&client.character.id, *meseta).await?;
// TODO: this should probably echo the packet
Ok(Box::new(None.into_iter()))
} else {
Err(ShipError::NotEnoughMeseta(id, client.character.meseta).into())
Err(ShipError::NotEnoughMeseta(id, meseta.0).into())
}
}
@ -280,18 +297,21 @@ where
pub async fn player_used_medical_center<EG>(id: ClientId,
_pumc: &PlayerUsedMedicalCenter, // not needed?
entity_gateway: &mut EG,
clients: &mut Clients)
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
{
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
if client.character.meseta >= 10 {
client.character.meseta -= 10;
entity_gateway.save_character(&client.character).await?;
let meseta = item_manager.get_character_meseta_mut(&client.character.id)?;
if meseta.0 >= 10 {
meseta.0 -= 10;
entity_gateway.set_character_meseta(&client.character.id, *meseta).await?;
// TODO: this should probably echo the packet
Ok(Box::new(None.into_iter()))
} else {
Err(ShipError::NotEnoughMeseta(id, client.character.meseta).into())
Err(ShipError::NotEnoughMeseta(id, meseta.0).into())
}
}

View File

@ -7,3 +7,4 @@ pub mod room;
pub mod settings;
pub mod quest;
pub mod ship;
pub mod trade;

View File

@ -0,0 +1,544 @@
use std::convert::TryInto;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients};
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, ItemToTradeDetail};
use crate::ship::items::inventory::InventoryItem;
use crate::ship::trade::{TradeItem, TradeState, TradeStatus};
use crate::entity::gateway::EntityGateway;
use crate::ship::packet::builder;
pub const MESETA_ITEM_ID: ClientItemId = ClientItemId(0xFFFFFF01);
pub const OTHER_MESETA_ITEM_ID: ClientItemId = ClientItemId(0xFFFFFFFF);
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum TradeError {
#[error("no partner")]
CouldNotFindTradePartner,
#[error("invalid item id")]
InvalidItemId(ClientItemId),
#[error("item does not match id")]
ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]),
#[error("invalid stack {1}")]
InvalidStackAmount(ClientItemId, usize),
#[error("not in trade menu")]
NotInTradeMenu,
#[error("trade menu at an invalid point")]
MismatchedStatus,
#[error("no space in inventory")]
NoInventorySpace,
#[error("no space in stack")]
NoStackSpace,
#[error("invalid meseta amount")]
InvalidMeseta,
#[error("tried starting a trade while in one already")]
ClientAlreadyInTrade,
#[error("tried starting a trade while with player already in a trade")]
OtherAlreadyInTrade,
#[error("tried to trade item not specified in trade request")]
SketchyTrade,
#[error("items in trade window and items attempted to trade do not match")]
MismatchedTradeItems,
}
// TODO: remove target
pub async fn trade_request(id: ClientId,
trade_request: &TradeRequest,
target: u32,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
trades: &mut TradeState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
{
let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet
match trade_request.trade {
TradeRequestCommand::Initialize(ref act, _meseta) => {
match act {
TradeRequestInitializeCommand::Initialize => {
if trades.in_trade(&id) {
return Err(TradeError::ClientAlreadyInTrade.into())
}
let trade_partner = client_location.get_client_neighbors(id)?
.into_iter()
.find(|ac| {
ac.local_client.id() == target as u8 //trade_request.client
})
.ok_or(TradeError::CouldNotFindTradePartner)?;
if trades.in_trade(&trade_partner.client) {
return Err(TradeError::OtherAlreadyInTrade.into())
}
trades.new_trade(&id, &trade_partner.client);
Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
})))
},
TradeRequestInitializeCommand::Respond => {
Ok(trades
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
if this.status == TradeStatus::ReceivedRequest && other.status == TradeStatus::SentRequest {
this.status = TradeStatus::Trading;
other.status = TradeStatus::Trading;
let trade_request = trade_request.clone();
Some(Box::new(client_location.get_all_clients_by_client(id).ok()?.into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
})))
}
else {
None
}
})?
.unwrap_or_else(|| -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
trades.remove_trade(&id);
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
}))
}
}
},
TradeRequestCommand::AddItem(item_id, amount) => {
Ok(trades
.with(&id, |this, other| -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading {
let client = clients.get(&this.client()).ok_or_else(|| ShipError::ClientNotFound(this.client()))?;
let inventory = item_manager.get_character_inventory(&client.character)?;
if ClientItemId(item_id) == MESETA_ITEM_ID {
this.meseta += amount as usize;
}
else {
let item = inventory.get_item_by_id(ClientItemId(item_id)).ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item_id)))?;
match item {
InventoryItem::Individual(_) => {
this.items.push(TradeItem::Individual(ClientItemId(item_id)));
},
InventoryItem::Stacked(stacked_item) => {
if stacked_item.count() < amount as usize {
return Err(TradeError::InvalidStackAmount(ClientItemId(item_id), amount as usize).into());
}
this.items.push(TradeItem::Stacked(ClientItemId(item_id), amount as usize));
},
}
}
let trade_request = trade_request.clone();
Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
})))
}
else {
Err(TradeError::MismatchedStatus.into())
}
})?
.unwrap_or_else(|_err| {
trades.remove_trade(&id);
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
}))
},
TradeRequestCommand::RemoveItem(item_id, amount) => {
Ok(trades
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading {
let client = clients.get(&this.client())?; //.ok_or(ShipError::ClientNotFound(id)).ok()?;
let inventory = item_manager.get_character_inventory(&client.character).ok()?;
if ClientItemId(item_id) == MESETA_ITEM_ID {
this.meseta -= amount as usize;
}
else {
let item = inventory.get_item_by_id(ClientItemId(item_id))?;
match item {
InventoryItem::Individual(_) => {
this.items.retain(|item| {
item.item_id() != ClientItemId(item_id)
})
},
InventoryItem::Stacked(_stacked_item) => {
let trade_item_index = this.items.iter()
.position(|item| {
item.item_id() == ClientItemId(item_id)
})?;
match this.items[trade_item_index].stacked()?.1.cmp(&(amount as usize)) {
std::cmp::Ordering::Greater => {
*this.items[trade_item_index].stacked_mut()?.1 -= amount as usize;
},
std::cmp::Ordering::Equal => {
this.items.remove(trade_item_index);
},
std::cmp::Ordering::Less => {
return None
}
}
},
}
}
let trade_request = trade_request.clone();
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
})))
}
else {
None
}
})?
.unwrap_or_else(|| {
trades.remove_trade(&id);
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
}))
},
TradeRequestCommand::Confirm => {
Ok(trades
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
if status_is(&this.status, &[TradeStatus::Trading]) && status_is(&other.status, &[TradeStatus::Trading, TradeStatus::Confirmed]) {
this.status = TradeStatus::Confirmed;
let trade_request = trade_request.clone();
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
})))
}
else {
None
}
})?
.unwrap_or_else(|| {
trades.remove_trade(&id);
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
}))
},
TradeRequestCommand::FinalConfirm => {
Ok(trades
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
if this.status == TradeStatus::Confirmed && (other.status == TradeStatus::Confirmed || other.status == TradeStatus::FinalConfirm) {
this.status = TradeStatus::FinalConfirm;
let trade_request = trade_request.clone();
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
})))
}
else {
None
}
})?
.unwrap_or_else(|| {
trades.remove_trade(&id);
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
}))
},
TradeRequestCommand::Cancel => {
trades.remove_trade(&id);
Ok(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
}
}
}
fn status_is<const N: usize>(status: &TradeStatus, statuses: &[TradeStatus; N]) -> bool {
statuses.iter().any(|s| s == status)
}
fn status_is_not<const N: usize>(status: &TradeStatus, statuses: &[TradeStatus; N]) -> bool {
!status_is(status, statuses)
}
pub async fn inner_items_to_trade(id: ClientId,
items_to_trade: &ItemsToTrade,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
trades: &mut TradeState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
{
Ok(trades
.with(&id, |this, other| -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) {
return Err(TradeError::MismatchedStatus.into())
}
let client = clients.get(&this.client()).ok_or_else(|| ShipError::ClientNotFound(this.client()))?;
let other_client = clients.get(&other.client()).ok_or_else(|| ShipError::ClientNotFound(other.client()))?;
let inventory = item_manager.get_character_inventory(&client.character)?;
if items_to_trade.count as usize != (this.items.len() + (if this.meseta != 0 { 1 } else { 0 })) {
return Err(TradeError::MismatchedTradeItems.into())
}
items_to_trade.items
.iter()
.take(items_to_trade.count as usize)
.map(|item| {
if ClientItemId(item.item_id) == OTHER_MESETA_ITEM_ID {
if item.item_data[0] != 4 {
return Err(TradeError::InvalidItemId(ClientItemId(item.item_id)).into())
}
let amount = u32::from_le_bytes(item.item_data2);
let character_meseta = item_manager.get_character_meseta(&client.character.id).map_err(|_| TradeError::InvalidMeseta)?;
let other_character_meseta = item_manager.get_character_meseta(&other_client.character.id).map_err(|_| TradeError::InvalidMeseta)?;
if amount > character_meseta.0 {
return Err(TradeError::InvalidMeseta.into())
}
if (amount + other_character_meseta.0) > 999999 {
return Err(TradeError::InvalidMeseta.into())
}
if amount != this.meseta as u32{
return Err(TradeError::InvalidMeseta.into())
}
Ok(())
}
else {
let real_item = inventory.get_item_by_id(ClientItemId(item.item_id))
.ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?;
let real_trade_item = this.items
.iter()
.find(|i| i.item_id() == ClientItemId(item.item_id))
.ok_or(TradeError::SketchyTrade)?;
let trade_item_bytes: [u8; 16] = item.item_data.iter()
.chain(item.item_data2.iter())
.cloned().collect::<Vec<u8>>()
.try_into()
.unwrap();
match real_item {
InventoryItem::Individual(_individual_inventory_item) => {
if real_item.as_client_bytes() == trade_item_bytes {
Ok(())
}
else {
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
}
},
InventoryItem::Stacked(stacked_inventory_item) => {
if real_item.as_client_bytes()[0..4] == trade_item_bytes[0..4] {
let amount = trade_item_bytes[5] as usize;
if amount <= stacked_inventory_item.entity_ids.len() {
if real_trade_item.stacked().ok_or(TradeError::SketchyTrade)?.1 == amount {
Ok(())
}
else {
Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into())
}
}
else {
Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into())
}
}
else {
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
}
}
}
}
})
.collect::<Result<Vec<_>, anyhow::Error>>()?;
this.status = TradeStatus::ItemsChecked;
if this.status == TradeStatus::ItemsChecked && other.status == TradeStatus::ItemsChecked {
Ok(Box::new(vec![
(this.client(), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})),
(other.client(), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})),
].into_iter()))
}
else {
Ok(Box::new(None.into_iter()))
}
})?
.unwrap_or_else(|err| {
log::warn!("trade error: {:?}", err);
let (_this, other) = trades.remove_trade(&id);
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| other.as_ref().map(|other| client.client == other.client() ).unwrap_or_else(|| false))
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
}))
}
pub async fn items_to_trade(id: ClientId,
items_to_trade_pkt: &ItemsToTrade,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
trades: &mut TradeState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
{
let t = inner_items_to_trade(id, items_to_trade_pkt, client_location, clients, item_manager, trades).await;
match t {
Ok(p) => Ok(p),
Err(err) => {
log::warn!("atrade error: {:?}", err);
let (_this, other) = trades.remove_trade(&id);
Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter()
.filter(move |client| other.as_ref().map(|other| client.client == other.client()).unwrap_or_else(|| false))
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
}
}
}
pub async fn trade_confirmed<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
trades: &mut TradeState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
{
enum TradeReady<'a> {
OnePlayer,
BothPlayers(crate::ship::location::RoomId,
(crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState),
(crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState)),
}
let trade_instructions = trades
.with(&id, |this, other| -> Result<_, anyhow::Error> {
if status_is_not(&this.status, &[TradeStatus::ItemsChecked]) || status_is_not(&other.status, &[TradeStatus::ItemsChecked, TradeStatus::TradeComplete]) {
return Err(TradeError::MismatchedStatus.into())
}
this.status = TradeStatus::TradeComplete;
if this.status == TradeStatus::TradeComplete && other.status == TradeStatus::TradeComplete {
let this_client = clients.get(&this.client()).ok_or_else(|| ShipError::ClientNotFound(this.client()))?;
let other_client = clients.get(&other.client()).ok_or_else(|| ShipError::ClientNotFound(other.client()))?;
let this_local_client = client_location.get_local_client(this.client())?;
let other_local_client = client_location.get_local_client(other.client())?;
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
Ok(TradeReady::BothPlayers(room_id,
(this_local_client, this_client, this.clone()),
(other_local_client, other_client, other.clone())))
}
else {
Ok(TradeReady::OnePlayer)
}
});
// TODO: this match needs to handle errors better
match trade_instructions {
Ok(Ok(trade)) => {
match trade {
TradeReady::OnePlayer => {
Ok(Box::new(None.into_iter()) as Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>)
},
TradeReady::BothPlayers(room_id, (this_local_client, this_client, this), (other_local_client, other_client, other)) => {
let traded_items = item_manager.trade_items(entity_gateway,
room_id,
(&this_local_client, &this_client.character, &this.items, this.meseta),
(&other_local_client, &other_client.character, &other.items, other.meseta)).await?;
let clients_in_room = client_location.get_all_clients_by_client(id)?;
let traded_item_packets = traded_items
.into_iter()
.map(|item| {
match item.item_detail {
ItemToTradeDetail::Individual(item_detail) => {
[
GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.new_item_id, &item_detail).unwrap()),
GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, 1)) // TODO: amount = ?
]
},
ItemToTradeDetail::Stacked(tool, amount) => {
[
GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.new_item_id, &tool, amount).unwrap()),
GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32))
]
},
ItemToTradeDetail::Meseta(amount) => {
[
GameMessage::CreateItem(builder::message::create_meseta(item.add_to, amount)),
GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32))
]
},
}
})
.flatten()
.map(move |packet| {
clients_in_room
.clone()
.into_iter()
.filter_map(move |client| {
match packet {
GameMessage::PlayerNoLongerHasItem(ref no_longer) => {
if client.local_client == no_longer.client {
None
}
else {
Some((client.client, SendShipPacket::Message(Message::new(packet.clone()))))
}
}
_ => Some((client.client, SendShipPacket::Message(Message::new(packet.clone()))))
}
})
})
.flatten();
let close_trade = vec![
(this.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default())),
(other.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default()))
].into_iter();
Ok(Box::new(traded_item_packets.chain(close_trade)))
}
}
},
_ => {
let (_this, other) = trades.remove_trade(&id);
Ok(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
.filter(move |client| other.as_ref().map(|other| client.client == other.client()).unwrap_or_else(|| false))
.map(move |client| {
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
}
}
}

View File

@ -33,6 +33,7 @@ use crate::ship::quests;
use crate::ship::map::{MapsError, MapAreaError, MapArea};
use crate::ship::packet::handler;
use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop, WeaponShopItem, ToolShopItem, ArmorShopItem};
use crate::ship::trade::TradeState;
pub const SHIP_PORT: u16 = 23423;
pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2;
@ -41,11 +42,12 @@ pub type Rooms = [Option<room::RoomState>; MAX_ROOMS];
pub type Clients = HashMap<ClientId, ClientState>;
#[derive(Error, Debug)]
#[error("")]
#[error("shiperror {0:?}")]
pub enum ShipError {
ClientNotFound(ClientId),
NoCharacterInSlot(ClientId, u32),
InvalidSlot(ClientId, u32),
#[error("")]
TooManyClients,
ClientLocationError(#[from] ClientLocationError),
GetNeighborError(#[from] GetNeighborError),
@ -56,10 +58,12 @@ pub enum ShipError {
InvalidRoom(u32),
MonsterAlreadyDroppedItem(ClientId, u16),
SliceError(#[from] std::array::TryFromSliceError),
#[error("")]
ItemError, // TODO: refine this
PickUpInvalidItemId(u32),
DropInvalidItemId(u32),
ItemManagerError(#[from] items::ItemManagerError),
#[error("")]
ItemDropLocationNotSet,
BoxAlreadyDroppedItem(ClientId, u16),
InvalidQuestCategory(u32),
@ -67,12 +71,15 @@ pub enum ShipError {
InvalidQuestFilename(String),
IoError(#[from] std::io::Error),
NotEnoughMeseta(ClientId, u32),
#[error("")]
ShopError,
GatewayError(#[from] GatewayError),
UnknownMonster(crate::ship::monster::MonsterType),
InvalidShip(usize),
InvalidBlock(usize),
InvalidItem(items::ClientItemId),
#[error("tradeerror {0}")]
TradeError(#[from] crate::ship::packet::handler::trade::TradeError),
}
#[derive(Debug)]
@ -106,6 +113,8 @@ pub enum RecvShipPacket {
SaveOptions(SaveOptions),
RequestShipList(RequestShipList),
RequestShipBlockList(RequestShipBlockList),
ItemsToTrade(ItemsToTrade),
TradeConfirmed(TradeConfirmed),
}
impl RecvServerPacket for RecvShipPacket {
@ -143,6 +152,8 @@ impl RecvServerPacket for RecvShipPacket {
0xA1 => Ok(RecvShipPacket::RequestShipBlockList(RequestShipBlockList::from_bytes(data)?)),
0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)),
0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)),
0xD0 => Ok(RecvShipPacket::ItemsToTrade(ItemsToTrade::from_bytes(data)?)),
0xD2 => Ok(RecvShipPacket::TradeConfirmed(TradeConfirmed::from_bytes(data)?)),
0xE7 => Ok(RecvShipPacket::FullCharacterData(Box::new(FullCharacterData::from_bytes(data)?))),
0x1ED => Ok(RecvShipPacket::SaveOptions(SaveOptions::from_bytes(data)?)),
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
@ -184,6 +195,9 @@ pub enum SendShipPacket {
DoneLoadingQuest(DoneLoadingQuest),
BankItemList(BankItemList),
RedirectClient(RedirectClient),
AcknowledgeTrade(AcknowledgeTrade),
CancelTrade(CancelTrade),
TradeSuccessful(TradeSuccessful),
}
impl SendServerPacket for SendShipPacket {
@ -221,6 +235,9 @@ impl SendServerPacket for SendShipPacket {
SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(),
SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(),
SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(),
SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(),
}
}
}
@ -239,6 +256,12 @@ pub struct LoadingQuest {
//pub quest_chunk_bin: Option<Box<dyn Iterator<Item = >>>,
}
pub struct ClientState {
pub user: UserAccountEntity,
pub settings: UserSettingsEntity,
@ -380,6 +403,7 @@ impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
ship_list: Vec::new(),
shipgate_sender: None,
trades: Default::default(),
}
}
}
@ -426,6 +450,7 @@ pub struct ShipServerState<EG: EntityGateway> {
auth_token: AuthToken,
ship_list: Vec<Ship>,
shipgate_sender: Option<Box<dyn Fn(ShipMessage) + Send + Sync>>,
trades: TradeState,
}
impl<EG: EntityGateway> ShipServerState<EG> {
@ -463,14 +488,14 @@ impl<EG: EntityGateway> ShipServerState<EG> {
handler::message::update_player_position(id, msg, &mut self.clients, &block.client_location, &block.rooms)?
},
GameMessage::ChargeAttack(charge_attack) => {
handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway).await?
handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway, &mut self.item_manager).await?
},
GameMessage::PlayerUseItem(player_use_item) => {
let block = self.blocks.with_client(id, &self.clients)?;
handler::message::use_item(id, player_use_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
},
GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => {
handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &mut self.clients).await?
handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await?
},
GameMessage::PlayerFeedMag(player_feed_mag) => {
let block = self.blocks.with_client(id, &self.clients)?;
@ -530,6 +555,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
GameMessage::TekAccept(tek_accept) => {
handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
},
GameMessage::TradeRequest(trade_request) => {
handler::trade::trade_request(id, trade_request, target, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
},
_ => {
let cmsg = msg.clone();
Box::new(block.client_location.get_all_clients_by_client(id).unwrap().into_iter()
@ -696,7 +724,15 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
RecvShipPacket::RequestShipBlockList(_) => {
handler::ship::block_list(id, &self.name, self.blocks.0.len())
}
},
RecvShipPacket::ItemsToTrade(items_to_trade) => {
let block = self.blocks.with_client(id, &self.clients)?;
handler::trade::items_to_trade(id, items_to_trade, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
},
RecvShipPacket::TradeConfirmed(_) => {
let block = self.blocks.with_client(id, &self.clients)?;
handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
},
})
}

View File

@ -315,7 +315,7 @@ fn number_of_weapons_to_generate(character_level: usize) -> usize {
#[derive(Debug)]
pub struct WeaponShop<R: Rng + SeedableRng> {
difficulty: Difficulty,
_difficulty: Difficulty,
section_id: SectionID,
weapon: WeaponTable,
special: SpecialTable,
@ -329,7 +329,7 @@ pub struct WeaponShop<R: Rng + SeedableRng> {
impl<R: Rng + SeedableRng> WeaponShop<R> {
pub fn new(difficulty: Difficulty, section_id: SectionID) -> WeaponShop<R> {
WeaponShop {
difficulty,
_difficulty: difficulty,
section_id,
weapon: load_weapon_table(difficulty, section_id),
special: load_special_table(),
@ -358,8 +358,10 @@ impl<R: Rng + SeedableRng> WeaponShop<R> {
.last()
.unwrap();
let special_tier = WeightedIndex::new(tier.special.iter().map(|t| t.probability)).unwrap();
match special_tier.sample(&mut self.rng) {
let special_tier_choice = WeightedIndex::new(tier.special.iter().map(|t| t.probability)).unwrap();
let special_tier_index = special_tier_choice.sample(&mut self.rng);
let special_tier = tier.special.get(special_tier_index)?;
match special_tier.tier {
1 => TIER1_SPECIAL.choose(&mut self.rng).cloned(),
2 => TIER2_SPECIAL.choose(&mut self.rng).cloned(),
_ => None

133
src/ship/trade.rs Normal file
View File

@ -0,0 +1,133 @@
use std::collections::HashMap;
use std::cell::RefCell;
use crate::common::serverstate::ClientId;
use crate::ship::items;
#[derive(Debug, Clone)]
pub enum TradeItem {
Individual(items::ClientItemId),
Stacked(items::ClientItemId, usize),
}
impl TradeItem {
pub fn stacked(&self) -> Option<(items::ClientItemId, usize)> {
match self {
TradeItem::Stacked(item_id, amount) => Some((*item_id, *amount)),
_ => None
}
}
pub fn stacked_mut(&mut self) -> Option<(items::ClientItemId, &mut usize)> {
match self {
TradeItem::Stacked(item_id, ref mut amount) => Some((*item_id, amount)),
_ => None
}
}
pub fn item_id(&self) -> items::ClientItemId {
match self {
TradeItem::Individual(item_id) => *item_id,
TradeItem::Stacked(item_id, _) => *item_id,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TradeStatus {
SentRequest,
ReceivedRequest,
Trading,
Confirmed,
FinalConfirm,
ItemsChecked,
TradeComplete,
}
#[derive(Debug, Clone)]
pub struct ClientTradeState {
client: ClientId,
other_client: ClientId,
pub items: Vec<TradeItem>,
pub meseta: usize,
pub status: TradeStatus,
}
impl ClientTradeState {
pub fn client(&self) -> ClientId {
self.client
}
pub fn other_client(&self) -> ClientId {
self.other_client
}
}
#[derive(thiserror::Error, Debug)]
#[error("")]
pub enum TradeStateError {
ClientNotInTrade(ClientId),
MismatchedTrade(ClientId, ClientId),
}
#[derive(Default, Debug)]
pub struct TradeState {
trades: HashMap<ClientId, RefCell<ClientTradeState>>,
}
impl TradeState {
pub fn new_trade(&mut self, sender: &ClientId, receiver: &ClientId) {
let state = ClientTradeState {
client: *sender,
other_client: *receiver,
items: Default::default(),
meseta: 0,
status: TradeStatus::SentRequest,
};
self.trades.insert(*sender, RefCell::new(state));
let state = ClientTradeState {
client: *receiver,
other_client: *sender,
items: Default::default(),
meseta: 0,
status: TradeStatus::ReceivedRequest,
};
self.trades.insert(*receiver, RefCell::new(state));
}
pub fn in_trade(&self, client: &ClientId) -> bool {
self.trades.contains_key(client)
}
pub fn with<T, F> (&self, client: &ClientId, func: F) -> Result<T, TradeStateError>
where
F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> T
{
let mut c1 = self.trades.get(client).ok_or_else(|| TradeStateError::ClientNotInTrade(*client))?.borrow_mut();
let mut c2 = self.trades.get(&c1.other_client).ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?.borrow_mut();
// sanity check
if c1.client != c2.other_client {
return Err(TradeStateError::MismatchedTrade(c1.client, c2.client));
}
Ok(func(&mut *c1, &mut *c2))
}
// TODO: is it possible for this to not return Options?
pub fn remove_trade(&mut self, client: &ClientId) -> (Option<ClientTradeState>, Option<ClientTradeState>) {
let c1 = self.trades.remove(client).map(|c| c.into_inner());
let c2 = if let Some(ref state) = c1 {
self.trades.remove(&state.other_client).map(|c| c.into_inner())
}
else {
None
};
(c1, c2)
}
}

View File

@ -4,6 +4,7 @@ use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::EntityGateway;
use elseware::entity::account::{UserAccountEntity, NewUserAccountEntity, NewUserSettingsEntity};
use elseware::entity::character::{CharacterEntity, NewCharacterEntity};
use elseware::entity::item::{Meseta, BankName};
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::ship::room::Difficulty;
@ -27,6 +28,8 @@ pub async fn new_user_character<EG: EntityGateway>(entity_gateway: &mut EG, user
let _settings = entity_gateway.create_user_settings(new_settings).await.unwrap();
let new_character = NewCharacterEntity::new(user.id);
let character = entity_gateway.create_character(new_character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, Meseta(0)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, BankName("".into()), Meseta(0)).await.unwrap();
(user, character)
}

View File

@ -29,10 +29,6 @@ async fn test_bank_items_sent_in_character_login() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap();
entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![item]), item::BankName("".into())).await.unwrap();
@ -70,10 +66,6 @@ async fn test_request_bank_items() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -118,10 +110,6 @@ async fn test_request_stacked_bank_items() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -167,10 +155,6 @@ async fn test_request_bank_items_sorted() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap();
let monomate = entity_gateway.create_item(
item::NewItemEntity {
@ -179,10 +163,6 @@ async fn test_request_bank_items_sorted() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap();
let item2 = entity_gateway.create_item(
item::NewItemEntity {
@ -195,10 +175,6 @@ async fn test_request_bank_items_sorted() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap();
let bank = vec![item::BankItemEntity::Individual(item1), vec![monomate].into(), item2.into()];
@ -245,9 +221,6 @@ async fn test_deposit_individual_item() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap();
let item1 = entity_gateway.create_item(
item::NewItemEntity {
@ -260,9 +233,6 @@ async fn test_deposit_individual_item() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap();
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![item0, item1])).await.unwrap();
@ -322,9 +292,6 @@ async fn test_deposit_stacked_item() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -386,9 +353,6 @@ async fn test_deposit_partial_stacked_item() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -460,9 +424,6 @@ async fn test_deposit_stacked_item_with_stack_already_in_bank() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
bank_monomates.push(entity_gateway.create_item(
@ -472,10 +433,6 @@ async fn test_deposit_stacked_item_with_stack_already_in_bank() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".into()),
}
}).await.unwrap());
}
@ -537,9 +494,6 @@ async fn test_deposit_stacked_item_with_full_stack_in_bank() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -552,10 +506,6 @@ async fn test_deposit_stacked_item_with_full_stack_in_bank() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".into()),
}
}).await.unwrap());
}
@ -619,9 +569,6 @@ async fn test_deposit_individual_item_in_full_bank() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
let mut bank = Vec::new();
@ -637,10 +584,6 @@ async fn test_deposit_individual_item_in_full_bank() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -697,9 +640,6 @@ async fn test_deposit_stacked_item_in_full_bank() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -716,10 +656,6 @@ async fn test_deposit_stacked_item_in_full_bank() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -777,9 +713,6 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -792,10 +725,6 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -812,10 +741,6 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap().into());
}
almost_full_bank.push(bank_monomates.into());
@ -860,9 +785,8 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() {
async fn test_deposit_meseta() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 300;
entity_gateway.save_character(&char1).await.unwrap();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
entity_gateway.set_character_meseta(&char1.id, item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -887,20 +811,19 @@ async fn test_deposit_meseta() {
unknown: 0,
})))).await.unwrap().for_each(drop);
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.meseta == 277);
assert!(char.bank_meseta == 23);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap();
assert!(c1_meseta.0 == 277);
assert!(c1_bank_meseta.0 == 23);
}
#[async_std::test]
async fn test_deposit_too_much_meseta() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 300;
char1.bank_meseta = 999980;
entity_gateway.save_character(&char1).await.unwrap();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
entity_gateway.set_character_meseta(&char1.id, item::Meseta(300)).await.unwrap();
entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(999980)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -925,21 +848,19 @@ async fn test_deposit_too_much_meseta() {
unknown: 0,
})))).await.unwrap().for_each(drop);
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.meseta == 300);
assert!(char.bank_meseta == 999980);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap();
assert!(c1_meseta.0 == 300);
assert!(c1_bank_meseta.0 == 999980);
}
#[async_std::test]
async fn test_deposit_meseta_when_bank_is_maxed() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 300;
char1.bank_meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
entity_gateway.set_character_meseta(&char1.id, item::Meseta(300)).await.unwrap();
entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -964,10 +885,10 @@ async fn test_deposit_meseta_when_bank_is_maxed() {
unknown: 0,
})))).await.unwrap().for_each(drop);
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.meseta == 300);
assert!(char.bank_meseta == 999999);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap();
assert!(c1_meseta.0 == 300);
assert!(c1_bank_meseta.0 == 999999);
}
@ -990,10 +911,6 @@ async fn test_withdraw_individual_item() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), item::BankName("".into())).await.unwrap();
@ -1053,10 +970,6 @@ async fn test_withdraw_stacked_item() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -1117,10 +1030,6 @@ async fn test_withdraw_partial_stacked_item() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".into())
}
}).await.unwrap());
}
entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), item::BankName("".into())).await.unwrap();
@ -1188,9 +1097,6 @@ async fn test_withdraw_stacked_item_with_stack_already_in_inventory() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
bank_monomates.push(entity_gateway.create_item(
@ -1200,10 +1106,6 @@ async fn test_withdraw_stacked_item_with_stack_already_in_inventory() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".into()),
}
}).await.unwrap());
}
@ -1267,10 +1169,6 @@ async fn test_withdraw_stacked_item_with_full_stack_in_inventory() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".into()),
}
}).await.unwrap());
}
@ -1283,9 +1181,6 @@ async fn test_withdraw_stacked_item_with_full_stack_in_inventory() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -1349,10 +1244,6 @@ async fn test_withdraw_individual_item_in_full_inventory() {
tekked: true,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
let mut inventory = Vec::new();
@ -1368,9 +1259,6 @@ async fn test_withdraw_individual_item_in_full_inventory() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -1423,10 +1311,6 @@ async fn test_withdraw_stacked_item_in_full_inventory() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
@ -1443,9 +1327,6 @@ async fn test_withdraw_stacked_item_in_full_inventory() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -1504,10 +1385,6 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Bank {
character_id: char1.id,
name: item::BankName("".to_string())
}
}).await.unwrap());
}
entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_item]), item::BankName("".into())).await.unwrap();
@ -1525,9 +1402,6 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap().into());
}
@ -1540,9 +1414,6 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
items.push(item::InventoryItemEntity::Stacked(item29));
@ -1589,9 +1460,8 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() {
async fn test_withdraw_meseta() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.bank_meseta = 300;
entity_gateway.save_character(&char1).await.unwrap();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -1616,20 +1486,19 @@ async fn test_withdraw_meseta() {
unknown: 0,
})))).await.unwrap().for_each(drop);
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.meseta == 23);
assert!(char.bank_meseta == 277);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap();
assert!(c1_meseta.0 == 23);
assert!(c1_bank_meseta.0 == 277);
}
#[async_std::test]
async fn test_withdraw_too_much_meseta() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 999980;
char1.bank_meseta = 300;
entity_gateway.save_character(&char1).await.unwrap();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999980)).await.unwrap();
entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -1654,20 +1523,19 @@ async fn test_withdraw_too_much_meseta() {
unknown: 0,
})))).await.unwrap().for_each(drop);
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.meseta == 999980);
assert!(char.bank_meseta == 300);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap();
assert!(c1_meseta.0 == 999980);
assert!(c1_bank_meseta.0 == 300);
}
#[async_std::test]
async fn test_withdraw_meseta_inventory_is_maxed() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 999999;
char1.bank_meseta = 300;
entity_gateway.save_character(&char1).await.unwrap();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -1692,8 +1560,8 @@ async fn test_withdraw_meseta_inventory_is_maxed() {
unknown: 0,
})))).await.unwrap().for_each(drop);
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.meseta == 999999);
assert!(char.bank_meseta == 300);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap();
assert!(c1_meseta.0 == 999999);
assert!(c1_bank_meseta.0 == 300);
}

View File

@ -26,9 +26,6 @@ async fn test_equip_unit_from_equip_menu() {
evp: 0,
slots: 4,
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
@ -38,9 +35,6 @@ async fn test_equip_unit_from_equip_menu() {
unit: item::unit::UnitType::KnightPower,
modifier: None,
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
@ -50,9 +44,6 @@ async fn test_equip_unit_from_equip_menu() {
unit: item::unit::UnitType::KnightPower,
modifier: Some(item::unit::UnitModifier::Plus),
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
let equipped = item::EquippedEntity {
@ -112,9 +103,6 @@ async fn test_unequip_armor_with_units() {
evp: 0,
slots: 4,
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
@ -124,9 +112,6 @@ async fn test_unequip_armor_with_units() {
unit: item::unit::UnitType::KnightPower,
modifier: None,
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
@ -136,9 +121,6 @@ async fn test_unequip_armor_with_units() {
unit: item::unit::UnitType::KnightPower,
modifier: Some(item::unit::UnitModifier::Plus),
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
let equipped = item::EquippedEntity {
@ -189,9 +171,6 @@ async fn test_sort_items() {
evp: 0,
slots: 4,
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
@ -201,9 +180,6 @@ async fn test_sort_items() {
unit: item::unit::UnitType::KnightPower,
modifier: None,
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
@ -213,9 +189,6 @@ async fn test_sort_items() {
unit: item::unit::UnitType::KnightPower,
modifier: Some(item::unit::UnitModifier::Plus),
}),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

View File

@ -10,6 +10,77 @@ use libpso::packet::messages::*;
mod common;
use common::*;
#[async_std::test]
async fn test_pick_up_individual_item() {
let mut entity_gateway = InMemoryGateway::default();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let mut p1_inv = Vec::new();
p1_inv.push(entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Handgun,
grind: 0,
special: None,
attrs: [None, None, None],
tekked: true,
}
),
}).await.unwrap());
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::<item::InventoryItemEntity>::new())).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
log_in_char(&mut ship, ClientId(2), "a2", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
join_lobby(&mut ship, ClientId(2)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
join_room(&mut ship, ClientId(2), 0).await;
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 1);
let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap();
assert_eq!(p2_items.items.len(), 0);
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDropItem(PlayerDropItem {
client: 0,
target: 0,
unknown1: 0,
map_area: 0,
item_id: 0x10000,
x: 0.0,
y: 0.0,
z: 0.0,
})))).await.unwrap().for_each(drop);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 0);
let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap();
assert_eq!(p2_items.items.len(), 0);
ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem {
client: 0,
target: 0,
item_id: 0x10000,
map_area: 0,
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 0);
let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap();
assert_eq!(p2_items.items.len(), 1);
}
#[async_std::test]
async fn test_pick_up_item_stack_of_items_already_in_inventory() {
let mut entity_gateway = InMemoryGateway::default();
@ -25,9 +96,6 @@ async fn test_pick_up_item_stack_of_items_already_in_inventory() {
tool: item::tool::ToolType::Monomate
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
let mut p2_items = Vec::new();
@ -41,9 +109,6 @@ async fn test_pick_up_item_stack_of_items_already_in_inventory() {
tool: tool
}
),
location: item::ItemLocation::Inventory {
character_id: char2.id,
}
}).await.unwrap());
}
p2_items.push(item);
@ -107,9 +172,6 @@ async fn test_pick_up_item_stack_of_items_not_already_held() {
tool: item::tool::ToolType::Monomate
}
),
location: item::ItemLocation::Inventory {
character_id: char2.id,
}
}).await.unwrap());
entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_monomate])).await.unwrap();
@ -159,7 +221,7 @@ async fn test_pick_up_meseta_when_inventory_full() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let (user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let mut p1_items = Vec::new();
for _ in 0..30usize {
@ -174,16 +236,11 @@ async fn test_pick_up_meseta_when_inventory_full() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();
char2.meseta = 300;
entity_gateway.save_character(&char2).await.unwrap();
entity_gateway.set_character_meseta(&char2.id, item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -225,12 +282,10 @@ async fn test_pick_up_meseta_when_inventory_full() {
let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(inventory_items.items.len(), 30);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
let characters2 = entity_gateway.get_characters_by_user(&user2).await.unwrap();
let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 23);
assert!(c2.meseta == 277);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap();
assert!(c1_meseta.0 == 23);
assert!(c2_meseta.0 == 277);
}
#[async_std::test]
@ -253,9 +308,6 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap().into());
}
@ -266,9 +318,6 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap()]));
let mut p2_monomates = Vec::new();
@ -279,9 +328,6 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char2.id,
}
}).await.unwrap());
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
@ -345,9 +391,6 @@ async fn test_can_not_pick_up_item_when_inventory_full() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -363,9 +406,6 @@ async fn test_can_not_pick_up_item_when_inventory_full() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char2.id,
}
}).await.unwrap());
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
@ -425,10 +465,9 @@ async fn test_can_not_pick_up_item_when_inventory_full() {
async fn test_can_not_drop_more_meseta_than_is_held() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 300;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -457,9 +496,8 @@ async fn test_can_not_drop_more_meseta_than_is_held() {
})))).await;
assert!(split_attempt.is_err());
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 300);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert!(c1_meseta.0 == 300);
}
#[async_std::test]
@ -478,9 +516,6 @@ async fn test_pick_up_stack_that_would_exceed_stack_limit() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -493,9 +528,6 @@ async fn test_pick_up_stack_that_would_exceed_stack_limit() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char2.id,
}
}).await.unwrap());
}
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_monomates])).await.unwrap();
@ -546,13 +578,11 @@ async fn test_pick_up_stack_that_would_exceed_stack_limit() {
async fn test_can_not_pick_up_meseta_when_full() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
char2.meseta = 300;
entity_gateway.save_character(&char2).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_character_meseta(&char2.id, item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -592,25 +622,21 @@ async fn test_can_not_pick_up_meseta_when_full() {
})))).await.unwrap().collect::<Vec<_>>();
assert!(packets.len() == 0);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
let characters2 = entity_gateway.get_characters_by_user(&user2).await.unwrap();
let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 999999);
assert!(c2.meseta == 277);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap();
assert!(c1_meseta.0 == 999999);
assert!(c2_meseta.0 == 277);
}
#[async_std::test]
async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
char1.meseta = 999998;
entity_gateway.save_character(&char1).await.unwrap();
char2.meseta = 300;
entity_gateway.save_character(&char2).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999998)).await.unwrap();
entity_gateway.set_character_meseta(&char2.id, item::Meseta(300)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -649,12 +675,10 @@ async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
let characters2 = entity_gateway.get_characters_by_user(&user2).await.unwrap();
let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 999999);
assert!(c2.meseta == 277);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap();
assert!(c1_meseta.0 == 999999);
assert!(c2_meseta.0 == 277);
}
#[async_std::test]
@ -673,9 +697,6 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -732,3 +753,20 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() {
vec![item::ItemEntityId(1), item::ItemEntityId(2)]);
}).unwrap();
}
/*
#[async_std::test]
async fn test_try_and_pick_up_individual_item_twice() {
panic!()
}
#[async_std::test]
async fn test_try_and_pick_up_stacked_item_twice() {
panic!()
}
#[async_std::test]
async fn test_try_and_pick_up_meseta_twice() {
panic!()
}
*/

View File

@ -29,9 +29,6 @@ async fn test_use_monomate() {
tool: tool
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
p1_items.push(item::InventoryItemEntity::Stacked(item));
@ -79,9 +76,6 @@ async fn test_use_monomate_twice() {
tool: tool
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
p1_items.push(item::InventoryItemEntity::Stacked(item));
@ -132,9 +126,6 @@ async fn test_use_last_monomate() {
tool: tool
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap()]));
}
@ -176,9 +167,6 @@ async fn test_use_nonstackable_tool() {
tool: item::tool::ToolType::MagicStoneIritista,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();
@ -217,9 +205,6 @@ async fn test_use_materials() {
tool: tool
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
p1_inv.push(item::InventoryItemEntity::Stacked(item));

View File

@ -22,10 +22,6 @@ async fn test_mag_feed() {
item: item::ItemDetail::Mag(
item::mag::Mag::baby_mag(0)
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
//equipped: true,
}
}).await.unwrap();
let mut monomates = Vec::new();
@ -37,9 +33,6 @@ async fn test_mag_feed() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -108,9 +101,6 @@ async fn test_mag_change_owner() {
item: item::ItemDetail::Mag(
item::mag::Mag::baby_mag(0)
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap();
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![mag])).await.unwrap();
@ -169,9 +159,6 @@ async fn test_mag_cell() {
item: item::ItemDetail::Mag(
item::mag::Mag::baby_mag(0)
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap();
for _ in 0..1000usize {
@ -182,9 +169,6 @@ async fn test_mag_cell() {
tool: item::tool::ToolType::Monomate,
}
),
location: item::ItemLocation::FedToMag {
mag: mag.id,
}
}).await.unwrap();
entity_gateway.feed_mag(&mag.id, &fed_tool.id).await.unwrap();
}
@ -195,9 +179,6 @@ async fn test_mag_cell() {
tool: item::tool::ToolType::CellOfMag502,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap();
let equipped = item::EquippedEntity {

View File

@ -31,9 +31,6 @@ async fn test_item_ids_reset_when_rejoining_rooms() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap());
}
@ -50,9 +47,6 @@ async fn test_item_ids_reset_when_rejoining_rooms() {
tekked: true,
}
),
location: item::ItemLocation::Inventory {
character_id: char2.id,
}
}).await.unwrap());
}

View File

@ -107,8 +107,8 @@ async fn test_player_buys_from_weapon_shop() {
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -132,9 +132,8 @@ async fn test_player_buys_from_weapon_shop() {
unknown1: 0,
})))).await.unwrap().for_each(drop);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta < 999999);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert!(c1_meseta.0 < 999999);
//let p1_items = entity_gateway.get_items_by_character(&char1.id).await.unwrap();
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 1);
@ -146,8 +145,8 @@ async fn test_player_buys_from_tool_shop() {
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -171,9 +170,8 @@ async fn test_player_buys_from_tool_shop() {
unknown1: 0,
})))).await.unwrap().for_each(drop);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta < 999999);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert!(c1_meseta.0 < 999999);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 1);
}
@ -184,8 +182,8 @@ async fn test_player_buys_multiple_from_tool_shop() {
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -209,9 +207,8 @@ async fn test_player_buys_multiple_from_tool_shop() {
unknown1: 0,
})))).await.unwrap().for_each(drop);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta < 999999);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert!(c1_meseta.0 < 999999);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 1);
p1_items.items[0].with_stacked(|item| {
@ -226,8 +223,8 @@ async fn test_player_buys_from_armor_shop() {
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -251,9 +248,8 @@ async fn test_player_buys_from_armor_shop() {
unknown1: 0,
})))).await.unwrap().for_each(drop);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta < 999999);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert!(c1_meseta.0 < 999999);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 1);
}
@ -269,7 +265,7 @@ async fn test_other_clients_see_purchase() {
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
@ -312,8 +308,8 @@ async fn test_other_clients_see_stacked_purchase() {
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Tool(
@ -321,9 +317,6 @@ async fn test_other_clients_see_stacked_purchase() {
tool: item::tool::ToolType::Monomate
}
),
location: item::ItemLocation::Inventory {
character_id: char1.id,
}
}).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
@ -388,9 +381,8 @@ async fn test_buying_item_without_enough_mseseta() {
})))).await;
assert!(packets.is_err());
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert_eq!(c1.meseta, 0);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert_eq!(c1_meseta.0, 0);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 0);
}
@ -401,8 +393,8 @@ async fn test_player_double_buys_from_tool_shop() {
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -444,9 +436,8 @@ async fn test_player_double_buys_from_tool_shop() {
unknown1: 0,
})))).await.unwrap().for_each(drop);
let characters1 = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta < 999999);
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert!(c1_meseta.0 < 999999);
let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
assert_eq!(p1_items.items.len(), 2);
p1_items.items[0].with_stacked(|item| {
@ -467,8 +458,8 @@ async fn test_techs_disappear_from_shop_when_bought() {
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
@ -529,8 +520,8 @@ async fn test_units_disappear_from_shop_when_bought() {
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.exp = 80000000;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())

4382
tests/test_trade.rs Normal file

File diff suppressed because it is too large Load Diff