Browse Source

wow i should have pushed much smaller chunks more often oops. shop selling added, shop buying quantity bug fixed(?) added lots of todos

pull/47/head
andy 4 years ago
parent
commit
10aca8c7ec
  1. 19
      src/bin/main.rs
  2. 3
      src/entity/gateway/postgres/models.rs
  3. 72
      src/entity/item/armor.rs
  4. 5
      src/entity/item/esweapon.rs
  5. 47
      src/entity/item/mag.rs
  6. 2
      src/entity/item/mod.rs
  7. 150
      src/entity/item/shield.rs
  8. 170
      src/entity/item/tool.rs
  9. 62
      src/entity/item/unit.rs
  10. 66
      src/entity/item/weapon.rs
  11. 60
      src/ship/items/inventory.rs
  12. 48
      src/ship/items/manager.rs
  13. 5
      src/ship/packet/handler/direct_message.rs
  14. 28
      src/ship/packet/handler/message.rs
  15. 3
      src/ship/ship.rs
  16. 14
      src/ship/shops/armor.rs
  17. 10
      src/ship/shops/tool.rs
  18. 22
      src/ship/shops/weapon.rs

19
src/bin/main.rs

@ -350,6 +350,23 @@ fn main() {
} }
} }
).await.unwrap(); ).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 { let equipped = item::EquippedEntity {
weapon: Some(item2_w.id), weapon: Some(item2_w.id),
@ -360,7 +377,7 @@ fn main() {
}; };
entity_gateway.set_character_equips(&character.id, &equipped).await.unwrap(); 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_inventory(&character.id, &inventory).await.unwrap();
entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), item::BankName("".into())).await.unwrap(); entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), item::BankName("".into())).await.unwrap();
} }

3
src/entity/gateway/postgres/models.rs

@ -597,6 +597,7 @@ pub enum PgItemLocationDetail {
mag: u32, mag: u32,
}, },
Shop, Shop,
SoldToShop,
} }
impl From<ItemLocation> for PgItemLocationDetail { impl From<ItemLocation> for PgItemLocationDetail {
@ -609,6 +610,7 @@ impl From<ItemLocation> for PgItemLocationDetail {
ItemLocation::Consumed => PgItemLocationDetail::Consumed, ItemLocation::Consumed => PgItemLocationDetail::Consumed,
ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0}, ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0},
ItemLocation::Shop => PgItemLocationDetail::Shop, ItemLocation::Shop => PgItemLocationDetail::Shop,
ItemLocation::SoldToShop => PgItemLocationDetail::SoldToShop,
} }
} }
} }
@ -623,6 +625,7 @@ impl From<PgItemLocationDetail> for ItemLocation {
PgItemLocationDetail::Consumed => ItemLocation::Consumed, PgItemLocationDetail::Consumed => ItemLocation::Consumed,
PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)}, PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)},
PgItemLocationDetail::Shop => ItemLocation::Shop, PgItemLocationDetail::Shop => ItemLocation::Shop,
PgItemLocationDetail::SoldToShop => ItemLocation::SoldToShop,
} }
} }
} }

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 struct Armor {
pub armor: ArmorType, pub armor: ArmorType,
pub dfp: u8, pub dfp: u8,
@ -329,4 +329,74 @@ impl Armor {
Err(ItemParseError::InvalidArmorBytes) // TODO: error handling if wrong bytes are given 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,
}
}
} }

5
src/entity/item/esweapon.rs

@ -253,6 +253,11 @@ impl ESWeapon {
name, 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)] #[cfg(test)]

47
src/entity/item/mag.rs

@ -1098,6 +1098,53 @@ impl Mag {
MagCell::LibertaKit => MagType::Agastya, 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,
}
}
} }

2
src/entity/item/mod.rs

