diff --git a/src/bin/main.rs b/src/bin/main.rs index d493ec2..b96552a 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -88,6 +88,7 @@ fn main() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -108,13 +109,14 @@ fn main() { NewItemEntity { item: ItemDetail::Weapon( item::weapon::Weapon { - weapon: item::weapon::WeaponType::Raygun, + weapon: item::weapon::WeaponType::SealedJSword, grind: 5, - special: Some(item::weapon::WeaponSpecial::Hell), + special: None, attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}), None,], - tekked: false, + tekked: true, + kills: Some(22995), } ), }).await.unwrap(); @@ -129,6 +131,7 @@ fn main() { Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}), None,], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -143,6 +146,7 @@ fn main() { Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), None,], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -157,6 +161,7 @@ fn main() { Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), None,], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -171,6 +176,7 @@ fn main() { Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -213,8 +219,17 @@ fn main() { let item6_1 = entity_gateway.create_item( NewItemEntity { - item: ItemDetail::ESWeapon( - item::esweapon::ESWeapon::new(item::esweapon::ESWeaponType::Saber) + 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, + kills: None, + } ), }).await.unwrap(); let item7_a = entity_gateway.create_item( @@ -244,8 +259,8 @@ fn main() { NewItemEntity { item: ItemDetail::Unit( item::unit::Unit { - unit: item::unit::UnitType::PriestMind, - modifier: Some(item::unit::UnitModifier::PlusPlus), + unit: item::unit::UnitType::Limiter, + modifier: None, } ), } diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index fd8b1aa..c5edff7 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -131,4 +131,15 @@ pub trait EntityGateway: Send + Sync + Clone { async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: BankName, _amount: Meseta) -> Result<(), GatewayError> { unimplemented!(); } + async fn increment_kill_counter(&mut self, _item_entity_id: &ItemEntityId) -> Result<(), GatewayError> { + unimplemented!(); + } + + async fn get_kill_counter() { + unimplemented!(); + } + + async fn set_kill_counter() { + unimplemented!(); + } } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 5c7afb4..885bfcc 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -349,4 +349,19 @@ impl EntityGateway for InMemoryGateway { Err(GatewayError::Error) } } + + async fn increment_kill_counter(&mut self, item_id: &ItemEntityId) -> Result<(), GatewayError> { + if let Some(item_entity) = self.items.lock().unwrap().get_mut(item_id) { + item_entity.increase_kill_counter(); + } + Ok(()) + } + + async fn get_kill_counter() { + println!("src/entity/gateway/inmemory.rs::get_kill_counter() - unimplemented!"); + } + + async fn set_kill_counter() { + println!("src/entity/gateway/inmemory.rs::set_kill_counter() - unimplemented!"); + } } diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index f2b8c94..bbb7e88 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -291,6 +291,7 @@ pub struct PgWeapon { grind: u8, attrs: HashMap, tekked: bool, + kills: Option, } impl From for PgWeapon { @@ -301,6 +302,7 @@ impl From for PgWeapon { grind: other.grind, attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(), tekked: other.tekked, + kills: other.kills, } } } @@ -321,6 +323,7 @@ impl From for weapon::Weapon { grind: other.grind, attrs, tekked: other.tekked, + kills: other.kills, } } } diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index c840034..b472594 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -169,6 +169,33 @@ impl ItemDetail { _ => None, } } + + pub fn has_kill_counter(self) -> bool { + match self { + ItemDetail::Weapon(w) => w.kills.is_some(), + ItemDetail::Armor(a) => false, + ItemDetail::Shield(s) => false, + // ItemDetail::Unit(u) => u.kills.is_some(), + ItemDetail::Unit(u) => false, + ItemDetail::Tool(t) => false, + ItemDetail::TechniqueDisk(d) => false, + ItemDetail::Mag(m) => false, + ItemDetail::ESWeapon(e) => false, + } + } + + pub fn increment_kill_counter(&self) { + match self { + ItemDetail::Weapon(w) => {}, + ItemDetail::Armor(a) => {}, + ItemDetail::Shield(s) => {}, + ItemDetail::Unit(u) => {}, + ItemDetail::Tool(t) => {}, + ItemDetail::TechniqueDisk(d) => {}, + ItemDetail::Mag(m) => {}, + ItemDetail::ESWeapon(e) => {}, + } + } } #[derive(Clone, Debug)] @@ -182,6 +209,28 @@ pub struct ItemEntity { pub item: ItemDetail, } +impl ItemEntity { + pub fn increase_kill_counter(&mut self) { + match &self.item { + ItemDetail::Weapon(w) => { + if let Some(kills) = w.kills { + self.item = ItemDetail::Weapon(weapon::Weapon { + kills: Some(kills + 1), + ..*w + }) + } + }, + // ItemDetail::Unit(u) => { + // if let Some(kills) = u.kills { + // kills += 1; + // u.kills = Some(kills); + // } + // } + _ => {}, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum InventoryItemEntity { diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index 11a8709..db4f8af 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -1421,6 +1421,10 @@ impl WeaponType { _ => Err(ItemParseError::InvalidWeaponType), } } + + pub fn has_counter(&self) -> bool { + matches!(self, WeaponType::SealedJSword | WeaponType::LameDArgent) + } } @@ -1464,6 +1468,7 @@ pub struct Weapon { pub grind: u8, pub attrs: [Option; 3], pub tekked: bool, + pub kills: Option, } @@ -1475,6 +1480,7 @@ impl Weapon { grind: 0, attrs: [None; 3], tekked: true, + kills: None, } } @@ -1530,9 +1536,15 @@ impl Weapon { result[4] += 0x80 }; - result[6..8].copy_from_slice(&self.attrs[0].map(|s| s.value()).unwrap_or([0,0])); - result[8..10].copy_from_slice(&self.attrs[1].map(|s| s.value()).unwrap_or([0,0])); - result[10..12].copy_from_slice(&self.attrs[2].map(|s| s.value()).unwrap_or([0,0])); + result[6..8].copy_from_slice(&self.attrs[0].map(|s| s.value()).unwrap_or([0,0])); + result[8..10].copy_from_slice(&self.attrs[1].map(|s| s.value()).unwrap_or([0,0])); + if self.weapon.has_counter() { + result[10..12].copy_from_slice(&self.kills.unwrap_or(0u16).to_be_bytes()); + result[10] += 0x80; + + } else { + result[10..12].copy_from_slice(&self.attrs[2].map(|s| s.value()).unwrap_or([0,0])); + } result } @@ -1543,6 +1555,7 @@ impl Weapon { if let Ok(weapon) = wep { let mut special = None; let mut tekked = true; + let mut kills = None; let grind = data[3]; if data[4] >= 0x81 && data[4] <= 0xA8 { @@ -1575,12 +1588,18 @@ impl Weapon { } } + if data[10] >= 0x80 { + attrs[2] = None; + kills = Some(u16::from_be_bytes([data[10], data[11]])); + } + Ok(Weapon { weapon, special, grind, attrs, tekked, + kills, }) } else { @@ -1651,4 +1670,10 @@ impl Weapon { | WeaponType::Scepter ) } + + pub fn increment_kill_counter(&mut self) { + if let Some(kills) = self.kills { + self.kills = Some(kills + 1); + } + } } diff --git a/src/login/character.rs b/src/login/character.rs index f1192eb..2a7d75b 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -220,6 +220,7 @@ async fn new_character(entity_gateway: &mut EG, user: &UserAc special: None, attrs: [None; 3], tekked: true, + kills: None, })}).await?; entity_gateway.add_item_note(&weapon.id, ItemNote::CharacterCreation { diff --git a/src/ship/drops/generic_weapon.rs b/src/ship/drops/generic_weapon.rs index d1fb2bb..7f354b5 100644 --- a/src/ship/drops/generic_weapon.rs +++ b/src/ship/drops/generic_weapon.rs @@ -497,6 +497,7 @@ impl GenericWeaponTable { grind: weapon_grind as u8, attrs: weapon_attributes, tekked: weapon_special.is_none(), + kills: None, })) } } @@ -518,6 +519,7 @@ mod test { grind: 0, attrs: [None, None, None], tekked: true, + kills: None, }))); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly); @@ -527,6 +529,7 @@ mod test { grind: 2, attrs: [None, None, None], tekked: true, + kills: None, }))); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly); @@ -536,6 +539,7 @@ mod test { grind: 0, attrs: [None, None, None], tekked: false, + kills: None, }))); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); @@ -545,6 +549,7 @@ mod test { grind: 0, attrs: [Some(WeaponAttribute {attr: Attribute::ABeast, value: 30}), Some(WeaponAttribute {attr: Attribute::Dark, value: 30}), None], tekked: true, + kills: None, }))); } } diff --git a/src/ship/drops/rare_drop_table.rs b/src/ship/drops/rare_drop_table.rs index 6631654..af7b541 100644 --- a/src/ship/drops/rare_drop_table.rs +++ b/src/ship/drops/rare_drop_table.rs @@ -103,6 +103,7 @@ impl RareDropTable { grind: 0, attrs: self.attribute_table.generate_rare_attributes(map_area, rng), tekked: false, + kills: None, }) }, diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index da4c4b0..5e25340 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -45,6 +45,13 @@ impl IndividualInventoryItem { _ => None } } + + pub fn weapon_mut(&mut self) -> Option<&mut Weapon> { + match self.item { + ItemDetail::Weapon(ref mut weapon) => Some(weapon), + _ => None + } + } } #[derive(Debug, Clone)] @@ -984,5 +991,14 @@ impl CharacterInventory { pub fn as_equipped_entity(&self) -> EquippedEntity { self.equipped.clone() } + + pub fn get_item_by_entity_id(&self, item_id: ItemEntityId) -> Option<&InventoryItem> { + for item in &self.items { + if let Some(_) = item.entity_ids().iter().find(|&&item| item == item_id) { + return Some(item) + } + } + None + } } diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 950e088..c852ad1 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -6,7 +6,7 @@ use thiserror::Error; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; use crate::entity::item::{ItemDetail, ItemNote, BankName}; -use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity}; +use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity, EquippedEntity, ItemEntityId}; use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::weapon; use crate::ship::map::MapArea; @@ -82,6 +82,7 @@ pub enum ItemManagerError { ItemTransactionAction(Box), #[error("invalid trade")] InvalidTrade, + EntityIdNotInInventory(ItemEntityId), } impl std::convert::From> for ItemManagerError @@ -1373,6 +1374,41 @@ impl ItemAction for TradeMeseta { dest_meseta.0 += self.amount as u32; entity_gateway.set_character_meseta(&self.dest_character_id, *dest_meseta).await?; } + } + + pub async fn increase_kill_counters( &mut self, + entity_gateway: &mut EG, + character: &CharacterEntity, + equipped_items: &EquippedEntity) + -> Result<(), anyhow::Error> { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + if let Some(weapon_entity) = equipped_items.weapon { + println!("updating weapon kill counter for weapon {:?}", weapon_entity); + // weapon_entity = &InventoryItem + + // let weapon_id = weapon_entity.item_id(); + let weapon_id = inventory.get_item_by_entity_id(weapon_entity).ok_or(ItemManagerError::EntityIdNotInInventory(weapon_entity))?.item_id(); + let mut weapon_handle = inventory.get_item_handle_by_id(weapon_id).ok_or(ItemManagerError::NoSuchItemId(weapon_id))?; + // weapon_handle = InventoryItemHandle + let individual_item = weapon_handle.item_mut() + .ok_or(ItemManagerError::NoSuchItemId(weapon_id))? + .individual_mut() + .ok_or(ItemManagerError::WrongItemType(weapon_id))?; + let weapon = individual_item + .weapon_mut() + .ok_or(ItemManagerError::WrongItemType(weapon_id))?; + + weapon.increment_kill_counter(); + entity_gateway.increment_kill_counter(&weapon_entity).await?; + entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + } + // for units in equipped_items.unit { + // if let Some(unit_id) = units { + // println!("UNIMPLEMENTED - updating unit kill counter for unit {:?}", unit_id); + // // entity_gateway.increase_kill_counter(&unit_id).await?; + // // let unit = inventory.get_item_by_entity_id(&unit_id) + // } + // } Ok(()) } } diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index c39ba73..41fa90b 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -395,6 +395,20 @@ where { 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())) // TODO: Do clients care about the order of other clients items? +} + +pub async fn player_killed_monster( id: ClientId, + pkt: &KillMonster, + entity_gateway: &mut EG, + clients: &Clients, + item_manager: &mut ItemManager) + -> Result + Send>, anyhow::Error> +where + EG: EntityGateway +{ + let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; + let equipped_items = entity_gateway.get_character_equips(&client.character.id).await?; + item_manager.increase_kill_counters(entity_gateway, &client.character, &equipped_items).await?; Ok(Box::new(None.into_iter())) } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 4cf5e2a..db33110 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -520,6 +520,9 @@ impl ShipServerState { 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? }, + GameMessage::KillMonster(kill_monster) => { + handler::message::player_killed_monster(id, kill_monster, &mut self.entity_gateway, &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/weapon.rs b/src/ship/shops/weapon.rs index 14509c8..0401017 100644 --- a/src/ship/shops/weapon.rs +++ b/src/ship/shops/weapon.rs @@ -148,6 +148,7 @@ impl ShopItem for WeaponShopItem { grind: self.grind as u8, attrs: [self.attributes[0], self.attributes[1], None], tekked: true, + kills: None, } ) } diff --git a/tests/test_bank.rs b/tests/test_bank.rs index 75777ab..1f80605 100644 --- a/tests/test_bank.rs +++ b/tests/test_bank.rs @@ -27,6 +27,7 @@ async fn test_bank_items_sent_in_character_login() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -64,6 +65,7 @@ async fn test_request_bank_items() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -153,6 +155,7 @@ async fn test_request_bank_items_sorted() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -173,6 +176,7 @@ async fn test_request_bank_items_sorted() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -219,6 +223,7 @@ async fn test_deposit_individual_item() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -231,6 +236,7 @@ async fn test_deposit_individual_item() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap(); @@ -567,6 +573,7 @@ async fn test_deposit_individual_item_in_full_bank() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -582,6 +589,7 @@ async fn test_deposit_individual_item_in_full_bank() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -654,6 +662,7 @@ async fn test_deposit_stacked_item_in_full_bank() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -739,6 +748,7 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap().into()); @@ -909,6 +919,7 @@ async fn test_withdraw_individual_item() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -1242,6 +1253,7 @@ async fn test_withdraw_individual_item_in_full_inventory() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -1257,6 +1269,7 @@ async fn test_withdraw_individual_item_in_full_inventory() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -1325,6 +1338,7 @@ async fn test_withdraw_stacked_item_in_full_inventory() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -1400,6 +1414,7 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap().into()); diff --git a/tests/test_item_pickup.rs b/tests/test_item_pickup.rs index 9520527..2b18f86 100644 --- a/tests/test_item_pickup.rs +++ b/tests/test_item_pickup.rs @@ -234,6 +234,7 @@ async fn test_pick_up_meseta_when_inventory_full() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -306,6 +307,7 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap().into()); @@ -389,6 +391,7 @@ async fn test_can_not_pick_up_item_when_inventory_full() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -404,6 +407,7 @@ async fn test_can_not_pick_up_item_when_inventory_full() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); diff --git a/tests/test_rooms.rs b/tests/test_rooms.rs index f435c45..1c4b537 100644 --- a/tests/test_rooms.rs +++ b/tests/test_rooms.rs @@ -29,6 +29,7 @@ async fn test_item_ids_reset_when_rejoining_rooms() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap()); @@ -45,6 +46,7 @@ async fn test_item_ids_reset_when_rejoining_rooms() { special: None, attrs: [None, None, None], tekked: true, + kills: None, } ), }).await.unwrap());