Merge pull request 'shop_sell' (#65) from shop_sell into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #65
This commit is contained in:
commit
6c1ae1a41e
@ -211,18 +211,10 @@ fn main() {
|
||||
}).await.unwrap();
|
||||
entity_gateway.use_mag_cell(&item5_m.id, &cell.id).await.unwrap();
|
||||
|
||||
entity_gateway.create_item(
|
||||
let item6_1 = entity_gateway.create_item(
|
||||
NewItemEntity {
|
||||
item: ItemDetail::Weapon(
|
||||
item::weapon::Weapon {
|
||||
weapon: item::weapon::WeaponType::Autogun,
|
||||
grind: 5,
|
||||
special: Some(item::weapon::WeaponSpecial::Hell),
|
||||
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 70}),
|
||||
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 80}),
|
||||
None,],
|
||||
tekked: false,
|
||||
}
|
||||
item: ItemDetail::ESWeapon(
|
||||
item::esweapon::ESWeapon::new(item::esweapon::ESWeaponType::Saber)
|
||||
),
|
||||
}).await.unwrap();
|
||||
let item7_a = entity_gateway.create_item(
|
||||
@ -230,8 +222,8 @@ fn main() {
|
||||
item: ItemDetail::Armor(
|
||||
item::armor::Armor {
|
||||
armor: item::armor::ArmorType::Frame,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
dfp: 2,
|
||||
evp: 2,
|
||||
slots: 4,
|
||||
}
|
||||
),
|
||||
@ -242,8 +234,8 @@ fn main() {
|
||||
item: ItemDetail::Shield(
|
||||
item::shield::Shield {
|
||||
shield: item::shield::ShieldType::Barrier,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
dfp: 5,
|
||||
evp: 5,
|
||||
}
|
||||
),
|
||||
}
|
||||
@ -253,7 +245,7 @@ fn main() {
|
||||
item: ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::PriestMind,
|
||||
modifier: Some(item::unit::UnitModifier::Minus),
|
||||
modifier: Some(item::unit::UnitModifier::PlusPlus),
|
||||
}
|
||||
),
|
||||
}
|
||||
@ -263,7 +255,7 @@ fn main() {
|
||||
item: ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::PriestMind,
|
||||
modifier: Some(item::unit::UnitModifier::Minus),
|
||||
modifier: Some(item::unit::UnitModifier::Plus),
|
||||
}
|
||||
),
|
||||
}
|
||||
@ -283,7 +275,7 @@ fn main() {
|
||||
item: ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::PriestMind,
|
||||
modifier: Some(item::unit::UnitModifier::Minus),
|
||||
modifier: Some(item::unit::UnitModifier::MinusMinus),
|
||||
}
|
||||
),
|
||||
}
|
||||
@ -295,6 +287,20 @@ fn main() {
|
||||
),
|
||||
}
|
||||
).await.unwrap();
|
||||
let item14 = entity_gateway.create_item(
|
||||
NewItemEntity {
|
||||
item: ItemDetail::Weapon(
|
||||
item::weapon::Weapon {
|
||||
weapon: item::weapon::WeaponType::Vulcan,
|
||||
grind: 5,
|
||||
special: Some(item::weapon::WeaponSpecial::Charge),
|
||||
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,
|
||||
}
|
||||
),
|
||||
}).await.unwrap();
|
||||
|
||||
let monomates = futures::future::join_all((0..6).map(|_| {
|
||||
let mut entity_gateway = entity_gateway.clone();
|
||||
@ -319,7 +325,7 @@ fn main() {
|
||||
};
|
||||
entity_gateway.set_character_equips(&character.id, &equipped).await.unwrap();
|
||||
|
||||
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()]);
|
||||
let inventory = item::InventoryEntity::new(vec![InventoryItemEntity::from(item0), item1.into(), item2_w.into(), item3.into(), item4.into(), item5_m.into(), item6.into(), item6_1.into(), item7_a.into(), item8_s.into(), item9_u0.into(), item10_u1.into(), item11_u2.into(), item12_u3.into(), item13.into(), item14.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();
|
||||
}
|
||||
|
@ -147,13 +147,13 @@ impl EntityGateway for PostgresGateway {
|
||||
let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;")
|
||||
.bind(settings.user_id.0)
|
||||
.bind(settings.settings.blocked_users.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
|
||||
.bind(settings.settings.key_config.to_vec())
|
||||
.bind(settings.settings.joystick_config.to_vec())
|
||||
.bind(settings.settings.option_flags as i32)
|
||||
.bind(settings.settings.shortcuts.to_vec())
|
||||
.bind(settings.settings.symbol_chats.to_vec())
|
||||
.bind(settings.settings.team_name.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
|
||||
.fetch_one(&self.pool).await?;
|
||||
Ok(new_settings.into())
|
||||
}
|
||||
@ -167,13 +167,13 @@ impl EntityGateway for PostgresGateway {
|
||||
|
||||
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
|
||||
sqlx::query("update user_settings set blocked_users=$1, key_config=$2, joystick_config=$3, option_flags=$4, shortcuts=$5, symbol_chats=$6, team_name=$7 where id=$8")
|
||||
.bind(settings.settings.blocked_users.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
|
||||
.bind(&settings.settings.key_config.to_vec())
|
||||
.bind(&settings.settings.joystick_config.to_vec())
|
||||
.bind(&settings.settings.option_flags)
|
||||
.bind(&settings.settings.shortcuts.to_vec())
|
||||
.bind(&settings.settings.symbol_chats.to_vec())
|
||||
.bind(settings.settings.team_name.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
|
||||
.bind(&settings.id.0)
|
||||
.fetch_one(&self.pool).await?;
|
||||
Ok(())
|
||||
|
@ -297,7 +297,7 @@ pub enum ArmorModifier {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Armor {
|
||||
pub armor: ArmorType,
|
||||
pub dfp: u8,
|
||||
@ -329,4 +329,74 @@ impl Armor {
|
||||
Err(ItemParseError::InvalidArmorBytes) // TODO: error handling if wrong bytes are given
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
matches!(
|
||||
self.armor,
|
||||
ArmorType::HunterField
|
||||
| ArmorType::RangerField
|
||||
| ArmorType::ForceField
|
||||
| ArmorType::RevivalGarment
|
||||
| ArmorType::SpiritGarment
|
||||
| ArmorType::StinkFrame
|
||||
| ArmorType::DPartsVer101
|
||||
| ArmorType::DPartsVer210
|
||||
| ArmorType::ParasiteWearDeRol
|
||||
| ArmorType::ParasiteWearNelgal
|
||||
| ArmorType::ParasiteWearVajulla
|
||||
| ArmorType::SensePlate
|
||||
| ArmorType::GravitonPlate
|
||||
| ArmorType::AttributePlate
|
||||
| ArmorType::FlowensFrame
|
||||
| ArmorType::CustomFrameVerOo
|
||||
| ArmorType::DbsArmor
|
||||
| ArmorType::GuardWave
|
||||
| ArmorType::DfField
|
||||
| ArmorType::LuminousField
|
||||
| ArmorType::ChuChuFever
|
||||
| ArmorType::LoveHeart
|
||||
| ArmorType::FlameGarment
|
||||
| ArmorType::VirusArmorLafuteria
|
||||
| ArmorType::BrightnessCircle
|
||||
| ArmorType::AuraField
|
||||
| ArmorType::ElectroFrame
|
||||
| ArmorType::SacredCloth
|
||||
| ArmorType::SmokingPlate
|
||||
| ArmorType::StarCuirass
|
||||
| ArmorType::BlackHoundCuirass
|
||||
| ArmorType::MorningPrayer
|
||||
| ArmorType::BlackOdoshiDomaru
|
||||
| ArmorType::RedOdoshiDomaru
|
||||
| ArmorType::BlackOdoshiRedNimaidou
|
||||
| ArmorType::BlueOdoshiVioletNimaidou
|
||||
| ArmorType::DirtyLifejacket
|
||||
| ArmorType::KroesSweater
|
||||
| ArmorType::WeddingDress
|
||||
| ArmorType::SonicteamArmor
|
||||
| ArmorType::RedCoat
|
||||
| ArmorType::Thirteen
|
||||
| ArmorType::MotherGarb
|
||||
| ArmorType::MotherGarbPlus
|
||||
| ArmorType::DressPlate
|
||||
| ArmorType::Sweetheart
|
||||
| ArmorType::IgnitionCloak
|
||||
| ArmorType::CongealCloak
|
||||
| ArmorType::TempestCloak
|
||||
| ArmorType::CursedCloak
|
||||
| ArmorType::SelectCloak
|
||||
| ArmorType::SpiritCuirass
|
||||
| ArmorType::RevivalCuriass
|
||||
| ArmorType::AllianceUniform
|
||||
| ArmorType::OfficerUniform
|
||||
| ArmorType::CommanderUniform
|
||||
| ArmorType::CrimsonCoat
|
||||
| ArmorType::InfantryGear
|
||||
| ArmorType::LieutenantGear
|
||||
| ArmorType::InfantryMantle
|
||||
| ArmorType::LieutenantMantle
|
||||
| ArmorType::UnionField
|
||||
| ArmorType::SamuraiArmor
|
||||
| ArmorType::StealthSuit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -253,6 +253,11 @@ impl ESWeapon {
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this isn't even needed. all sranks are rare and only sell for 10? meseta in the shop
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1099,6 +1099,53 @@ impl Mag {
|
||||
MagCell::LibertaKit => MagType::Agastya,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: is this even needed? mags are not shop sellable...yet
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
matches!(
|
||||
self.mag,
|
||||
MagType::Pitri
|
||||
| MagType::Soniti
|
||||
| MagType::Preta
|
||||
| MagType::Churel
|
||||
| MagType::Robochao
|
||||
| MagType::OpaOpa
|
||||
| MagType::Pian
|
||||
| MagType::Chao
|
||||
| MagType::ChuChu
|
||||
| MagType::KapuKapu
|
||||
| MagType::AngelsWing
|
||||
| MagType::DevilsWing
|
||||
| MagType::Elenor
|
||||
| MagType::MarkIII
|
||||
| MagType::MasterSystem
|
||||
| MagType::Genesis
|
||||
| MagType::SegaSaturn
|
||||
| MagType::Dreamcast
|
||||
| MagType::Hamburger
|
||||
| MagType::PanzersTail
|
||||
| MagType::DevilsTail
|
||||
| MagType::Deva
|
||||
| MagType::Rati
|
||||
| MagType::Savitri
|
||||
| MagType::Rukmin
|
||||
| MagType::Pushan
|
||||
| MagType::Diwari
|
||||
| MagType::Sato
|
||||
| MagType::Bhima
|
||||
| MagType::Nidra
|
||||
| MagType::GeungSi
|
||||
| MagType::Tellusis
|
||||
| MagType::StrikerUnit
|
||||
| MagType::Pioneer
|
||||
| MagType::Puyo
|
||||
| MagType::Moro
|
||||
| MagType::Rappy
|
||||
| MagType::Yahoo
|
||||
| MagType::GaelGiel
|
||||
| MagType::Agastya
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -202,6 +202,7 @@ impl std::convert::From<Vec<ItemEntity>> for InventoryItemEntity {
|
||||
}
|
||||
|
||||
impl InventoryItemEntity {
|
||||
#[must_use]
|
||||
pub fn map_individual<F: Fn(ItemEntity) -> ItemEntity>(self, func: F) -> InventoryItemEntity {
|
||||
match self {
|
||||
InventoryItemEntity::Individual(item) => InventoryItemEntity::Individual(func(item)),
|
||||
|
@ -548,4 +548,154 @@ impl Shield {
|
||||
Err(ItemParseError::InvalidShieldBytes) // TODO: error handling if wrong bytes are given
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
matches!(
|
||||
self.shield,
|
||||
ShieldType::InvisibleGuard
|
||||
| ShieldType::SacredGuard
|
||||
| ShieldType::SPartsVer116
|
||||
| ShieldType::SPartsVer201
|
||||
| ShieldType::LightRelief
|
||||
| ShieldType::ShieldOfDelsaber
|
||||
| ShieldType::ForceWall
|
||||
| ShieldType::RangerWall
|
||||
| ShieldType::HunterWall
|
||||
| ShieldType::AttributeWall
|
||||
| ShieldType::SecretGear
|
||||
| ShieldType::CombatGear
|
||||
| ShieldType::ProtoRegeneGear
|
||||
| ShieldType::RegenerateGear
|
||||
| ShieldType::RegeneGearAdv
|
||||
| ShieldType::FlowensShield
|
||||
| ShieldType::CustomBarrierVerOo
|
||||
| ShieldType::DbsShield
|
||||
| ShieldType::RedRing
|
||||
| ShieldType::TripolicShield
|
||||
| ShieldType::StandstillShield
|
||||
| ShieldType::SafetyHeart
|
||||
| ShieldType::KasamiBracer
|
||||
| ShieldType::GodsShieldSuzaku
|
||||
| ShieldType::GodsShieldGenbu
|
||||
| ShieldType::GodsShieldByakko
|
||||
| ShieldType::GodsShieldSeiryu
|
||||
| ShieldType::HuntersShell
|
||||
| ShieldType::RicosGlasses
|
||||
| ShieldType::RicosEarring
|
||||
| ShieldType::BlueRing
|
||||
| ShieldType::Barrier2
|
||||
| ShieldType::SecureFeet
|
||||
| ShieldType::Barrier3
|
||||
| ShieldType::Barrier4
|
||||
| ShieldType::Barrier5
|
||||
| ShieldType::Barrier6
|
||||
| ShieldType::RestaMerge
|
||||
| ShieldType::AntiMerge
|
||||
| ShieldType::ShiftaMerge
|
||||
| ShieldType::DebandMerge
|
||||
| ShieldType::FoieMerge
|
||||
| ShieldType::GifoieMerge
|
||||
| ShieldType::RafoieMerge
|
||||
| ShieldType::RedMerge
|
||||
| ShieldType::BartaMerge
|
||||
| ShieldType::GibartaMerge
|
||||
| ShieldType::RabartaMerge
|
||||
| ShieldType::BlueMerge
|
||||
| ShieldType::ZondeMerge
|
||||
| ShieldType::GizondeMerge
|
||||
| ShieldType::RazondeMerge
|
||||
| ShieldType::YellowMerge
|
||||
| ShieldType::RecoveryBarrier
|
||||
| ShieldType::AssistBarrier
|
||||
| ShieldType::RedBarrier
|
||||
| ShieldType::BlueBarrier
|
||||
| ShieldType::YellowBarrier
|
||||
| ShieldType::WeaponsGoldShield
|
||||
| ShieldType::BlackGear
|
||||
| ShieldType::WorksGuard
|
||||
| ShieldType::RagolRing
|
||||
| ShieldType::BlueRing2
|
||||
| ShieldType::BlueRing3
|
||||
| ShieldType::BlueRing4
|
||||
| ShieldType::BlueRing5
|
||||
| ShieldType::BlueRing6
|
||||
| ShieldType::BlueRing7
|
||||
| ShieldType::BlueRing8
|
||||
| ShieldType::BlueRing9
|
||||
| ShieldType::GreenRing
|
||||
| ShieldType::GreenRing2
|
||||
| ShieldType::GreenRing3
|
||||
| ShieldType::GreenRing4
|
||||
| ShieldType::GreenRing5
|
||||
| ShieldType::GreenRing6
|
||||
| ShieldType::GreenRing7
|
||||
| ShieldType::GreenRing8
|
||||
| ShieldType::YellowRing
|
||||
| ShieldType::YellowRing2
|
||||
| ShieldType::YellowRing3
|
||||
| ShieldType::YellowRing4
|
||||
| ShieldType::YellowRing5
|
||||
| ShieldType::YellowRing6
|
||||
| ShieldType::YellowRing7
|
||||
| ShieldType::YellowRing8
|
||||
| ShieldType::PurpleRing
|
||||
| ShieldType::PurpleRing2
|
||||
| ShieldType::PurpleRing3
|
||||
| ShieldType::PurpleRing4
|
||||
| ShieldType::PurpleRing5
|
||||
| ShieldType::PurpleRing6
|
||||
| ShieldType::PurpleRing7
|
||||
| ShieldType::PurpleRing8
|
||||
| ShieldType::WhiteRing
|
||||
| ShieldType::WhiteRing2
|
||||
| ShieldType::WhiteRing3
|
||||
| ShieldType::WhiteRing4
|
||||
| ShieldType::WhiteRing5
|
||||
| ShieldType::WhiteRing6
|
||||
| ShieldType::WhiteRing7
|
||||
| ShieldType::WhiteRing8
|
||||
| ShieldType::BlackRing
|
||||
| ShieldType::BlackRing2
|
||||
| ShieldType::BlackRing3
|
||||
| ShieldType::BlackRing4
|
||||
| ShieldType::BlackRing5
|
||||
| ShieldType::BlackRing6
|
||||
| ShieldType::BlackRing7
|
||||
| ShieldType::BlackRing8
|
||||
| ShieldType::WeaponsSilverShield
|
||||
| ShieldType::WeaponsCopperShield
|
||||
| ShieldType::Gratia
|
||||
| ShieldType::TripolicReflector
|
||||
| ShieldType::StrikerPlus
|
||||
| ShieldType::RegenerateGearBP
|
||||
| ShieldType::Rupika
|
||||
| ShieldType::YataMirror
|
||||
| ShieldType::BunnyEars
|
||||
| ShieldType::CatEars
|
||||
| ShieldType::ThreeSeals
|
||||
| ShieldType::GodsShieldKouryu
|
||||
| ShieldType::DfShield
|
||||
| ShieldType::FromTheDepths
|
||||
| ShieldType::DeRolLeShield
|
||||
| ShieldType::HoneycombReflector
|
||||
| ShieldType::Epsiguard
|
||||
| ShieldType::AngelRing
|
||||
| ShieldType::UnionGuard
|
||||
| ShieldType::UnionGuard2
|
||||
| ShieldType::UnionGuard3
|
||||
| ShieldType::UnionGuard4
|
||||
| ShieldType::StinkShield
|
||||
| ShieldType::Unknownb
|
||||
| ShieldType::Genpei
|
||||
| ShieldType::Genpei2
|
||||
| ShieldType::Genpei3
|
||||
| ShieldType::Genpei4
|
||||
| ShieldType::Genpei5
|
||||
| ShieldType::Genpei6
|
||||
| ShieldType::Genpei7
|
||||
| ShieldType::Genpei8
|
||||
| ShieldType::Genpei9
|
||||
| ShieldType::Genpei10
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -680,4 +680,174 @@ impl Tool {
|
||||
pub fn max_stack(&self) -> usize {
|
||||
self.tool.max_stack()
|
||||
}
|
||||
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
matches!(
|
||||
self.tool,
|
||||
ToolType::CellOfMag502
|
||||
| ToolType::CellOfMag213
|
||||
| ToolType::PartsOfRobochao
|
||||
| ToolType::HeartOfOpaOpa
|
||||
| ToolType::HeartOfPian
|
||||
| ToolType::HeartOfChao
|
||||
| ToolType::SorcerersRightArm
|
||||
| ToolType::SBeatsArms
|
||||
| ToolType::PArmsArms
|
||||
| ToolType::DelsabersRightArm
|
||||
| ToolType::BringersRightArm
|
||||
| ToolType::DelsabersLeftArm
|
||||
| ToolType::SRedsArms
|
||||
| ToolType::DragonsClaw
|
||||
| ToolType::HildebearsHead
|
||||
| ToolType::HildebluesHead
|
||||
| ToolType::PartsOfBaranz
|
||||
| ToolType::BelrasRightArm
|
||||
| ToolType::GiGuesBody
|
||||
| ToolType::SinowBerillsArms
|
||||
| ToolType::GrassAssassinsArms
|
||||
| ToolType::BoomasRightArm
|
||||
| ToolType::GoboomasRightArm
|
||||
| ToolType::GigoboomasRightArm
|
||||
| ToolType::GalGryphonsWing
|
||||
| ToolType::RappysWing
|
||||
| ToolType::CladdingOfEpsilon
|
||||
| ToolType::DeRolLeShell
|
||||
| ToolType::BerillPhoton
|
||||
| ToolType::ParasiticGeneFlow
|
||||
| ToolType::MagicStoneIritista
|
||||
| ToolType::BlueBlackStone
|
||||
| ToolType::Syncesta
|
||||
| ToolType::MagicWater
|
||||
| ToolType::ParasiticCellTypeD
|
||||
| ToolType::MagicRockHeartKey
|
||||
| ToolType::MagicRockMoola
|
||||
| ToolType::StarAmplifier
|
||||
| ToolType::BookOfHitogata
|
||||
| ToolType::HeartOfChuChu
|
||||
| ToolType::PartsOfEggBlaster
|
||||
| ToolType::HeartOfAngel
|
||||
| ToolType::HeartOfDevil
|
||||
| ToolType::KitOfHamburger
|
||||
| ToolType::PanthersSpirit
|
||||
| ToolType::KitOfMark3
|
||||
| ToolType::KitOfMasterSystem
|
||||
| ToolType::KitOfGenesis
|
||||
| ToolType::KitOfSegaSaturn
|
||||
| ToolType::KitOfDreamcast
|
||||
| ToolType::AmplifierOfResta
|
||||
| ToolType::AmplifierOfAnti
|
||||
| ToolType::AmplifierOfShifta
|
||||
| ToolType::AmplifierOfDeband
|
||||
| ToolType::AmplifierOfFoie
|
||||
| ToolType::AmplifierOfGifoie
|
||||
| ToolType::AmplifierOfRafoie
|
||||
| ToolType::AmplifierOfBarta
|
||||
| ToolType::AmplifierOfGibarta
|
||||
| ToolType::AmplifierOfRabarta
|
||||
| ToolType::AmplifierOfZonde
|
||||
| ToolType::AmplifierOfGizonde
|
||||
| ToolType::AmplifierOfRazonde
|
||||
| ToolType::AmplifierOfRed
|
||||
| ToolType::AmplifierOfBlue
|
||||
| ToolType::AmplifierOfYellow
|
||||
| ToolType::HeartOfKapuKapu
|
||||
| ToolType::PhotonBooster
|
||||
| ToolType::Addslot
|
||||
| ToolType::PhotonDrop
|
||||
| ToolType::PhotonSphere
|
||||
| ToolType::PhotonCrystal
|
||||
| ToolType::SecretTicket
|
||||
| ToolType::PhotonTicket
|
||||
| ToolType::BookOfKatana1
|
||||
| ToolType::BookOfKatana2
|
||||
| ToolType::BookOfKatana3
|
||||
| ToolType::WeaponsBronzeBadge
|
||||
| ToolType::WeaponsSilverBadge
|
||||
| ToolType::WeaponsGoldBadge
|
||||
| ToolType::WeaponsCrystalBadge
|
||||
| ToolType::WeaponsSteelBadge
|
||||
| ToolType::WeaponsAluminumBadge
|
||||
| ToolType::WeaponsLeatherBadge
|
||||
| ToolType::WeaponsBoneBadge
|
||||
| ToolType::LetterOfAppreciation
|
||||
| ToolType::ItemTicket
|
||||
| ToolType::ValentinesChocolate
|
||||
| ToolType::NewYearsCard
|
||||
| ToolType::ChristmasCard
|
||||
| ToolType::BirthdayCard
|
||||
| ToolType::ProofOfSonicTeam
|
||||
| ToolType::SpecialEventTicket
|
||||
| ToolType::FlowerBouquet
|
||||
| ToolType::Cake
|
||||
| ToolType::Accessories
|
||||
| ToolType::MrNakasBusinessCard
|
||||
| ToolType::Present
|
||||
| ToolType::Chocolate
|
||||
| ToolType::Candy
|
||||
| ToolType::Cake2
|
||||
| ToolType::WeaponsSilverBadge2
|
||||
| ToolType::WeaponsGoldBadge2
|
||||
| ToolType::WeaponsCrystalBadge2
|
||||
| ToolType::WeaponsSteelBadge2
|
||||
| ToolType::WeaponsAluminumBadge2
|
||||
| ToolType::WeaponsLeatherBadge2
|
||||
| ToolType::WeaponsBoneBadge2
|
||||
| ToolType::Bouquet
|
||||
| ToolType::Decoction
|
||||
| ToolType::ChristmasPresent
|
||||
| ToolType::EasterEgg
|
||||
| ToolType::JackOLantern
|
||||
| ToolType::DiskVol1WeddingMarch
|
||||
| ToolType::DiskVol2DayLight
|
||||
| ToolType::DiskVol3BurningRangers
|
||||
| ToolType::DiskVol4OpenYourHeart
|
||||
| ToolType::DiskVol5LiveLearn
|
||||
| ToolType::DiskVol6Nights
|
||||
| ToolType::DiskVol7EndingThemePianoVer
|
||||
| ToolType::DiskVol8HeartToHeart
|
||||
| ToolType::DiskVol9StrangeBlue
|
||||
| ToolType::DiskVol10ReunionSystem
|
||||
| ToolType::DiskVol11Pinnacles
|
||||
| ToolType::DiskVol12FightInsideTheSpaceship
|
||||
| ToolType::HuntersReport
|
||||
| ToolType::HuntersReport2
|
||||
| ToolType::HuntersReport3
|
||||
| ToolType::HuntersReport4
|
||||
| ToolType::HuntersReport5
|
||||
| ToolType::Tablet
|
||||
| ToolType::Unknown2
|
||||
| ToolType::DragonScale
|
||||
| ToolType::HeavenStrikerCoat
|
||||
| ToolType::PioneerParts
|
||||
| ToolType::AmitiesMemo
|
||||
| ToolType::HeartOfMorolian
|
||||
| ToolType::RappysBeak
|
||||
| ToolType::YahoosEngine
|
||||
| ToolType::DPhotonCore
|
||||
| ToolType::LibertaKit
|
||||
| ToolType::CellOfMag0503
|
||||
| ToolType::CellOfMag0504
|
||||
| ToolType::CellOfMag0505
|
||||
| ToolType::CellOfMag0506
|
||||
| ToolType::CellOfMag0507
|
||||
| ToolType::TeamPoints500
|
||||
| ToolType::TeamPoints1000
|
||||
| ToolType::TeamPoints5000
|
||||
| ToolType::TeamPoints10000
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: do we actually need this function?
|
||||
pub fn is_material(self) -> bool {
|
||||
matches!(
|
||||
self.tool,
|
||||
ToolType::PowerMaterial
|
||||
| ToolType::MindMaterial
|
||||
| ToolType::EvadeMaterial
|
||||
| ToolType::HpMaterial
|
||||
| ToolType::TpMaterial
|
||||
| ToolType::DefMaterial
|
||||
| ToolType::LuckMaterial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -384,4 +384,76 @@ impl Unit {
|
||||
Err(ItemParseError::InvalidUnitBytes) // TODO: error handling if wrong bytes are given
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
matches!(
|
||||
self.unit,
|
||||
UnitType::GodPower
|
||||
| UnitType::GodMind
|
||||
| UnitType::GodArm
|
||||
| UnitType::GodLegs
|
||||
| UnitType::GodHp
|
||||
| UnitType::GodTp
|
||||
| UnitType::GodBody
|
||||
| UnitType::GodLuck
|
||||
| UnitType::HeroAbility
|
||||
| UnitType::GodAbility
|
||||
| UnitType::AllResist
|
||||
| UnitType::SuperResist
|
||||
| UnitType::PerfectResist
|
||||
| UnitType::HpRevival
|
||||
| UnitType::TpRevival
|
||||
| UnitType::PbAmplifier
|
||||
| UnitType::PbGenerate
|
||||
| UnitType::PbCreate
|
||||
| UnitType::DevilTechnique
|
||||
| UnitType::GodTechnique
|
||||
| UnitType::DevilBattle
|
||||
| UnitType::GodBattle
|
||||
| UnitType::CurePoison
|
||||
| UnitType::CureParalysis
|
||||
| UnitType::CureSlow
|
||||
| UnitType::CureConfuse
|
||||
| UnitType::CureFreeze
|
||||
| UnitType::CureShock
|
||||
| UnitType::YasakaniMagatama
|
||||
| UnitType::V101
|
||||
| UnitType::V501
|
||||
| UnitType::V502
|
||||
| UnitType::V801
|
||||
| UnitType::Limiter
|
||||
| UnitType::Adept
|
||||
| UnitType::SwordsmanLore
|
||||
| UnitType::ProofOfSwordSaint
|
||||
| UnitType::Smartlink
|
||||
| UnitType::DivineProtection
|
||||
| UnitType::HeavenlyBattle
|
||||
| UnitType::HeavenlyPower
|
||||
| UnitType::HeavenlyMind
|
||||
| UnitType::HeavenlyArms
|
||||
| UnitType::HeavenlyLegs
|
||||
| UnitType::HeavenlyBody
|
||||
| UnitType::HeavenlyLuck
|
||||
| UnitType::HeavenlyAbility
|
||||
| UnitType::CenturionAbility
|
||||
| UnitType::FriendRing
|
||||
| UnitType::HeavenlyHp
|
||||
| UnitType::HeavenlyTp
|
||||
| UnitType::HeavenlyResist
|
||||
| UnitType::HeavenlyTechnique
|
||||
| UnitType::HpRessurection
|
||||
| UnitType::TpRessurection
|
||||
| UnitType::PbIncrease
|
||||
)
|
||||
}
|
||||
|
||||
pub fn modifier_stars(&self) -> i8 {
|
||||
match self.modifier {
|
||||
Some(UnitModifier::PlusPlus) => 1,
|
||||
Some(UnitModifier::Plus) => 1,
|
||||
Some(UnitModifier::Minus) => -1,
|
||||
Some(UnitModifier::MinusMinus) => -1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,8 @@ impl WeaponSpecial {
|
||||
pub fn value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn rank_up(&self) -> WeaponSpecial {
|
||||
match self {
|
||||
WeaponSpecial::Draw => WeaponSpecial::Drain,
|
||||
@ -138,6 +140,7 @@ impl WeaponSpecial {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn rank_down(&self) -> WeaponSpecial {
|
||||
match self {
|
||||
WeaponSpecial::Draw => WeaponSpecial::Draw,
|
||||
@ -1454,7 +1457,7 @@ pub enum WeaponModifier {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Weapon {
|
||||
pub weapon: WeaponType,
|
||||
pub special: Option<WeaponSpecial>,
|
||||
@ -1584,6 +1587,68 @@ impl Weapon {
|
||||
Err(ItemParseError::InvalidWeaponBytes) // TODO: error handling if wrong bytes are given
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: invert this? ie: handgun, saber, dagger etc. => false, _ => true?
|
||||
pub fn is_rare_item(self) -> bool {
|
||||
!matches!(
|
||||
self.weapon,
|
||||
WeaponType::Saber
|
||||
| WeaponType::Brand
|
||||
| WeaponType::Buster
|
||||
| WeaponType::Pallasch
|
||||
| WeaponType::Gladius
|
||||
| WeaponType::Sword
|
||||
| WeaponType::Gigush
|
||||
| WeaponType::Breaker
|
||||
| WeaponType::Claymore
|
||||
| WeaponType::Calibur
|
||||
| WeaponType::Dagger
|
||||
| WeaponType::Knife
|
||||
| WeaponType::Blade
|
||||
| WeaponType::Edge
|
||||
| WeaponType::Ripper
|
||||
| WeaponType::Partisan
|
||||
| WeaponType::Halbert
|
||||
| WeaponType::Glaive
|
||||
| WeaponType::Berdys
|
||||
| WeaponType::Gungnir
|
||||
| WeaponType::Slicer
|
||||
| WeaponType::Spinner
|
||||
| WeaponType::Cutter
|
||||
| WeaponType::Sawcer
|
||||
| WeaponType::Diska
|
||||
| WeaponType::Handgun
|
||||
| WeaponType::Autogun
|
||||
| WeaponType::Lockgun
|
||||
| WeaponType::Railgun
|
||||
| WeaponType::Raygun
|
||||
| WeaponType::Rifle
|
||||
| WeaponType::Sniper
|
||||
| WeaponType::Blaster
|
||||
| WeaponType::Beam
|
||||
| WeaponType::Laser
|
||||
| WeaponType::Mechgun
|
||||
| WeaponType::Assault
|
||||
| WeaponType::Repeater
|
||||
| WeaponType::Gatling
|
||||
| WeaponType::Vulcan
|
||||
| WeaponType::Shot
|
||||
| WeaponType::Spread
|
||||
| WeaponType::Cannon
|
||||
| WeaponType::Launcher
|
||||
| WeaponType::Arms
|
||||
| WeaponType::Cane
|
||||
| WeaponType::Stick
|
||||
| WeaponType::Mace
|
||||
| WeaponType::Club
|
||||
| WeaponType::Rod
|
||||
| WeaponType::Pole
|
||||
| WeaponType::Pillar
|
||||
| WeaponType::Striker
|
||||
| WeaponType::Wand
|
||||
| WeaponType::Staff
|
||||
| WeaponType::Baton
|
||||
| WeaponType::Scepter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -232,7 +232,7 @@ mod test {
|
||||
async fn save_user(&mut self, _user: &UserAccountEntity) -> Result<(), GatewayError> {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap());
|
||||
|
||||
|
@ -14,6 +14,7 @@ pub struct CharacterBytesBuilder<'a> {
|
||||
}
|
||||
|
||||
impl<'a> CharacterBytesBuilder<'a> {
|
||||
#[must_use]
|
||||
pub fn character(self, character: &'a CharacterEntity) -> CharacterBytesBuilder<'a> {
|
||||
CharacterBytesBuilder {
|
||||
character: Some(character),
|
||||
@ -21,6 +22,7 @@ impl<'a> CharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn stats(self, stats: &'a CharacterStats) -> CharacterBytesBuilder<'a> {
|
||||
CharacterBytesBuilder {
|
||||
stats: Some(stats),
|
||||
@ -28,6 +30,7 @@ impl<'a> CharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn level(self, level: u32) -> CharacterBytesBuilder<'a> {
|
||||
CharacterBytesBuilder {
|
||||
level: Some(level),
|
||||
@ -35,6 +38,7 @@ impl<'a> CharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn meseta(self, meseta: Meseta) -> CharacterBytesBuilder<'a> {
|
||||
CharacterBytesBuilder {
|
||||
meseta: Some(meseta),
|
||||
@ -95,6 +99,7 @@ pub struct FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
|
||||
impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
#[must_use]
|
||||
pub fn character(self, character: &'a CharacterEntity) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
character: Some(character),
|
||||
@ -102,6 +107,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn stats(self, stats: &'a CharacterStats) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
stats: Some(stats),
|
||||
@ -109,6 +115,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn level(self, level: u32) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
level: Some(level),
|
||||
@ -116,6 +123,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn meseta(self, meseta: Meseta) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
meseta: Some(meseta),
|
||||
@ -123,6 +131,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn inventory(self, inventory: &'a CharacterInventory) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
inventory: Some(inventory),
|
||||
@ -130,6 +139,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn bank(self, bank: &'a CharacterBank) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
bank: Some(bank),
|
||||
@ -137,6 +147,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn key_config(self, key_config: &'a [u8; 0x16C]) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
key_config: Some(key_config),
|
||||
@ -144,6 +155,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn joystick_config(self, joystick_config: &'a [u8; 0x38]) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
joystick_config: Some(joystick_config),
|
||||
@ -151,6 +163,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn symbol_chat(self, symbol_chat: &'a [u8; 1248]) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
symbol_chat: Some(symbol_chat),
|
||||
@ -158,6 +171,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn tech_menu(self, tech_menu: &'a [u8; 40]) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
tech_menu: Some(tech_menu),
|
||||
@ -165,6 +179,7 @@ impl<'a> FullCharacterBytesBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn option_flags(self, option_flags: u32) -> FullCharacterBytesBuilder<'a> {
|
||||
FullCharacterBytesBuilder {
|
||||
option_flags: Some(option_flags),
|
||||
|
@ -1,13 +1,14 @@
|
||||
use std::cmp::Ordering;
|
||||
use thiserror::Error;
|
||||
use libpso::character::character;//::InventoryItem;
|
||||
use libpso::character::character;
|
||||
use crate::entity::character::CharacterEntityId;
|
||||
use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, InventoryEntity, InventoryItemEntity, EquippedEntity};
|
||||
use crate::entity::item::tool::Tool;
|
||||
use crate::entity::item::tool::{Tool, ToolType};
|
||||
use crate::entity::item::mag::Mag;
|
||||
use crate::entity::item::weapon::Weapon;
|
||||
use crate::ship::items::{ClientItemId, BankItem, BankItemHandle};
|
||||
use crate::ship::items::{ClientItemId, BankItem, BankItemHandle, ItemManagerError};
|
||||
use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem};
|
||||
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
|
||||
|
||||
const INVENTORY_CAPACITY: usize = 30;
|
||||
|
||||
@ -239,6 +240,62 @@ impl InventoryItem {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sell_price(&self) -> Result<u32, ItemManagerError> {
|
||||
match self {
|
||||
InventoryItem::Individual(individual_item) => {
|
||||
match &individual_item.item {
|
||||
// TODO: can wrapped items be sold?
|
||||
ItemDetail::Weapon(w) => {
|
||||
if !w.tekked {
|
||||
return Ok(1u32)
|
||||
}
|
||||
if w.is_rare_item() {
|
||||
return Ok(10u32)
|
||||
}
|
||||
Ok((WeaponShopItem::from(w).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::Armor(a) => {
|
||||
if a.is_rare_item() {
|
||||
return Ok(10u32)
|
||||
}
|
||||
Ok((ArmorShopItem::from(a).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::Shield(s) => {
|
||||
if s.is_rare_item() {
|
||||
return Ok(10u32)
|
||||
}
|
||||
Ok((ArmorShopItem::from(s).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::Unit(u) => {
|
||||
if u.is_rare_item() {
|
||||
return Ok(10u32)
|
||||
}
|
||||
Ok((ArmorShopItem::from(u).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::Tool(t) => {
|
||||
if !matches!(t.tool, ToolType::PhotonDrop | ToolType::PhotonSphere | ToolType::PhotonCrystal) && t.is_rare_item() {
|
||||
return Ok(10u32)
|
||||
}
|
||||
Ok((ToolShopItem::from(t).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::TechniqueDisk(d) => {
|
||||
Ok((ToolShopItem::from(d).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::Mag(_m) => {
|
||||
Err(ItemManagerError::ItemNotSellable(self.clone()))
|
||||
},
|
||||
ItemDetail::ESWeapon(_e) => {
|
||||
Ok(10u32)
|
||||
},
|
||||
}
|
||||
},
|
||||
// the number of stacked items sold is handled by the caller. this is just the price of 1
|
||||
InventoryItem::Stacked(stacked_item) => {
|
||||
Ok((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stacked(&self) -> Option<&StackedInventoryItem> {
|
||||
match self {
|
||||
InventoryItem::Stacked(ref stacked_inventory_item) => Some(stacked_inventory_item),
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::ship::items::ClientItemId;
|
||||
use std::collections::HashMap;
|
||||
use std::cmp::Ordering;
|
||||
use std::cell::RefCell;
|
||||
use thiserror::Error;
|
||||
use crate::entity::gateway::{EntityGateway, GatewayError};
|
||||
@ -72,6 +73,12 @@ pub enum ItemManagerError {
|
||||
GatewayError(#[from] GatewayError),
|
||||
#[error("stacked item")]
|
||||
StackedItemError(Vec<ItemEntity>),
|
||||
#[error("item not sellable")]
|
||||
ItemNotSellable(InventoryItem),
|
||||
#[error("wallet full")]
|
||||
WalletFull,
|
||||
#[error("invalid sale")]
|
||||
InvalidSale,
|
||||
ItemTransactionAction(Box<dyn std::error::Error + Send + Sync>),
|
||||
#[error("invalid trade")]
|
||||
InvalidTrade,
|
||||
@ -887,6 +894,48 @@ impl ItemManager {
|
||||
Ok(inventory_item)
|
||||
}
|
||||
|
||||
pub async fn player_sells_item<EG: EntityGateway>(&mut self,
|
||||
entity_gateway: &mut EG,
|
||||
character: &mut CharacterEntity,
|
||||
item_id: ClientItemId,
|
||||
amount: usize)
|
||||
-> Result<(), anyhow::Error> {
|
||||
let character_meseta = self.get_character_meseta(&character.id)?.0;
|
||||
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
||||
let sold_item_handle = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
||||
if let Some(item_sold) = sold_item_handle.item() {
|
||||
let unit_price = item_sold.get_sell_price()?; {
|
||||
let total_sale = unit_price * amount as u32;
|
||||
if character_meseta + total_sale <= 999999 {
|
||||
match item_sold {
|
||||
InventoryItem::Individual(i) => {
|
||||
entity_gateway.add_item_note(&i.entity_id, ItemNote::SoldToShop).await?;
|
||||
inventory.remove_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?;
|
||||
},
|
||||
InventoryItem::Stacked(s) => {
|
||||
match amount.cmp(&s.count()) {
|
||||
Ordering::Less | Ordering::Equal => {
|
||||
sold_item_handle.consume(amount)?;
|
||||
},
|
||||
Ordering::Greater => return Err(ItemManagerError::InvalidSale.into()),
|
||||
};
|
||||
},
|
||||
}
|
||||
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
||||
let character_meseta = self.get_character_meseta_mut(&character.id)?;
|
||||
character_meseta.0 += total_sale;
|
||||
entity_gateway.set_character_meseta(&character.id, *character_meseta).await?;
|
||||
}
|
||||
else {
|
||||
return Err(ItemManagerError::WalletFull.into())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ItemManagerError::ItemIdNotInInventory(item_id).into())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: check if slot exists before putting units into it
|
||||
pub async fn player_equips_item<EG: EntityGateway>(&mut self,
|
||||
entity_gateway: &mut EG,
|
||||
@ -944,10 +993,9 @@ impl ItemManager {
|
||||
.ok_or(ItemManagerError::WrongItemType(item_id))?;
|
||||
|
||||
let entity_id = individual.entity_id;
|
||||
let mut weapon = individual
|
||||
let mut weapon = *individual
|
||||
.weapon()
|
||||
.ok_or(ItemManagerError::WrongItemType(item_id))?
|
||||
.clone();
|
||||
.ok_or(ItemManagerError::WrongItemType(item_id))?;
|
||||
|
||||
weapon.apply_modifier(&tek);
|
||||
entity_gateway.add_weapon_modifier(&entity_id, tek).await?;
|
||||
@ -955,7 +1003,7 @@ impl ItemManager {
|
||||
inventory.add_item(InventoryItem::Individual(IndividualInventoryItem {
|
||||
entity_id,
|
||||
item_id,
|
||||
item: ItemDetail::Weapon(weapon.clone()),
|
||||
item: ItemDetail::Weapon(weapon),
|
||||
}));
|
||||
|
||||
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
||||
@ -1018,9 +1066,9 @@ impl ItemManager {
|
||||
TradeItem::Stacked(item_id, amount) => {
|
||||
let stacked_inventory_item = src_inventory
|
||||
.get_item_by_id(*item_id)
|
||||
.ok_or_else(|| TradeError::InvalidItemId(*item_id))?
|
||||
.ok_or(TradeError::InvalidItemId(*item_id))?
|
||||
.stacked()
|
||||
.ok_or_else(|| TradeError::InvalidItemId(*item_id))?;
|
||||
.ok_or(TradeError::InvalidItemId(*item_id))?;
|
||||
match dest_inventory.space_for_stacked_item(&stacked_inventory_item.tool, *amount) {
|
||||
SpaceForStack::Yes(YesThereIsSpace::ExistingStack) => {
|
||||
Ok(acc)
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod bank;
|
||||
mod floor;
|
||||
pub mod inventory;
|
||||
mod manager;
|
||||
pub mod manager;
|
||||
pub mod transaction;
|
||||
pub mod use_tool;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
@ -341,6 +341,7 @@ pub struct MapAreaLookupBuilder {
|
||||
}
|
||||
|
||||
impl MapAreaLookupBuilder {
|
||||
#[must_use]
|
||||
pub fn add(mut self, value: u16, map_area: MapArea) -> MapAreaLookupBuilder {
|
||||
self.map_areas.insert(value, map_area);
|
||||
self
|
||||
|
@ -316,6 +316,7 @@ impl MapEnemy {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn set_shiny(self) -> MapEnemy {
|
||||
MapEnemy {
|
||||
shiny: true,
|
||||
@ -337,6 +338,7 @@ impl MapEnemy {
|
||||
TODO: distinguish between a `random` rare monster and a `set/guaranteed` rare monster? (does any acceptable quest even have this?)
|
||||
guaranteed rare monsters don't count towards the limit
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn set_rare_appearance(self) -> MapEnemy {
|
||||
match (self.monster, self.map_area.to_episode()) {
|
||||
(MonsterType::RagRappy, Episode::One) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
|
||||
@ -357,6 +359,7 @@ impl MapEnemy {
|
||||
}
|
||||
|
||||
// in theory this should only be called on monsters we know can have rare types
|
||||
#[must_use]
|
||||
pub fn roll_appearance_for_mission(self, rare_monster_table: &RareMonsterAppearTable) -> MapEnemy {
|
||||
if rare_monster_table.roll_appearance(&self.monster) {
|
||||
return self.set_rare_appearance()
|
||||
|
@ -285,9 +285,9 @@ impl Maps {
|
||||
enemy_data
|
||||
}),
|
||||
object_data: map_variants.iter()
|
||||
.map(|map_variant| {
|
||||
.flat_map(|map_variant| {
|
||||
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
|
||||
}).flatten().collect(),
|
||||
}).collect(),
|
||||
map_variants,
|
||||
};
|
||||
maps.roll_monster_appearance(rare_monster_table);
|
||||
|
@ -81,9 +81,9 @@ where
|
||||
{
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get_mut(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
let monster = room.maps.enemy_by_id(request_item.enemy_id as usize)?;
|
||||
if monster.dropped_item {
|
||||
@ -187,9 +187,9 @@ EG: EntityGateway
|
||||
{
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get_mut(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
let box_object = room.maps.object_by_id(box_drop_request.object_id as usize)?;
|
||||
if box_object.dropped_item {
|
||||
@ -297,13 +297,12 @@ where
|
||||
};
|
||||
|
||||
Ok(Box::new(other_clients_in_area.into_iter()
|
||||
.map(move |c| {
|
||||
.flat_map(move |c| {
|
||||
bank_action_pkts.clone().into_iter()
|
||||
.map(move |pkt| {
|
||||
(c.client, pkt)
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
))
|
||||
}
|
||||
|
||||
@ -319,9 +318,9 @@ pub async fn shop_request(id: ClientId,
|
||||
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_ref()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
let level = level_table.get_level_from_exp(client.character.char_class, client.character.exp) as usize;
|
||||
let shop_list = match shop_request.shop_type {
|
||||
SHOP_OPTION_WEAPON => {
|
||||
@ -381,11 +380,11 @@ where
|
||||
};
|
||||
|
||||
let character_meseta = item_manager.get_character_meseta_mut(&client.character.id)?;
|
||||
if character_meseta.0 < item.price() as u32 {
|
||||
if character_meseta.0 < (item.price() * buy_item.amount as usize) as u32 {
|
||||
return Err(ShipError::ShopError.into())
|
||||
}
|
||||
|
||||
character_meseta.0 -= item.price() as u32;
|
||||
character_meseta.0 -= (item.price() * buy_item.amount as usize) 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?;
|
||||
@ -446,11 +445,10 @@ where
|
||||
let inventory = item_manager.get_character_inventory(&client.character)?;
|
||||
let item = inventory.get_item_by_id(ClientItemId(tek_request.item_id))
|
||||
.ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?;
|
||||
let mut weapon = item.individual()
|
||||
let mut weapon = *item.individual()
|
||||
.ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?
|
||||
.weapon()
|
||||
.ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?
|
||||
.clone();
|
||||
.ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?;
|
||||
|
||||
weapon.apply_modifier(&item::weapon::WeaponModifier::Tekked {
|
||||
special: special_mod,
|
||||
|
@ -20,9 +20,9 @@ pub async fn request_exp<EG: EntityGateway>(id: ClientId,
|
||||
let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get_mut(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
let monster = room.maps.enemy_by_id(request_exp.enemy_id as usize)?;
|
||||
let monster_stats = room.monster_stats.get(&monster.monster).ok_or(ShipError::UnknownMonster(monster.monster))?;
|
||||
@ -76,9 +76,9 @@ where
|
||||
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get_mut(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
let area = room.map_areas.get_area_map(player_drop_item.map_area)?;
|
||||
item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, ClientItemId(player_drop_item.item_id), (*area, player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?;
|
||||
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
@ -99,9 +99,9 @@ pub fn drop_coordinates(id: ClientId,
|
||||
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_ref()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
client.item_drop_location = Some(ItemDropLocation {
|
||||
map_area: *room.map_areas.get_area_map(drop_coordinates.map_area)?,
|
||||
@ -110,7 +110,7 @@ pub fn drop_coordinates(id: ClientId,
|
||||
item_id: ClientItemId(drop_coordinates.item_id),
|
||||
});
|
||||
|
||||
Ok(Box::new(None.into_iter()))
|
||||
Ok(Box::new(None.into_iter())) // TODO: do we need to send a packet here?
|
||||
}
|
||||
|
||||
pub async fn no_longer_has_item<EG>(id: ClientId,
|
||||
@ -140,7 +140,7 @@ where
|
||||
|
||||
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| {
|
||||
.flat_map(move |c| {
|
||||
std::iter::once((c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_meseta_pkt.clone())))))
|
||||
.chain(
|
||||
if c.client != id {
|
||||
@ -153,7 +153,6 @@ where
|
||||
}
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
))
|
||||
}
|
||||
else {
|
||||
@ -191,9 +190,9 @@ pub fn update_player_position(id: ClientId,
|
||||
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
if let Ok(room_id) = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() }) {
|
||||
let room = rooms.get(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_ref()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
match &message.msg {
|
||||
GameMessage::PlayerChangedMap(p) => {
|
||||
@ -291,7 +290,7 @@ where
|
||||
let item_used_type = item_manager.player_consumes_tool(entity_gateway, &mut client.character, ClientItemId(player_use_tool.item_id), 1).await?;
|
||||
|
||||
item_manager.use_item(item_used_type, entity_gateway, &mut client.character).await?;
|
||||
Ok(Box::new(None.into_iter()))
|
||||
Ok(Box::new(None.into_iter())) // TODO: should probably tell other players we used an item
|
||||
}
|
||||
|
||||
pub async fn player_used_medical_center<EG>(id: ClientId,
|
||||
@ -353,7 +352,7 @@ where
|
||||
0
|
||||
};
|
||||
item_manager.player_equips_item(entity_gateway, &client.character, ClientItemId(pkt.item_id), equip_slot).await?;
|
||||
Ok(Box::new(None.into_iter()))
|
||||
Ok(Box::new(None.into_iter())) // TODO: tell other players you equipped an item
|
||||
}
|
||||
|
||||
pub async fn player_unequips_item<EG>(id: ClientId,
|
||||
@ -367,7 +366,7 @@ where
|
||||
{
|
||||
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
item_manager.player_unequips_item(entity_gateway, &client.character, ClientItemId(pkt.item_id)).await?;
|
||||
Ok(Box::new(None.into_iter()))
|
||||
Ok(Box::new(None.into_iter())) // TODO: tell other players if you unequip an item
|
||||
}
|
||||
|
||||
pub async fn player_sorts_items<EG>(id: ClientId,
|
||||
@ -381,5 +380,21 @@ where
|
||||
{
|
||||
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
item_manager.player_sorts_items(entity_gateway, &client.character, pkt.item_ids).await?;
|
||||
Ok(Box::new(None.into_iter())) // Do clients care about the order of other clients items?
|
||||
Ok(Box::new(None.into_iter())) // TODO: clients probably care about each others item orders
|
||||
}
|
||||
|
||||
pub async fn player_sells_item<EG> (id: ClientId,
|
||||
sold_item: &PlayerSoldItem,
|
||||
entity_gateway: &mut EG,
|
||||
// client_location: &ClientLocation,
|
||||
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))?;
|
||||
item_manager.player_sells_item(entity_gateway, &mut client.character, ClientItemId(sold_item.item_id), sold_item.amount as usize).await?;
|
||||
// TODO: send the packet to other clients
|
||||
Ok(Box::new(None.into_iter()))
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quest
|
||||
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get_mut(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
|
||||
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &room.rare_monster_table);
|
||||
room.map_areas = quest.map_areas.clone();
|
||||
|
||||
@ -95,9 +95,9 @@ pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quest
|
||||
client.done_loading_quest = false;
|
||||
}
|
||||
});
|
||||
Ok(Box::new(area_clients.into_iter().map(move |c| {
|
||||
Ok(Box::new(area_clients.into_iter().flat_map(move |c| {
|
||||
vec![(c.client, SendShipPacket::QuestHeader(bin.clone())), (c.client, SendShipPacket::QuestHeader(dat.clone()))]
|
||||
}).flatten()))
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn quest_file_request(id: ClientId, quest_file_request: &QuestFileRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
|
||||
|
@ -157,10 +157,9 @@ pub fn done_bursting(id: ClientId,
|
||||
};
|
||||
}
|
||||
let area_client = client_location.get_local_client(id).unwrap(); // TODO: unwrap
|
||||
|
||||
let mut result: Box<dyn Iterator<Item=(ClientId, SendShipPacket)> + Send> = Box::new(
|
||||
client_location.get_client_neighbors(id).unwrap().into_iter() // TODO: unwrap
|
||||
.map(move |client| {
|
||||
.flat_map(move |client| {
|
||||
vec![
|
||||
(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
|
||||
client: area_client.local_client.id(),
|
||||
@ -168,7 +167,6 @@ pub fn done_bursting(id: ClientId,
|
||||
})))),
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
);
|
||||
|
||||
// TODO: check how often `done_bursting` is called. ie: make sure it's only used when joining a room and not each time a player warps in a pipe
|
||||
|
@ -481,7 +481,7 @@ where
|
||||
let clients_in_room = client_location.get_all_clients_by_client(id)?;
|
||||
let traded_item_packets = traded_items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
.flat_map(|item| {
|
||||
match item.item_detail {
|
||||
ItemToTradeDetail::Individual(item_detail) => {
|
||||
[
|
||||
@ -503,8 +503,7 @@ where
|
||||
},
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(move |packet| {
|
||||
.flat_map(move |packet| {
|
||||
clients_in_room
|
||||
.clone()
|
||||
.into_iter()
|
||||
@ -521,8 +520,7 @@ where
|
||||
_ => 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()))
|
||||
|
@ -357,31 +357,37 @@ impl<EG: EntityGateway> Default for ShipServerStateBuilder<EG> {
|
||||
}
|
||||
|
||||
impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
|
||||
#[must_use]
|
||||
pub fn gateway(mut self, entity_gateway: EG) -> ShipServerStateBuilder<EG> {
|
||||
self.entity_gateway = Some(entity_gateway);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn name(mut self, name: String) -> ShipServerStateBuilder<EG> {
|
||||
self.name = Some(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn ip(mut self, ip: Ipv4Addr) -> ShipServerStateBuilder<EG> {
|
||||
self.ip = Some(ip);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn port(mut self, port: u16) -> ShipServerStateBuilder<EG> {
|
||||
self.port = Some(port);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn auth_token(mut self, auth_token: AuthToken) -> ShipServerStateBuilder<EG> {
|
||||
self.auth_token = Some(auth_token);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder<EG> {
|
||||
self.num_blocks = num_blocks;
|
||||
self
|
||||
@ -510,6 +516,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
|
||||
GameMessage::SortItems(sort_items) => {
|
||||
handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await?
|
||||
},
|
||||
GameMessage::PlayerSoldItem(player_sold_item) => {
|
||||
handler::message::player_sells_item(id, player_sold_item, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await?
|
||||
},
|
||||
_ => {
|
||||
let cmsg = msg.clone();
|
||||
let block = self.blocks.with_client(id, &self.clients)?;
|
||||
|
@ -12,47 +12,55 @@ use crate::entity::item::unit::{Unit, UnitType};
|
||||
use crate::ship::shops::ShopItem;
|
||||
use crate::ship::item_stats::{ARMOR_STATS, SHIELD_STATS, UNIT_STATS};
|
||||
|
||||
// #[derive(Debug)]
|
||||
// pub enum ArmorShopItem {
|
||||
// Frame(ArmorType, usize), // slots
|
||||
// Barrier(ShieldType),
|
||||
// Unit(UnitType),
|
||||
// }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArmorShopItem {
|
||||
Frame(ArmorType, usize),
|
||||
Barrier(ShieldType),
|
||||
Unit(UnitType),
|
||||
Frame(Armor), // slots
|
||||
Barrier(Shield),
|
||||
Unit(Unit),
|
||||
}
|
||||
|
||||
const ARMOR_MULTIPLIER: f32 = 0.799_999_95;
|
||||
const SHIELD_MULTIPLIER: f32 = 1.5;
|
||||
const UNIT_MULTIPLIER: f32 = 1000.0;
|
||||
|
||||
// TODO: reduce the number of type casts?
|
||||
impl ShopItem for ArmorShopItem {
|
||||
fn price(&self) -> usize {
|
||||
match self {
|
||||
ArmorShopItem::Frame(frame, slot) => {
|
||||
ARMOR_STATS.get(frame)
|
||||
ArmorShopItem::Frame(frame) => {
|
||||
ARMOR_STATS.get(&frame.armor)
|
||||
.map(|frame_stats| {
|
||||
let mut price = (frame_stats.dfp + frame_stats.evp) as f32;
|
||||
let mut price = (frame_stats.dfp + frame_stats.evp + frame.dfp as i32 + frame.evp as i32) as f32;
|
||||
price *= price;
|
||||
price /= ARMOR_MULTIPLIER;
|
||||
price += 70.0 * frame_stats.level_req as f32;
|
||||
price += 70.0 * frame_stats.level_req as f32 * *slot as f32;
|
||||
price += 70.0 * (frame_stats.level_req + 1) as f32;
|
||||
price += 70.0 * (frame_stats.level_req + 1) as f32 * frame.slots as f32;
|
||||
price as usize
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
},
|
||||
ArmorShopItem::Barrier(barrier) => {
|
||||
SHIELD_STATS.get(barrier)
|
||||
SHIELD_STATS.get(&barrier.shield)
|
||||
.map(|barrier_stats| {
|
||||
let mut price = (barrier_stats.dfp + barrier_stats.evp) as f32;
|
||||
let mut price = (barrier_stats.dfp + barrier_stats.evp + barrier.dfp as i32 + barrier.evp as i32) as f32;
|
||||
price *= price;
|
||||
price /= SHIELD_MULTIPLIER;
|
||||
price += 70.0 * barrier_stats.level_req as f32;
|
||||
price += 70.0 * (barrier_stats.level_req + 1) as f32;
|
||||
price as usize
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
},
|
||||
ArmorShopItem::Unit(unit) => {
|
||||
UNIT_STATS.get(unit)
|
||||
UNIT_STATS.get(&unit.unit)
|
||||
.map(|unit_stats| {
|
||||
(unit_stats.stars as f32 * UNIT_MULTIPLIER) as usize
|
||||
((unit_stats.stars as f32 + unit.modifier_stars() as f32) * UNIT_MULTIPLIER) as usize
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
}
|
||||
@ -65,24 +73,24 @@ impl ShopItem for ArmorShopItem {
|
||||
|
||||
fn as_item(&self) -> ItemDetail {
|
||||
match self {
|
||||
ArmorShopItem::Frame(frame, slot) => {
|
||||
ArmorShopItem::Frame(frame) => {
|
||||
ItemDetail::Armor(Armor {
|
||||
armor: *frame,
|
||||
armor: frame.armor,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
slots: *slot as u8,
|
||||
slots: frame.slots as u8,
|
||||
})
|
||||
},
|
||||
ArmorShopItem::Barrier(barrier) => {
|
||||
ItemDetail::Shield(Shield {
|
||||
shield: *barrier,
|
||||
shield: barrier.shield,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
})
|
||||
},
|
||||
ArmorShopItem::Unit(unit) => {
|
||||
ItemDetail::Unit(Unit {
|
||||
unit: *unit,
|
||||
unit: unit.unit,
|
||||
modifier: None,
|
||||
})
|
||||
},
|
||||
@ -90,6 +98,24 @@ impl ShopItem for ArmorShopItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Armor> for ArmorShopItem {
|
||||
fn from(armor: &Armor) -> ArmorShopItem {
|
||||
ArmorShopItem::Frame(*armor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Shield> for ArmorShopItem {
|
||||
fn from(shield: &Shield) -> ArmorShopItem {
|
||||
ArmorShopItem::Barrier(*shield)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Unit> for ArmorShopItem {
|
||||
fn from(unit: &Unit) -> ArmorShopItem {
|
||||
ArmorShopItem::Unit(*unit)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct FrameTierItem {
|
||||
@ -262,7 +288,12 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
let frame_detail = tier.item.get(frame_choice.sample(&mut self.rng)).unwrap();
|
||||
let slot = self.frame.slot_rate.get(slot_choice.sample(&mut self.rng)).unwrap();
|
||||
|
||||
ArmorShopItem::Frame(frame_detail.item, slot.slot)
|
||||
ArmorShopItem::Frame(Armor {
|
||||
armor: frame_detail.item,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
slots: slot.slot as u8,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@ -279,7 +310,11 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
.map(|_| {
|
||||
let barrier_detail = tier.item.get(barrier_choice.sample(&mut self.rng)).unwrap();
|
||||
|
||||
ArmorShopItem::Barrier(barrier_detail.item)
|
||||
ArmorShopItem::Barrier(Shield {
|
||||
shield: barrier_detail.item,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@ -295,7 +330,10 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
.map(|_| {
|
||||
let unit_detail = tier.item.get(unit_choice.sample(&mut self.rng)).unwrap();
|
||||
|
||||
ArmorShopItem::Unit(unit_detail.item)
|
||||
ArmorShopItem::Unit(Unit {
|
||||
unit: unit_detail.item,
|
||||
modifier: None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
@ -96,6 +96,18 @@ impl ShopItem for ToolShopItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Tool> for ToolShopItem {
|
||||
fn from(tool: &Tool) -> ToolShopItem {
|
||||
ToolShopItem::Tool(tool.tool)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TechniqueDisk> for ToolShopItem {
|
||||
fn from(techdisk: &TechniqueDisk) -> ToolShopItem {
|
||||
ToolShopItem::Tech(*techdisk)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ToolTable(Vec<ToolType>);
|
||||
|
@ -27,7 +27,7 @@ pub struct WeaponShopItem {
|
||||
weapon: WeaponType,
|
||||
special: Option<WeaponSpecial>,
|
||||
grind: usize,
|
||||
attributes: [Option<WeaponAttribute>; 2],
|
||||
attributes: [Option<WeaponAttribute>; 3],
|
||||
}
|
||||
|
||||
impl PartialEq for WeaponShopItem {
|
||||
@ -53,6 +53,17 @@ impl PartialOrd for WeaponShopItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Weapon> for WeaponShopItem {
|
||||
fn from(weapon: &Weapon) -> WeaponShopItem {
|
||||
WeaponShopItem {
|
||||
weapon: weapon.weapon,
|
||||
special: weapon.special,
|
||||
grind: weapon.grind as usize,
|
||||
attributes: weapon.attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn special_stars(special: &WeaponSpecial) -> usize {
|
||||
match special {
|
||||
@ -120,7 +131,6 @@ impl ShopItem for WeaponShopItem {
|
||||
}).unwrap_or(0.0);
|
||||
|
||||
price += special * special * 1000.0;
|
||||
|
||||
price as usize
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
@ -143,6 +153,17 @@ impl ShopItem for WeaponShopItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl WeaponShopItem {
|
||||
pub fn weapon_from_item(w: &Weapon) -> WeaponShopItem {
|
||||
WeaponShopItem {
|
||||
weapon: w.weapon,
|
||||
special: w.special,
|
||||
grind: w.grind as usize,
|
||||
attributes: w.attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct WeaponTableTierEntry {
|
||||
@ -517,7 +538,7 @@ impl<R: Rng + SeedableRng> WeaponShop<R> {
|
||||
weapon,
|
||||
grind,
|
||||
special,
|
||||
attributes: [attr1, attr2],
|
||||
attributes: [attr1, attr2, None],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ impl TradeState {
|
||||
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 c1 = self.trades.get(client).ok_or(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
|
||||
|
@ -3,6 +3,7 @@ use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
|
||||
use elseware::entity::item;
|
||||
use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket};
|
||||
use elseware::ship::room::Difficulty;
|
||||
use elseware::ship::items::manager::ItemManagerError;
|
||||
|
||||
use libpso::packet::ship::*;
|
||||
use libpso::packet::messages::*;
|
||||
@ -208,7 +209,7 @@ async fn test_player_buys_multiple_from_tool_shop() {
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert!(c1_meseta.0 < 999999);
|
||||
assert_eq!(c1_meseta.0, 999749);
|
||||
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| {
|
||||
@ -255,7 +256,47 @@ async fn test_player_buys_from_armor_shop() {
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_to_shop() {
|
||||
async fn test_player_sells_3_attr_weapon_to_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "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::Vulcan,
|
||||
grind: 5,
|
||||
special: Some(item::weapon::WeaponSpecial::Charge),
|
||||
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,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 4406);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@ -574,3 +615,535 @@ async fn test_units_disappear_from_shop_when_bought() {
|
||||
}).unwrap();
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_untekked_weapon() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "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::Vulcan,
|
||||
grind: 5,
|
||||
special: Some(item::weapon::WeaponSpecial::Charge),
|
||||
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: false,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 1);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_rare_item() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "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::DarkFlow,
|
||||
grind: 5,
|
||||
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,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 10);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_partial_photon_drop_stack() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
let mut photon_drops = Vec::new();
|
||||
for _ in 0..7usize {
|
||||
photon_drops.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Tool(
|
||||
item::tool::Tool {
|
||||
tool: item::tool::ToolType::PhotonDrop,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
}
|
||||
|
||||
p1_inv.push(item::InventoryItemEntity::Stacked(photon_drops));
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 3,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 3000);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_basic_frame() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Armor(
|
||||
item::armor::Armor {
|
||||
armor: item::armor::ArmorType::Frame,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
slots: 0,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 24);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_max_frame() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Armor(
|
||||
item::armor::Armor {
|
||||
armor: item::armor::ArmorType::Frame,
|
||||
dfp: 2,
|
||||
evp: 2,
|
||||
slots: 4,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 74);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_basic_barrier() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Shield(
|
||||
item::shield::Shield {
|
||||
shield: item::shield::ShieldType::Barrier,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 69);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_max_barrier() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Shield(
|
||||
item::shield::Shield {
|
||||
shield: item::shield::ShieldType::Barrier,
|
||||
dfp: 5,
|
||||
evp: 5,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 122);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_1_star_minusminus_unit() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::PriestMind,
|
||||
modifier: Some(item::unit::UnitModifier::MinusMinus),
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 125);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_5_star_plusplus_unit() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::GeneralHp,
|
||||
modifier: Some(item::unit::UnitModifier::PlusPlus),
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 625);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_rare_frame() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Armor(
|
||||
item::armor::Armor {
|
||||
armor: item::armor::ArmorType::StinkFrame,
|
||||
dfp: 10,
|
||||
evp: 20,
|
||||
slots: 3,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 10);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_rare_barrier() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Shield(
|
||||
item::shield::Shield {
|
||||
shield: item::shield::ShieldType::RedRing,
|
||||
dfp: 10,
|
||||
evp: 20,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 10);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_rare_unit() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::V101,
|
||||
modifier: None,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 10);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_cant_sell_if_meseta_would_go_over_max() {
|
||||
let mut entity_gateway = InMemoryGateway::default();
|
||||
|
||||
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999995)).await.unwrap();
|
||||
|
||||
let mut p1_inv = Vec::new();
|
||||
|
||||
p1_inv.push(entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Unit(
|
||||
item::unit::Unit {
|
||||
unit: item::unit::UnitType::V101,
|
||||
modifier: None,
|
||||
}
|
||||
),
|
||||
}).await.unwrap());
|
||||
|
||||
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
|
||||
|
||||
let mut ship = Box::new(ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.build());
|
||||
|
||||
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||
join_lobby(&mut ship, ClientId(1)).await;
|
||||
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||
|
||||
let ack = ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSoldItem(PlayerSoldItem {
|
||||
client: 0,
|
||||
target: 0,
|
||||
item_id: 0x10000,
|
||||
amount: 1,
|
||||
})))).await.err().unwrap();
|
||||
assert!(matches!(ack.downcast::<ItemManagerError>().unwrap(), ItemManagerError::WalletFull));
|
||||
|
||||
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
|
||||
assert_eq!(c1_meseta.0, 999995);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user