From ed550842f6ef9f6c33b1c5ee6d904938708c3519 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 16 May 2022 23:22:34 +0000 Subject: [PATCH] prevent exp steal from bosses and friends --- src/entity/item/weapon.rs | 2 +- src/ship/packet/handler/message.rs | 134 +++++++++++++++-------------- tests/test_exp_gain.rs | 70 ++++++++++++++- 3 files changed, 135 insertions(+), 71 deletions(-) diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index 8ab6c0d..468f5dc 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -1423,7 +1423,7 @@ impl WeaponType { } pub fn special_penalty(&self) -> f32 { - match(self) { + match self { WeaponType::Saber => 0.0, WeaponType::Brand => 0.0, WeaponType::Buster => 0.0, diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 79c8c11..352fc94 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -402,11 +402,9 @@ where Ok(Box::new(None.into_iter())) } -// TODO: multihit weapon penalty? // TODO: restrict stealable exp to 100% // TODO: track stealable exp per client // TODO: convenience function for giving exp and checking levelups (un-duplicate code here and `request_exp`) -// TODO: reject bosses // TODO: use real errors (Idunnoman) // TODO: create InventoryError::CannotGetItemHandle or something pub async fn player_steals_exp (id: ClientId, @@ -430,79 +428,83 @@ where .ok_or(ShipError::InvalidRoom(room_id.0 as u32))?; let monster = room.maps.enemy_by_id(expsteal.enemy_id as usize)?; - let monster_stats = room.monster_stats.get(&monster.monster).ok_or(ShipError::UnknownMonster(monster.monster))?; + if monster.monster.is_boss() { + Ok(Box::new(None.into_iter())) // should this be an error? + } else { + let monster_stats = room.monster_stats.get(&monster.monster).ok_or(ShipError::UnknownMonster(monster.monster))?; - let char_special_modifier: f32 = if client.character.char_class.is_android() { - if room.mode.difficulty() == crate::ship::room::Difficulty::Ultimate { - 0.3 + let char_special_modifier: f32 = if client.character.char_class.is_android() { + if room.mode.difficulty() == crate::ship::room::Difficulty::Ultimate { + 0.3 + } else { + 0.0 + } } else { 0.0 - } - } else { - 0.0 - }; - - let equipped_weapon_handle = item_manager - .get_character_inventory_mut(&client.character)? - .get_equipped_weapon_handle() - .ok_or(ItemManagerError::CannotGetIndividualItem)?; - - let equipped_weapon = &equipped_weapon_handle - .item() - .ok_or(ItemManagerError::Idunnoman)? - .individual() - .ok_or(ItemManagerError::Idunnoman)?.item; - - let special_exp_ratio: f32 = { - match equipped_weapon { - ItemDetail::Weapon(weapon) => match weapon.special { - Some(WeaponSpecial::Masters) => 0.08, - Some(WeaponSpecial::Lords) => 0.10, - Some(WeaponSpecial::Kings) => 0.12, - _ => 0.0, // TODO: error - stealing exp with wrong special - }, - ItemDetail::ESWeapon(esweapon) => match esweapon.special { - Some(ESWeaponSpecial::Kings) => 0.12, - _ => 0.0, // TODO: error - stealing exp with wrong special - }, - _ => 0.0, // TODO: error - stealing exp without a weapon!! - } - }; + }; + + let equipped_weapon_handle = item_manager + .get_character_inventory_mut(&client.character)? + .get_equipped_weapon_handle() + .ok_or(ItemManagerError::CannotGetIndividualItem)?; + + let equipped_weapon = &equipped_weapon_handle + .item() + .ok_or(ItemManagerError::Idunnoman)? + .individual() + .ok_or(ItemManagerError::Idunnoman)?.item; + + let special_exp_ratio: f32 = { + match equipped_weapon { + ItemDetail::Weapon(weapon) => match weapon.special { + Some(WeaponSpecial::Masters) => 0.08, + Some(WeaponSpecial::Lords) => 0.10, + Some(WeaponSpecial::Kings) => 0.12, + _ => 0.0, // TODO: error - stealing exp with wrong special + }, + ItemDetail::ESWeapon(esweapon) => match esweapon.special { + Some(ESWeaponSpecial::Kings) => 0.12, + _ => 0.0, // TODO: error - stealing exp with wrong special + }, + _ => 0.0, // TODO: error - stealing exp without a weapon!! + } + }; - let weapon_special_reduction: f32 = { - match equipped_weapon { - ItemDetail::Weapon(weapon) => weapon.weapon.special_penalty(), - ItemDetail::ESWeapon(_esweapon) => 0.0, - _ => 0.0, - } - }; + let weapon_special_reduction: f32 = { + match equipped_weapon { + ItemDetail::Weapon(weapon) => weapon.weapon.special_penalty(), + ItemDetail::ESWeapon(_esweapon) => 0.0, + _ => 0.0, + } + }; - let exp_gain = ((monster_stats.exp as f32 * (char_special_modifier + special_exp_ratio)).clamp(1.0, 80.0) * (1.0 - weapon_special_reduction)) as u32; + let exp_gain = ((monster_stats.exp as f32 * (char_special_modifier + special_exp_ratio)).clamp(1.0, 80.0) * (1.0 - weapon_special_reduction)) as u32; - let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; - let gain_exp_pkt = builder::message::character_gained_exp(area_client, exp_gain); - let mut exp_pkts: Box + Send> = Box::new(clients_in_area.clone().into_iter() - .map(move |c| { - (c.client, SendShipPacket::Message(Message::new(GameMessage::GiveCharacterExp(gain_exp_pkt.clone())))) - })); + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + let gain_exp_pkt = builder::message::character_gained_exp(area_client, exp_gain); + let mut exp_pkts: Box + Send> = Box::new(clients_in_area.clone().into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::GiveCharacterExp(gain_exp_pkt.clone())))) + })); - let before_level = level_table.get_level_from_exp(client.character.char_class, client.character.exp); - let after_level = level_table.get_level_from_exp(client.character.char_class, client.character.exp + exp_gain); - let level_up = before_level != after_level; + let before_level = level_table.get_level_from_exp(client.character.char_class, client.character.exp); + let after_level = level_table.get_level_from_exp(client.character.char_class, client.character.exp + exp_gain); + let level_up = before_level != after_level; - if level_up { - let (_, before_stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); - let (after_level, after_stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp + exp_gain); + if level_up { + let (_, before_stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); + let (after_level, after_stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp + exp_gain); - let level_up_pkt = builder::message::character_leveled_up(area_client, after_level, before_stats, after_stats); - exp_pkts = Box::new(exp_pkts.chain(clients_in_area.into_iter() - .map(move |c| { - (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone())))) - }))) - } + let level_up_pkt = builder::message::character_leveled_up(area_client, after_level, before_stats, after_stats); + exp_pkts = Box::new(exp_pkts.chain(clients_in_area.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone())))) + }))) + } - client.character.exp += exp_gain; - entity_gateway.save_character(&client.character).await?; + client.character.exp += exp_gain; + entity_gateway.save_character(&client.character).await?; - Ok(exp_pkts) + Ok(exp_pkts) + } } \ No newline at end of file diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index 3520f74..fe31773 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -667,17 +667,79 @@ async fn test_exp_steal_multihit_penalty() { } #[async_std::test] -async fn test_exp_steal_doesnt_exceed_100p() { - assert!(false) +async fn test_cannot_steal_exp_from_boss() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + + let mut p1_inv = Vec::new(); + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Raygun, + grind: 5, + special: Some(item::weapon::WeaponSpecial::Kings), + attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 100}), + Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}), + None,], + tekked: true, + } + ), + }).await.unwrap()); + + let equipped = item::EquippedEntity { + weapon: Some(p1_inv[0].id), + armor: None, + shield: None, + unit: [None; 4], + mag: None, + }; + entity_gateway.set_character_equips(&char1.id, &equipped).await.unwrap(); + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + let enemy_id = { + let room = ship.blocks.0[0].rooms[0].as_ref().unwrap(); + let enemy_id = (0..).filter_map(|i| { + room.maps.enemy_by_id(i).ok().and_then(|enemy| { + if enemy.monster == MonsterType::Dragon { + Some(i) + } + else { + None + } + }) + }).next().unwrap(); + enemy_id + }; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::ExperienceSteal(ExperienceSteal{ + client: 0, + target: 0, + client2: enemy_id as u8, + target2: 16, + enemy_id: enemy_id as u16, + })))).await.unwrap().for_each(drop); + + let c1 = ship.clients.get(&ClientId(1)).unwrap(); + assert!(c1.character.exp == 0); } #[async_std::test] -async fn test_each_client_can_steal_full_exp_from_same_enemy() { +async fn test_exp_steal_doesnt_exceed_100p() { assert!(false) } #[async_std::test] -async fn test_cannot_steal_exp_from_boss() { +async fn test_each_client_can_steal_full_exp_from_same_enemy() { assert!(false) }