@ -12,6 +12,7 @@ use serde::{Serialize, Deserialize};
use crate::entity::character::CharacterEntityId; use crate::entity::character::CharacterEntityId;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::ship::drops::ItemDropType; use crate::ship::drops::ItemDropType;
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
#[derive(PartialEq, Copy, Clone, Debug, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(PartialEq, Copy, Clone, Debug, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ItemEntityId(pub u32); pub struct ItemEntityId(pub u32);
@ -47,6 +48,7 @@ pub enum ItemLocation {
mag: ItemEntityId, mag: ItemEntityId,
}, },
Shop, Shop,
SoldToShop,
/*Destroyed { /*Destroyed {
// marks an item that has been consumed in some way // marks an item that has been consumed in some way
}, },

150
src/entity/item/shield.rs

@ -548,4 +548,154 @@ impl Shield {
Err(ItemParseError::InvalidShieldBytes) // TODO: error handling if wrong bytes are given 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,
}
}
} }

170
src/entity/item/tool.rs

@ -680,4 +680,174 @@ impl Tool {
pub fn max_stack(&self) -> usize { pub fn max_stack(&self) -> usize {
self.tool.max_stack() 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,
}
}
} }

62
src/entity/item/unit.rs

@ -384,4 +384,66 @@ impl Unit {
Err(ItemParseError::InvalidUnitBytes) // TODO: error handling if wrong bytes are given 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,
}
}
} }

66
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 struct Weapon {
pub weapon: WeaponType, pub weapon: WeaponType,
pub special: Option<WeaponSpecial>, pub special: Option<WeaponSpecial>,
@ -1584,6 +1584,70 @@ impl Weapon {
Err(ItemParseError::InvalidWeaponBytes) // TODO: error handling if wrong bytes are given 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,
}
}
} }

60
src/ship/items/inventory.rs

@ -3,11 +3,12 @@ use thiserror::Error;
use libpso::character::character;//::InventoryItem; use libpso::character::character;//::InventoryItem;
use crate::entity::character::CharacterEntityId; use crate::entity::character::CharacterEntityId;
use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, ItemLocation, InventoryEntity, InventoryItemEntity, EquippedEntity}; use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, 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::mag::Mag;
use crate::entity::item::weapon::Weapon; use crate::entity::item::weapon::Weapon;
use crate::ship::items::{ClientItemId, BankItem, BankItemHandle}; use crate::ship::items::{ClientItemId, BankItem, BankItemHandle};
use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem}; use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem};
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
const INVENTORY_CAPACITY: usize = 30; const INVENTORY_CAPACITY: usize = 30;
@ -220,6 +221,63 @@ impl InventoryItem {
_ => None _ => None
} }
} }
pub fn get_sell_price(&self) -> Option<u32> {
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)
},
}
}
} }

48
src/ship/items/manager.rs

@ -1,5 +1,6 @@
use crate::ship::items::ClientItemId; use crate::ship::items::ClientItemId;
use std::collections::HashMap;
use std::collections::{HashMap, BTreeMap};
use std::cmp::Ordering;
use thiserror::Error; use thiserror::Error;
use crate::entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
@ -55,6 +56,8 @@ pub enum ItemManagerError {
NoArmorEquipped, NoArmorEquipped,
GatewayError(#[from] crate::entity::gateway::GatewayError), GatewayError(#[from] crate::entity::gateway::GatewayError),
StackedItemError(Vec<ItemEntity>), StackedItemError(Vec<ItemEntity>),
ItemNotSellable, // TODO: capture what item was attempted to be sold
WalletFull,
} }
pub struct ItemManager { pub struct ItemManager {
@ -844,6 +847,49 @@ impl ItemManager {
Ok(inventory_item) 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 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> 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 // TODO: check if slot exists before putting units into it
pub async fn player_equips_item<EG: EntityGateway>(&mut self, pub async fn player_equips_item<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG, entity_gateway: &mut EG,

5
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()) 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?; 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?; let inventory_item = item_manager.player_buys_item(entity_gateway, &client.character, item, ClientItemId(buy_item.item_id), buy_item.amount as usize).await?;

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

@ -110,7 +110,7 @@ pub fn drop_coordinates(id: ClientId,
item_id: ClientItemId(drop_coordinates.item_id), 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, pub async fn no_longer_has_item<EG>(id: ClientId,
@ -254,7 +254,7 @@ where
if client.character.meseta >= charge.meseta { if client.character.meseta >= charge.meseta {
client.character.meseta -= charge.meseta; client.character.meseta -= charge.meseta;
entity_gateway.save_character(&client.character).await?; 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 { } else {
Err(ShipError::NotEnoughMeseta(id, client.character.meseta).into()) 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?; 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?; 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, pub async fn player_used_medical_center<EG>(id: ClientId,
@ -333,7 +333,7 @@ where
0 0
}; };
item_manager.player_equips_item(entity_gateway, &client.character, ClientItemId(pkt.item_id), equip_slot).await?; 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, pub async fn player_unequips_item<EG>(id: ClientId,
@ -347,7 +347,7 @@ where
{ {
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
item_manager.player_unequips_item(entity_gateway, &client.character, ClientItemId(pkt.item_id)).await?; 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, pub async fn player_sorts_items<EG>(id: ClientId,
@ -361,5 +361,21 @@ where
{ {
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
item_manager.player_sorts_items(entity_gateway, &client.character, pkt.item_ids).await?; 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()))
} }

3
src/ship/ship.rs

@ -485,6 +485,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
GameMessage::SortItems(sort_items) => { GameMessage::SortItems(sort_items) => {
handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await? 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 cmsg = msg.clone();
let block = self.blocks.with_client(id, &self.clients)?; let block = self.blocks.with_client(id, &self.clients)?;

14
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)] #[derive(Debug, Deserialize, Clone)]
struct FrameTierItem { struct FrameTierItem {

10
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)] #[derive(Debug, Deserialize)]
struct ToolTable(Vec<ToolType>); struct ToolTable(Vec<ToolType>);

22
src/ship/shops/weapon.rs

@ -27,7 +27,7 @@ pub struct WeaponShopItem {
weapon: WeaponType, weapon: WeaponType,
special: Option<WeaponSpecial>, special: Option<WeaponSpecial>,
grind: usize, grind: usize,
attributes: [Option<WeaponAttribute>; 2],
attributes: [Option<WeaponAttribute>; 3],
} }
impl PartialEq for WeaponShopItem { impl PartialEq for WeaponShopItem {
@ -120,7 +120,6 @@ impl ShopItem for WeaponShopItem {
}).unwrap_or(0.0); }).unwrap_or(0.0);
price += special * special * 1000.0; price += special * special * 1000.0;
price as usize price as usize
}) })
.unwrap_or(0xFFFF) .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)] #[derive(Debug, Deserialize)]
struct WeaponTableTierEntry { struct WeaponTableTierEntry {
@ -512,10 +522,10 @@ impl<R: Rng + SeedableRng> WeaponShop<R> {
}; };
WeaponShopItem { WeaponShopItem {
weapon,
grind,
special,
attributes: [attr1, attr2],
weapon: weapon,
grind: grind,
special: special,
attributes: [attr1, attr2, None],
} }
} }

Loading…
Cancel
Save