diff --git a/src/bin/main.rs b/src/bin/main.rs index a9ed329..ee9aa8c 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -350,6 +350,23 @@ 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, + } + ), + location: ItemLocation::Inventory { + character_id: character.id, + } + }).await.unwrap(); let equipped = item::EquippedEntity { weapon: Some(item2_w.id), @@ -360,7 +377,7 @@ fn main() { }; entity_gateway.set_character_equips(&character.id, &equipped).await.unwrap(); - let inventory = item::InventoryEntity::new(vec![item0, item1, item2_w, item3, item4, item5_m, item6, item7_a, item8_s, item9_u0, item10_u1, item11_u2, item12_u3, item13]); + let inventory = item::InventoryEntity::new(vec![item0, item1, item2_w, item3, item4, item5_m, item6, item7_a, item8_s, item9_u0, item10_u1, item11_u2, item12_u3, item13, item14]); 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(); } diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index 02bd750..5f90986 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -597,6 +597,7 @@ pub enum PgItemLocationDetail { mag: u32, }, Shop, + SoldToShop, } impl From for PgItemLocationDetail { @@ -609,6 +610,7 @@ impl From for PgItemLocationDetail { ItemLocation::Consumed => PgItemLocationDetail::Consumed, ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0}, ItemLocation::Shop => PgItemLocationDetail::Shop, + ItemLocation::SoldToShop => PgItemLocationDetail::SoldToShop, } } } @@ -623,6 +625,7 @@ impl From for ItemLocation { PgItemLocationDetail::Consumed => ItemLocation::Consumed, PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)}, PgItemLocationDetail::Shop => ItemLocation::Shop, + PgItemLocationDetail::SoldToShop => ItemLocation::SoldToShop, } } } diff --git a/src/entity/item/armor.rs b/src/entity/item/armor.rs index 6d2489d..4d7291e 100644 --- a/src/entity/item/armor.rs +++ b/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 { + match self.armor { + ArmorType::HunterField => true, + ArmorType::RangerField => true, + ArmorType::ForceField => true, + ArmorType::RevivalGarment => true, + ArmorType::SpiritGarment => true, + ArmorType::StinkFrame => true, + ArmorType::DPartsVer101 => true, + ArmorType::DPartsVer210 => true, + ArmorType::ParasiteWearDeRol => true, + ArmorType::ParasiteWearNelgal => true, + ArmorType::ParasiteWearVajulla => true, + ArmorType::SensePlate => true, + ArmorType::GravitonPlate => true, + ArmorType::AttributePlate => true, + ArmorType::FlowensFrame => true, + ArmorType::CustomFrameVerOo => true, + ArmorType::DbsArmor => true, + ArmorType::GuardWave => true, + ArmorType::DfField => true, + ArmorType::LuminousField => true, + ArmorType::ChuChuFever => true, + ArmorType::LoveHeart => true, + ArmorType::FlameGarment => true, + ArmorType::VirusArmorLafuteria => true, + ArmorType::BrightnessCircle => true, + ArmorType::AuraField => true, + ArmorType::ElectroFrame => true, + ArmorType::SacredCloth => true, + ArmorType::SmokingPlate => true, + ArmorType::StarCuirass => true, + ArmorType::BlackHoundCuirass => true, + ArmorType::MorningPrayer => true, + ArmorType::BlackOdoshiDomaru => true, + ArmorType::RedOdoshiDomaru => true, + ArmorType::BlackOdoshiRedNimaidou => true, + ArmorType::BlueOdoshiVioletNimaidou => true, + ArmorType::DirtyLifejacket => true, + ArmorType::KroesSweater => true, + ArmorType::WeddingDress => true, + ArmorType::SonicteamArmor => true, + ArmorType::RedCoat => true, + ArmorType::Thirteen => true, + ArmorType::MotherGarb => true, + ArmorType::MotherGarbPlus => true, + ArmorType::DressPlate => true, + ArmorType::Sweetheart => true, + ArmorType::IgnitionCloak => true, + ArmorType::CongealCloak => true, + ArmorType::TempestCloak => true, + ArmorType::CursedCloak => true, + ArmorType::SelectCloak => true, + ArmorType::SpiritCuirass => true, + ArmorType::RevivalCuriass => true, + ArmorType::AllianceUniform => true, + ArmorType::OfficerUniform => true, + ArmorType::CommanderUniform => true, + ArmorType::CrimsonCoat => true, + ArmorType::InfantryGear => true, + ArmorType::LieutenantGear => true, + ArmorType::InfantryMantle => true, + ArmorType::LieutenantMantle => true, + ArmorType::UnionField => true, + ArmorType::SamuraiArmor => true, + ArmorType::StealthSuit => true, + _ => false, + } + } } diff --git a/src/entity/item/esweapon.rs b/src/entity/item/esweapon.rs index 908b026..3a88ccd 100644 --- a/src/entity/item/esweapon.rs +++ b/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)] diff --git a/src/entity/item/mag.rs b/src/entity/item/mag.rs index 4f36983..b5aebd6 100644 --- a/src/entity/item/mag.rs +++ b/src/entity/item/mag.rs @@ -1098,6 +1098,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 { + match self.mag { + MagType::Pitri => true, + MagType::Soniti => true, + MagType::Preta => true, + MagType::Churel => true, + MagType::Robochao => true, + MagType::OpaOpa => true, + MagType::Pian => true, + MagType::Chao => true, + MagType::ChuChu => true, + MagType::KapuKapu => true, + MagType::AngelsWing => true, + MagType::DevilsWing => true, + MagType::Elenor => true, + MagType::MarkIII => true, + MagType::MasterSystem => true, + MagType::Genesis => true, + MagType::SegaSaturn => true, + MagType::Dreamcast => true, + MagType::Hamburger => true, + MagType::PanzersTail => true, + MagType::DevilsTail => true, + MagType::Deva => true, + MagType::Rati => true, + MagType::Savitri => true, + MagType::Rukmin => true, + MagType::Pushan => true, + MagType::Diwari => true, + MagType::Sato => true, + MagType::Bhima => true, + MagType::Nidra => true, + MagType::GeungSi => true, + MagType::Tellusis => true, + MagType::StrikerUnit => true, + MagType::Pioneer => true, + MagType::Puyo => true, + MagType::Moro => true, + MagType::Rappy => true, + MagType::Yahoo => true, + MagType::GaelGiel => true, + MagType::Agastya => true, + _ => false, + } + } } diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 9959f54..9a0a0c8 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -12,6 +12,7 @@ use serde::{Serialize, Deserialize}; use crate::entity::character::CharacterEntityId; use crate::ship::map::MapArea; use crate::ship::drops::ItemDropType; +use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; #[derive(PartialEq, Copy, Clone, Debug, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct ItemEntityId(pub u32); @@ -47,6 +48,7 @@ pub enum ItemLocation { mag: ItemEntityId, }, Shop, + SoldToShop, /*Destroyed { // marks an item that has been consumed in some way }, diff --git a/src/entity/item/shield.rs b/src/entity/item/shield.rs index 5784fd4..2f494cd 100644 --- a/src/entity/item/shield.rs +++ b/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 { + match self.shield { + ShieldType::InvisibleGuard => true, + ShieldType::SacredGuard => true, + ShieldType::SPartsVer116 => true, + ShieldType::SPartsVer201 => true, + ShieldType::LightRelief => true, + ShieldType::ShieldOfDelsaber => true, + ShieldType::ForceWall => true, + ShieldType::RangerWall => true, + ShieldType::HunterWall => true, + ShieldType::AttributeWall => true, + ShieldType::SecretGear => true, + ShieldType::CombatGear => true, + ShieldType::ProtoRegeneGear => true, + ShieldType::RegenerateGear => true, + ShieldType::RegeneGearAdv => true, + ShieldType::FlowensShield => true, + ShieldType::CustomBarrierVerOo => true, + ShieldType::DbsShield => true, + ShieldType::RedRing => true, + ShieldType::TripolicShield => true, + ShieldType::StandstillShield => true, + ShieldType::SafetyHeart => true, + ShieldType::KasamiBracer => true, + ShieldType::GodsShieldSuzaku => true, + ShieldType::GodsShieldGenbu => true, + ShieldType::GodsShieldByakko => true, + ShieldType::GodsShieldSeiryu => true, + ShieldType::HuntersShell => true, + ShieldType::RicosGlasses => true, + ShieldType::RicosEarring => true, + ShieldType::BlueRing => true, + ShieldType::Barrier2 => true, + ShieldType::SecureFeet => true, + ShieldType::Barrier3 => true, + ShieldType::Barrier4 => true, + ShieldType::Barrier5 => true, + ShieldType::Barrier6 => true, + ShieldType::RestaMerge => true, + ShieldType::AntiMerge => true, + ShieldType::ShiftaMerge => true, + ShieldType::DebandMerge => true, + ShieldType::FoieMerge => true, + ShieldType::GifoieMerge => true, + ShieldType::RafoieMerge => true, + ShieldType::RedMerge => true, + ShieldType::BartaMerge => true, + ShieldType::GibartaMerge => true, + ShieldType::RabartaMerge => true, + ShieldType::BlueMerge => true, + ShieldType::ZondeMerge => true, + ShieldType::GizondeMerge => true, + ShieldType::RazondeMerge => true, + ShieldType::YellowMerge => true, + ShieldType::RecoveryBarrier => true, + ShieldType::AssistBarrier => true, + ShieldType::RedBarrier => true, + ShieldType::BlueBarrier => true, + ShieldType::YellowBarrier => true, + ShieldType::WeaponsGoldShield => true, + ShieldType::BlackGear => true, + ShieldType::WorksGuard => true, + ShieldType::RagolRing => true, + ShieldType::BlueRing2 => true, + ShieldType::BlueRing3 => true, + ShieldType::BlueRing4 => true, + ShieldType::BlueRing5 => true, + ShieldType::BlueRing6 => true, + ShieldType::BlueRing7 => true, + ShieldType::BlueRing8 => true, + ShieldType::BlueRing9 => true, + ShieldType::GreenRing => true, + ShieldType::GreenRing2 => true, + ShieldType::GreenRing3 => true, + ShieldType::GreenRing4 => true, + ShieldType::GreenRing5 => true, + ShieldType::GreenRing6 => true, + ShieldType::GreenRing7 => true, + ShieldType::GreenRing8 => true, + ShieldType::YellowRing => true, + ShieldType::YellowRing2 => true, + ShieldType::YellowRing3 => true, + ShieldType::YellowRing4 => true, + ShieldType::YellowRing5 => true, + ShieldType::YellowRing6 => true, + ShieldType::YellowRing7 => true, + ShieldType::YellowRing8 => true, + ShieldType::PurpleRing => true, + ShieldType::PurpleRing2 => true, + ShieldType::PurpleRing3 => true, + ShieldType::PurpleRing4 => true, + ShieldType::PurpleRing5 => true, + ShieldType::PurpleRing6 => true, + ShieldType::PurpleRing7 => true, + ShieldType::PurpleRing8 => true, + ShieldType::WhiteRing => true, + ShieldType::WhiteRing2 => true, + ShieldType::WhiteRing3 => true, + ShieldType::WhiteRing4 => true, + ShieldType::WhiteRing5 => true, + ShieldType::WhiteRing6 => true, + ShieldType::WhiteRing7 => true, + ShieldType::WhiteRing8 => true, + ShieldType::BlackRing => true, + ShieldType::BlackRing2 => true, + ShieldType::BlackRing3 => true, + ShieldType::BlackRing4 => true, + ShieldType::BlackRing5 => true, + ShieldType::BlackRing6 => true, + ShieldType::BlackRing7 => true, + ShieldType::BlackRing8 => true, + ShieldType::WeaponsSilverShield => true, + ShieldType::WeaponsCopperShield => true, + ShieldType::Gratia => true, + ShieldType::TripolicReflector => true, + ShieldType::StrikerPlus => true, + ShieldType::RegenerateGearBP => true, + ShieldType::Rupika => true, + ShieldType::YataMirror => true, + ShieldType::BunnyEars => true, + ShieldType::CatEars => true, + ShieldType::ThreeSeals => true, + ShieldType::GodsShieldKouryu => true, + ShieldType::DfShield => true, + ShieldType::FromTheDepths => true, + ShieldType::DeRolLeShield => true, + ShieldType::HoneycombReflector => true, + ShieldType::Epsiguard => true, + ShieldType::AngelRing => true, + ShieldType::UnionGuard => true, + ShieldType::UnionGuard2 => true, + ShieldType::UnionGuard3 => true, + ShieldType::UnionGuard4 => true, + ShieldType::StinkShield => true, + ShieldType::Unknownb => true, + ShieldType::Genpei => true, + ShieldType::Genpei2 => true, + ShieldType::Genpei3 => true, + ShieldType::Genpei4 => true, + ShieldType::Genpei5 => true, + ShieldType::Genpei6 => true, + ShieldType::Genpei7 => true, + ShieldType::Genpei8 => true, + ShieldType::Genpei9 => true, + ShieldType::Genpei10 => true, + _ => false, + } + } } diff --git a/src/entity/item/tool.rs b/src/entity/item/tool.rs index 054a922..b20845f 100644 --- a/src/entity/item/tool.rs +++ b/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 { + match self.tool { + ToolType::CellOfMag502 => true, + ToolType::CellOfMag213 => true, + ToolType::PartsOfRobochao => true, + ToolType::HeartOfOpaOpa => true, + ToolType::HeartOfPian => true, + ToolType::HeartOfChao => true, + ToolType::SorcerersRightArm => true, + ToolType::SBeatsArms => true, + ToolType::PArmsArms => true, + ToolType::DelsabersRightArm => true, + ToolType::BringersRightArm => true, + ToolType::DelsabersLeftArm => true, + ToolType::SRedsArms => true, + ToolType::DragonsClaw => true, + ToolType::HildebearsHead => true, + ToolType::HildebluesHead => true, + ToolType::PartsOfBaranz => true, + ToolType::BelrasRightArm => true, + ToolType::GiGuesBody => true, + ToolType::SinowBerillsArms => true, + ToolType::GrassAssassinsArms => true, + ToolType::BoomasRightArm => true, + ToolType::GoboomasRightArm => true, + ToolType::GigoboomasRightArm => true, + ToolType::GalGryphonsWing => true, + ToolType::RappysWing => true, + ToolType::CladdingOfEpsilon => true, + ToolType::DeRolLeShell => true, + ToolType::BerillPhoton => true, + ToolType::ParasiticGeneFlow => true, + ToolType::MagicStoneIritista => true, + ToolType::BlueBlackStone => true, + ToolType::Syncesta => true, + ToolType::MagicWater => true, + ToolType::ParasiticCellTypeD => true, + ToolType::MagicRockHeartKey => true, + ToolType::MagicRockMoola => true, + ToolType::StarAmplifier => true, + ToolType::BookOfHitogata => true, + ToolType::HeartOfChuChu => true, + ToolType::PartsOfEggBlaster => true, + ToolType::HeartOfAngel => true, + ToolType::HeartOfDevil => true, + ToolType::KitOfHamburger => true, + ToolType::PanthersSpirit => true, + ToolType::KitOfMark3 => true, + ToolType::KitOfMasterSystem => true, + ToolType::KitOfGenesis => true, + ToolType::KitOfSegaSaturn => true, + ToolType::KitOfDreamcast => true, + ToolType::AmplifierOfResta => true, + ToolType::AmplifierOfAnti => true, + ToolType::AmplifierOfShifta => true, + ToolType::AmplifierOfDeband => true, + ToolType::AmplifierOfFoie => true, + ToolType::AmplifierOfGifoie => true, + ToolType::AmplifierOfRafoie => true, + ToolType::AmplifierOfBarta => true, + ToolType::AmplifierOfGibarta => true, + ToolType::AmplifierOfRabarta => true, + ToolType::AmplifierOfZonde => true, + ToolType::AmplifierOfGizonde => true, + ToolType::AmplifierOfRazonde => true, + ToolType::AmplifierOfRed => true, + ToolType::AmplifierOfBlue => true, + ToolType::AmplifierOfYellow => true, + ToolType::HeartOfKapuKapu => true, + ToolType::PhotonBooster => true, + ToolType::Addslot => true, + ToolType::PhotonDrop => true, + ToolType::PhotonSphere => true, + ToolType::PhotonCrystal => true, + ToolType::SecretTicket => true, + ToolType::PhotonTicket => true, + ToolType::BookOfKatana1 => true, + ToolType::BookOfKatana2 => true, + ToolType::BookOfKatana3 => true, + ToolType::WeaponsBronzeBadge => true, + ToolType::WeaponsSilverBadge => true, + ToolType::WeaponsGoldBadge => true, + ToolType::WeaponsCrystalBadge => true, + ToolType::WeaponsSteelBadge => true, + ToolType::WeaponsAluminumBadge => true, + ToolType::WeaponsLeatherBadge => true, + ToolType::WeaponsBoneBadge => true, + ToolType::LetterOfAppreciation => true, + ToolType::ItemTicket => true, + ToolType::ValentinesChocolate => true, + ToolType::NewYearsCard => true, + ToolType::ChristmasCard => true, + ToolType::BirthdayCard => true, + ToolType::ProofOfSonicTeam => true, + ToolType::SpecialEventTicket => true, + ToolType::FlowerBouquet => true, + ToolType::Cake => true, + ToolType::Accessories => true, + ToolType::MrNakasBusinessCard => true, + ToolType::Present => true, + ToolType::Chocolate => true, + ToolType::Candy => true, + ToolType::Cake2 => true, + ToolType::WeaponsSilverBadge2 => true, + ToolType::WeaponsGoldBadge2 => true, + ToolType::WeaponsCrystalBadge2 => true, + ToolType::WeaponsSteelBadge2 => true, + ToolType::WeaponsAluminumBadge2 => true, + ToolType::WeaponsLeatherBadge2 => true, + ToolType::WeaponsBoneBadge2 => true, + ToolType::Bouquet => true, + ToolType::Decoction => true, + ToolType::ChristmasPresent => true, + ToolType::EasterEgg => true, + ToolType::JackOLantern => true, + ToolType::DiskVol1WeddingMarch => true, + ToolType::DiskVol2DayLight => true, + ToolType::DiskVol3BurningRangers => true, + ToolType::DiskVol4OpenYourHeart => true, + ToolType::DiskVol5LiveLearn => true, + ToolType::DiskVol6Nights => true, + ToolType::DiskVol7EndingThemePianoVer => true, + ToolType::DiskVol8HeartToHeart => true, + ToolType::DiskVol9StrangeBlue => true, + ToolType::DiskVol10ReunionSystem => true, + ToolType::DiskVol11Pinnacles => true, + ToolType::DiskVol12FightInsideTheSpaceship => true, + ToolType::HuntersReport => true, + ToolType::HuntersReport2 => true, + ToolType::HuntersReport3 => true, + ToolType::HuntersReport4 => true, + ToolType::HuntersReport5 => true, + ToolType::Tablet => true, + ToolType::Unknown2 => true, + ToolType::DragonScale => true, + ToolType::HeavenStrikerCoat => true, + ToolType::PioneerParts => true, + ToolType::AmitiesMemo => true, + ToolType::HeartOfMorolian => true, + ToolType::RappysBeak => true, + ToolType::YahoosEngine => true, + ToolType::DPhotonCore => true, + ToolType::LibertaKit => true, + ToolType::CellOfMag0503 => true, + ToolType::CellOfMag0504 => true, + ToolType::CellOfMag0505 => true, + ToolType::CellOfMag0506 => true, + ToolType::CellOfMag0507 => true, + ToolType::TeamPoints500 => true, + ToolType::TeamPoints1000 => true, + ToolType::TeamPoints5000 => true, + ToolType::TeamPoints10000 => true, + _ => false, + } + } + + // TODO: do we actually need this function? + pub fn is_material(self) -> bool { + match self.tool { + ToolType::PowerMaterial => true, + ToolType::MindMaterial => true, + ToolType::EvadeMaterial => true, + ToolType::HpMaterial => true, + ToolType::TpMaterial => true, + ToolType::DefMaterial => true, + ToolType::LuckMaterial => true, + _ => false, + } + } } diff --git a/src/entity/item/unit.rs b/src/entity/item/unit.rs index df0b611..6e90bb0 100644 --- a/src/entity/item/unit.rs +++ b/src/entity/item/unit.rs @@ -384,4 +384,66 @@ impl Unit { Err(ItemParseError::InvalidUnitBytes) // TODO: error handling if wrong bytes are given } } + + pub fn is_rare_item(self) -> bool { + match self.unit { + UnitType::GodPower => true, + UnitType::GodMind => true, + UnitType::GodArm => true, + UnitType::GodLegs => true, + UnitType::GodHp => true, + UnitType::GodTp => true, + UnitType::GodBody => true, + UnitType::GodLuck => true, + UnitType::HeroAbility => true, + UnitType::GodAbility => true, + UnitType::AllResist => true, + UnitType::SuperResist => true, + UnitType::PerfectResist => true, + UnitType::HpRevival => true, + UnitType::TpRevival => true, + UnitType::PbAmplifier => true, + UnitType::PbGenerate => true, + UnitType::PbCreate => true, + UnitType::DevilTechnique => true, + UnitType::GodTechnique => true, + UnitType::DevilBattle => true, + UnitType::GodBattle => true, + UnitType::CurePoison => true, + UnitType::CureParalysis => true, + UnitType::CureSlow => true, + UnitType::CureConfuse => true, + UnitType::CureFreeze => true, + UnitType::CureShock => true, + UnitType::YasakaniMagatama => true, + UnitType::V101 => true, + UnitType::V501 => true, + UnitType::V502 => true, + UnitType::V801 => true, + UnitType::Limiter => true, + UnitType::Adept => true, + UnitType::SwordsmanLore => true, + UnitType::ProofOfSwordSaint => true, + UnitType::Smartlink => true, + UnitType::DivineProtection => true, + UnitType::HeavenlyBattle => true, + UnitType::HeavenlyPower => true, + UnitType::HeavenlyMind => true, + UnitType::HeavenlyArms => true, + UnitType::HeavenlyLegs => true, + UnitType::HeavenlyBody => true, + UnitType::HeavenlyLuck => true, + UnitType::HeavenlyAbility => true, + UnitType::CenturionAbility => true, + UnitType::FriendRing => true, + UnitType::HeavenlyHp => true, + UnitType::HeavenlyTp => true, + UnitType::HeavenlyResist => true, + UnitType::HeavenlyTechnique => true, + UnitType::HpRessurection => true, + UnitType::TpRessurection => true, + UnitType::PbIncrease => true, + _ => false, + } + } } diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index 6473dd8..5d20e71 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -1454,7 +1454,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, @@ -1584,6 +1584,70 @@ 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 { + match self.weapon { + WeaponType::Saber => false, + WeaponType::Brand => false, + WeaponType::Buster => false, + WeaponType::Pallasch => false, + WeaponType::Gladius => false, + WeaponType::Sword => false, + WeaponType::Gigush => false, + WeaponType::Breaker => false, + WeaponType::Claymore => false, + WeaponType::Calibur => false, + WeaponType::Dagger => false, + WeaponType::Knife => false, + WeaponType::Blade => false, + WeaponType::Edge => false, + WeaponType::Ripper => false, + WeaponType::Partisan => false, + WeaponType::Halbert => false, + WeaponType::Glaive => false, + WeaponType::Berdys => false, + WeaponType::Gungnir => false, + WeaponType::Slicer => false, + WeaponType::Spinner => false, + WeaponType::Cutter => false, + WeaponType::Sawcer => false, + WeaponType::Diska => false, + WeaponType::Handgun => false, + WeaponType::Autogun => false, + WeaponType::Lockgun => false, + WeaponType::Railgun => false, + WeaponType::Raygun => false, + WeaponType::Rifle => false, + WeaponType::Sniper => false, + WeaponType::Blaster => false, + WeaponType::Beam => false, + WeaponType::Laser => false, + WeaponType::Mechgun => false, + WeaponType::Assault => false, + WeaponType::Repeater => false, + WeaponType::Gatling => false, + WeaponType::Vulcan => false, + WeaponType::Shot => false, + WeaponType::Spread => false, + WeaponType::Cannon => false, + WeaponType::Launcher => false, + WeaponType::Arms => false, + WeaponType::Cane => false, + WeaponType::Stick => false, + WeaponType::Mace => false, + WeaponType::Club => false, + WeaponType::Rod => false, + WeaponType::Pole => false, + WeaponType::Pillar => false, + WeaponType::Striker => false, + WeaponType::Wand => false, + WeaponType::Staff => false, + WeaponType::Baton => false, + WeaponType::Scepter => false, + _ => true, + } + } } diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index 9589849..81bd733 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -3,11 +3,12 @@ use thiserror::Error; use libpso::character::character;//::InventoryItem; use crate::entity::character::CharacterEntityId; use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, ItemLocation, InventoryEntity, InventoryItemEntity, EquippedEntity}; -use crate::entity::item::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::floor::{IndividualFloorItem, StackedFloorItem}; +use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; const INVENTORY_CAPACITY: usize = 30; @@ -220,6 +221,63 @@ impl InventoryItem { _ => None } } + + pub fn get_sell_price(&self) -> Option { + match self { + InventoryItem::Individual(individual_item) => { + match &individual_item.item { + // TODO: can wrapped items be sold? + ItemDetail::Weapon(w) => { + if !w.tekked { + return Some(1u32) + } + if w.is_rare_item() { + return Some(10u32) + } + // other item factors? + return Some((WeaponShopItem::weapon_from_item(w).price() / 8) as u32) + }, + ItemDetail::Armor(a) => { + if a.is_rare_item() { + return Some(10u32) + } + return Some((ArmorShopItem::armor_from_item(a).price() / 8) as u32) + }, + ItemDetail::Shield(s) => { + if s.is_rare_item() { + return Some(10u32) + } + return Some((ArmorShopItem::shield_from_item(s).price() / 8) as u32) + }, + ItemDetail::Unit(u) => { + if u.is_rare_item() { + return Some(10u32) + } + return Some((ArmorShopItem::unit_from_item(u).price() / 8) as u32) + }, + ItemDetail::Tool(t) => { + if t.is_rare_item() { // TODO: photon drop/sphere etc + return Some(10u32) + } + return Some((ToolShopItem::tool_from_item(t).price() / 8) as u32) + }, + ItemDetail::TechniqueDisk(d) => { // TODO: are all techs the same? + return Some((ToolShopItem::tech_from_item(d).price() / 8) as u32) + }, + ItemDetail::Mag(_m) => { //TODO: error. mags are not sellable + return None + }, + ItemDetail::ESWeapon(_e) => { + return Some(10u32) // TODO: check price + }, + } + }, + // the number of stacked items sold is handled by the caller. this is just the price of 1 + InventoryItem::Stacked(stacked_item) => { + return Some((ToolShopItem::tool_from_item(&stacked_item.tool).price() / 8) as u32) + }, + } + } } diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 441c85b..65cede0 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -1,5 +1,6 @@ use crate::ship::items::ClientItemId; -use std::collections::HashMap; +use std::collections::{HashMap, BTreeMap}; +use std::cmp::Ordering; use thiserror::Error; use crate::entity::gateway::EntityGateway; use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; @@ -55,6 +56,8 @@ pub enum ItemManagerError { NoArmorEquipped, GatewayError(#[from] crate::entity::gateway::GatewayError), StackedItemError(Vec), + ItemNotSellable, // TODO: capture what item was attempted to be sold + WalletFull, } pub struct ItemManager { @@ -844,6 +847,49 @@ impl ItemManager { Ok(inventory_item) } + pub async fn player_sells_item(&mut self, + entity_gateway: &mut EG, + character: &mut CharacterEntity, + item_id: ClientItemId, + amount: usize) + -> Result<(), anyhow::Error> { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let 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() { + if let Some(unit_price) = item_sold.get_sell_price() { // -> Option u32 = meseta, or None if error + let total_sale = unit_price * amount as u32; + if character.meseta + total_sale <= 999999 { + character.meseta += total_sale; + match item_sold { + InventoryItem::Individual(i) => { + entity_gateway.change_item_location(&i.entity_id, ItemLocation::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)?; + }, + // TODO: put a real error here + Ordering::Greater => {println!("i can't believe you've done this.");}, + }; + }, + } + entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + entity_gateway.save_character(&character).await?; + } else { + return Err(ItemManagerError::WalletFull.into()); + } + } else { + return Err(ItemManagerError::ItemNotSellable.into()); + } + } else { + return Err(ItemManagerError::ItemIdNotInInventory(item_id).into()) + } + // TODO: why did i put this + Ok(()) + } + // TODO: check if slot exists before putting units into it pub async fn player_equips_item(&mut self, entity_gateway: &mut EG, diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index b2cb864..7a650a7 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -370,11 +370,12 @@ where } }; - if client.character.meseta < item.price() as u32 { + // TODO: stop giving away wares for free + if client.character.meseta < (item.price() * buy_item.amount as usize) as u32 { return Err(ShipError::ShopError.into()) } - client.character.meseta -= item.price() as u32; + client.character.meseta -= (item.price() * buy_item.amount as usize) as u32; entity_gateway.save_character(&client.character).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?; diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 39d3717..1ded48e 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -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(id: ClientId, @@ -254,7 +254,7 @@ where if client.character.meseta >= charge.meseta { client.character.meseta -= charge.meseta; entity_gateway.save_character(&client.character).await?; - Ok(Box::new(None.into_iter())) + Ok(Box::new(None.into_iter())) // TODO: should probably tell other players we used charge and lost money? } else { Err(ShipError::NotEnoughMeseta(id, client.character.meseta).into()) } @@ -274,7 +274,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(id: ClientId, @@ -333,7 +333,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(id: ClientId, @@ -347,7 +347,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(id: ClientId, @@ -361,5 +361,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 (id: ClientId, + sold_item: &PlayerSoldItem, + entity_gateway: &mut EG, + // client_location: &ClientLocation, + clients: &mut Clients, + item_manager: &mut ItemManager) + -> Result + 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())) } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 8091647..ae9255d 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -485,6 +485,9 @@ impl ShipServerState { 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)?; diff --git a/src/ship/shops/armor.rs b/src/ship/shops/armor.rs index 789db56..282cf96 100644 --- a/src/ship/shops/armor.rs +++ b/src/ship/shops/armor.rs @@ -90,6 +90,20 @@ impl ShopItem for ArmorShopItem { } } +impl ArmorShopItem { + pub fn armor_from_item(a: &Armor) -> ArmorShopItem { + ArmorShopItem::Frame(a.armor, a.slots as usize) + } + + pub fn shield_from_item(s: &Shield) -> ArmorShopItem { + ArmorShopItem::Barrier(s.shield) + } + + pub fn unit_from_item(u: &Unit) -> ArmorShopItem { + ArmorShopItem::Unit(u.unit) + } +} + #[derive(Debug, Deserialize, Clone)] struct FrameTierItem { diff --git a/src/ship/shops/tool.rs b/src/ship/shops/tool.rs index d3a8e4b..e68f38d 100644 --- a/src/ship/shops/tool.rs +++ b/src/ship/shops/tool.rs @@ -96,6 +96,16 @@ impl ShopItem for ToolShopItem { } } +impl ToolShopItem { + pub fn tool_from_item(t: &Tool) -> ToolShopItem { + ToolShopItem::Tool(t.tool) + } + + pub fn tech_from_item(d: &TechniqueDisk) -> ToolShopItem { + ToolShopItem::Tech(*d) + } +} + #[derive(Debug, Deserialize)] struct ToolTable(Vec); diff --git a/src/ship/shops/weapon.rs b/src/ship/shops/weapon.rs index 6818f23..cecec0c 100644 --- a/src/ship/shops/weapon.rs +++ b/src/ship/shops/weapon.rs @@ -27,7 +27,7 @@ pub struct WeaponShopItem { weapon: WeaponType, special: Option, grind: usize, - attributes: [Option; 2], + attributes: [Option; 3], } impl PartialEq for WeaponShopItem { @@ -120,7 +120,6 @@ impl ShopItem for WeaponShopItem { }).unwrap_or(0.0); price += special * special * 1000.0; - price as usize }) .unwrap_or(0xFFFF) @@ -143,6 +142,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 { @@ -512,10 +522,10 @@ impl WeaponShop { }; WeaponShopItem { - weapon, - grind, - special, - attributes: [attr1, attr2], + weapon: weapon, + grind: grind, + special: special, + attributes: [attr1, attr2, None], } }