Browse Source

Merge pull request 'shop_sell' (#65) from shop_sell into master

Reviewed-on: #65
pull/84/head
jake 2 years ago
parent
commit
6c1ae1a41e
  1. 44
      src/bin/main.rs
  2. 8
      src/entity/gateway/postgres/postgres.rs
  3. 72
      src/entity/item/armor.rs
  4. 5
      src/entity/item/esweapon.rs
  5. 47
      src/entity/item/mag.rs
  6. 1
      src/entity/item/mod.rs
  7. 150
      src/entity/item/shield.rs
  8. 170
      src/entity/item/tool.rs
  9. 72
      src/entity/item/unit.rs
  10. 71
      src/entity/item/weapon.rs
  11. 2
      src/login/login.rs
  12. 15
      src/ship/character.rs
  13. 63
      src/ship/items/inventory.rs
  14. 60
      src/ship/items/manager.rs
  15. 2
      src/ship/items/mod.rs
  16. 1
      src/ship/map/area.rs
  17. 3
      src/ship/map/enemy.rs
  18. 4
      src/ship/map/maps.rs
  19. 24
      src/ship/packet/handler/direct_message.rs
  20. 45
      src/ship/packet/handler/message.rs
  21. 8
      src/ship/packet/handler/quest.rs
  22. 4
      src/ship/packet/handler/room.rs
  23. 8
      src/ship/packet/handler/trade.rs
  24. 9
      src/ship/ship.rs
  25. 80
      src/ship/shops/armor.rs
  26. 12
      src/ship/shops/tool.rs
  27. 27
      src/ship/shops/weapon.rs
  28. 2
      src/ship/trade.rs
  29. 577
      tests/test_shops.rs

44
src/bin/main.rs

@ -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();
}

8
src/entity/gateway/postgres/postgres.rs

@ -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(())

72
src/entity/item/armor.rs

@ -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
)
}
}

5
src/entity/item/esweapon.rs

@ -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)]

47
src/entity/item/mag.rs

@ -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
)
}
}

1
src/entity/item/mod.rs

@ -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)),

150
src/entity/item/shield.rs

@ -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
)
}
}

170
src/entity/item/tool.rs

@ -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
)
}
}

72
src/entity/item/unit.rs

@ -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,
}
}
}

71
src/entity/item/weapon.rs

@ -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
)
}
}

2
src/login/login.rs

@ -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());

15
src/ship/character.rs

@ -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),

63
src/ship/items/inventory.rs

@ -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),

60
src/ship/items/manager.rs

@ -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)

2
src/ship/items/mod.rs

@ -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};

1
src/ship/map/area.rs

@ -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

3
src/ship/map/enemy.rs

@ -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()

4
src/ship/map/maps.rs

@ -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);

24
src/ship/packet/handler/direct_message.rs

@ -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,

45
src/ship/packet/handler/message.rs

@ -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()))
}

8
src/ship/packet/handler/quest.rs

@ -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> {

4
src/ship/packet/handler/room.rs

@ -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

8
src/ship/packet/handler/trade.rs

@ -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()))

9
src/ship/ship.rs

@ -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)?;

80
src/ship/shops/armor.rs

@ -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()
})

12
src/ship/shops/tool.rs

@ -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
src/ship/shops/weapon.rs

@ -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],
}
}

2
src/ship/trade.rs

@ -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

577
tests/test_shops.rs

@ -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…
Cancel
Save