Browse Source

add kill counter field to weapons and increment it when a client kills a monster

kill_counters
andy 3 years ago
parent
commit
9025458ee8
  1. 29
      src/bin/main.rs
  2. 11
      src/entity/gateway/entitygateway.rs
  3. 15
      src/entity/gateway/inmemory.rs
  4. 3
      src/entity/gateway/postgres/models.rs
  5. 49
      src/entity/item/mod.rs
  6. 31
      src/entity/item/weapon.rs
  7. 1
      src/login/character.rs
  8. 5
      src/ship/drops/generic_weapon.rs
  9. 1
      src/ship/drops/rare_drop_table.rs
  10. 16
      src/ship/items/inventory.rs
  11. 38
      src/ship/items/manager.rs
  12. 16
      src/ship/packet/handler/message.rs
  13. 3
      src/ship/ship.rs
  14. 1
      src/ship/shops/weapon.rs
  15. 15
      tests/test_bank.rs
  16. 4
      tests/test_item_pickup.rs
  17. 2
      tests/test_rooms.rs

29
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,
}
),
}

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

15
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!");
}
}

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

@ -291,6 +291,7 @@ pub struct PgWeapon {
grind: u8,
attrs: HashMap<weapon::Attribute, i8>,
tekked: bool,
kills: Option<u16>,
}
impl From<weapon::Weapon> for PgWeapon {
@ -301,6 +302,7 @@ impl From<weapon::Weapon> 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<PgWeapon> for weapon::Weapon {
grind: other.grind,
attrs,
tekked: other.tekked,
kills: other.kills,
}
}
}

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

31
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<WeaponAttribute>; 3],
pub tekked: bool,
pub kills: Option<u16>,
}
@ -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);
}
}
}

1
src/login/character.rs

@ -220,6 +220,7 @@ async fn new_character<EG: EntityGateway>(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 {

5
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,
})));
}
}

1
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,
})
},

16
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
}
}

38
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<dyn std::error::Error + Send + Sync>),
#[error("invalid trade")]
InvalidTrade,
EntityIdNotInInventory(ItemEntityId),
}
impl<E> std::convert::From<TransactionError<E>> for ItemManagerError
@ -1373,6 +1374,41 @@ impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway>( &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(())
}
}

16
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<EG>( id: ClientId,
pkt: &KillMonster,
entity_gateway: &mut EG,
clients: &Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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()))
}

3
src/ship/ship.rs

@ -520,6 +520,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
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)?;

1
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,
}
)
}

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

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

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

Loading…
Cancel
Save