From 9f54e3f3eb6a8d6306cde664f227843627da1371 Mon Sep 17 00:00:00 2001 From: andy Date: Sun, 15 May 2022 19:01:11 +0000 Subject: [PATCH 1/7] exp stela part 1 --- src/bin/main.rs | 11 +-- src/ship/packet/handler/message.rs | 103 ++++++++++++++++++++++++++++- src/ship/ship.rs | 4 ++ 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index d493ec2..152b79f 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -74,6 +74,7 @@ fn main() { character.slot = 2; character.name = "ItemRefactor".into(); character.exp = 80000000; + character.char_class = elseware::entity::character::CharacterClass::HUcast; let character = entity_gateway.create_character(character).await.unwrap(); entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap(); entity_gateway.set_bank_meseta(&character.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap(); @@ -110,11 +111,11 @@ fn main() { item::weapon::Weapon { weapon: item::weapon::WeaponType::Raygun, grind: 5, - special: Some(item::weapon::WeaponSpecial::Hell), - attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}), + 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: false, + tekked: true, } ), }).await.unwrap(); @@ -124,8 +125,8 @@ fn main() { item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, grind: 5, - special: Some(item::weapon::WeaponSpecial::Charge), - attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}), + special: Some(item::weapon::WeaponSpecial::Lords), + 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, diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index c39ba73..6f2997e 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -3,9 +3,12 @@ use libpso::packet::messages::*; use crate::entity::gateway::EntityGateway; use crate::common::serverstate::ClientId; use crate::common::leveltable::CharacterLevelTable; +use crate::entity::item::ItemDetail; +use crate::entity::item::esweapon::{ESWeaponSpecial}; +use crate::entity::item::weapon::{WeaponSpecial}; use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation}; use crate::ship::location::{ClientLocation, ClientLocationError}; -use crate::ship::items::{ItemManager, ClientItemId}; +use crate::ship::items::{ItemManager, ClientItemId, ItemManagerError}; use crate::ship::packet::builder; pub async fn request_exp(id: ClientId, @@ -398,3 +401,101 @@ where // TODO: send the packet to other clients 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, + expsteal: &ExperienceSteal, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + rooms: &mut Rooms, + item_manager: &mut ItemManager, + level_table: &CharacterLevelTable) + -> Result + Send>, anyhow::Error> +where + EG: EntityGateway +{ + let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; + let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; + let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?; + let room = rooms.get_mut(room_id.0) + .ok_or(ShipError::InvalidRoom(room_id.0 as u32))? + .as_mut() + .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))?; + + 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 + }; + + + let weapon_exp_ratio: f32 = { + 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; + 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 exp_gain = (monster_stats.exp as f32 * (char_special_modifier + weapon_exp_ratio)).clamp(1.0, 80.0) 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 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); + + 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?; + + Ok(exp_pkts) +} \ No newline at end of file diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 4cf5e2a..a5a12f8 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -520,6 +520,10 @@ 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::ExperienceSteal(exp_steal) => { + let block = self.blocks.with_client(id, &self.clients)?; + handler::message::player_steals_exp(id, exp_steal, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager, &self.level_table).await? + }, _ => { let cmsg = msg.clone(); let block = self.blocks.with_client(id, &self.clients)?; -- 2.36.0 From 66037d5e5b17015d3f7cb0f994dcbcfc4a45f1b2 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 16 May 2022 15:14:59 +0000 Subject: [PATCH 2/7] wow some tests --- tests/test_exp_gain.rs | 391 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index 0db6ba9..4c2e5a3 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -1,8 +1,11 @@ use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::character::{CharacterClass}; use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::common::leveltable::CharacterLevelTable; use elseware::ship::ship::{ShipServerState, SendShipPacket, RecvShipPacket}; use elseware::ship::monster::MonsterType; +use elseware::entity::item; +use elseware::ship::room::{Difficulty}; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -190,3 +193,391 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() { assert!(c1.character.exp == exp); assert!(c2.character.exp == (exp as f32 * 0.8) as u32); } + +#[async_std::test] +async fn test_exp_steal_min_1() { + 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::Booma { + 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 == 1); +} + +#[async_std::test] +async fn test_exp_steal_max_80() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + char1.exp = 80000000; + char1.char_class = CharacterClass::HUcast; + entity_gateway.save_character(&char1).await.unwrap(); + + 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_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).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::Booma { + 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 == 80000080); +} + +#[async_std::test] +async fn test_exp_steal_android_boost_in_ultimate() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + char1.exp = 80000000; + char1.char_class = CharacterClass::HUcast; + entity_gateway.save_character(&char1).await.unwrap(); + + let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + char2.exp = 80000000; + char2.char_class = CharacterClass::HUmar; + entity_gateway.save_character(&char2).await.unwrap(); + + 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 p2_inv = Vec::new(); + p2_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(p2_inv[0].id), + armor: None, + shield: None, + unit: [None; 4], + mag: None, + }; + entity_gateway.set_character_equips(&char2.id, &equipped).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_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_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).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::Booma { + 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); + + ship.handle(ClientId(2), &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(); + let c2 = ship.clients.get(&ClientId(2)).unwrap(); + println!("c1 exp: {:?}, c2 exp: {:?}", c1.character.exp, c2.character.exp); + assert!(c1.character.exp == 80000080); + assert!(c2.character.exp == 80000032); +} + +#[async_std::test] +async fn test_exp_steal_no_android_boost_in_vhard() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + char1.exp = 80000000; + char1.char_class = CharacterClass::HUcast; + entity_gateway.save_character(&char1).await.unwrap(); + + let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + char2.exp = 80000000; + char2.char_class = CharacterClass::HUmar; + entity_gateway.save_character(&char2).await.unwrap(); + + 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 p2_inv = Vec::new(); + p2_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(p2_inv[0].id), + armor: None, + shield: None, + unit: [None; 4], + mag: None, + }; + entity_gateway.set_character_equips(&char2.id, &equipped).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_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_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::VeryHard).await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).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::Booma { + 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); + + ship.handle(ClientId(2), &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(); + let c2 = ship.clients.get(&ClientId(2)).unwrap(); + println!("c1 exp: {:?}, c2 exp: {:?}", c1.character.exp, c2.character.exp); + assert!(c1.character.exp == 80000010); + assert!(c2.character.exp == 80000010); +} + +#[async_std::test] +async fn test_exp_steal_doesnt_exceed_100p() { + assert!(false) +} + +#[async_std::test] +async fn test_exp_steal_multihit_penalty() { + assert!(false) +} + +#[async_std::test] +async fn test_each_client_can_steal_exp_from_same_enemy() { + assert!(false) +} + +#[async_std::test] +async fn test_cannot_steal_exp_from_boss() { + assert!(false) +} + -- 2.36.0 From 23a2df136bcdb2ca38ed92d0b65dae0502a97fd4 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 16 May 2022 23:08:03 +0000 Subject: [PATCH 3/7] multihit weapon penalties and tests --- src/bin/main.rs | 6 +- src/entity/item/weapon.rs | 74 +++++++++++++++++++ src/ship/monster.rs | 38 ++++++++++ src/ship/packet/handler/message.rs | 35 +++++---- tests/test_exp_gain.rs | 112 +++++++++++++++++++++++++++-- 5 files changed, 242 insertions(+), 23 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 152b79f..f43276e 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -137,9 +137,9 @@ fn main() { NewItemEntity { item: ItemDetail::Weapon( item::weapon::Weapon { - weapon: item::weapon::WeaponType::Vjaya, + weapon: item::weapon::WeaponType::Ripper, grind: 5, - special: Some(item::weapon::WeaponSpecial::Charge), + special: Some(item::weapon::WeaponSpecial::Kings), attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), None,], @@ -153,7 +153,7 @@ fn main() { item::weapon::Weapon { weapon: item::weapon::WeaponType::Vulcan, grind: 5, - special: Some(item::weapon::WeaponSpecial::Charge), + 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: 100}), None,], diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index 11a8709..8ab6c0d 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -1421,6 +1421,80 @@ impl WeaponType { _ => Err(ItemParseError::InvalidWeaponType), } } + + pub fn special_penalty(&self) -> f32 { + match(self) { + WeaponType::Saber => 0.0, + WeaponType::Brand => 0.0, + WeaponType::Buster => 0.0, + WeaponType::Pallasch => 0.0, + WeaponType::Gladius => 0.0, + WeaponType::Handgun => 0.0, + WeaponType::Autogun => 0.0, + WeaponType::Lockgun => 0.0, + WeaponType::Railgun => 0.0, + WeaponType::Raygun => 0.0, + WeaponType::Rifle => 0.0, + WeaponType::Sniper => 0.0, + WeaponType::Blaster => 0.0, + WeaponType::Beam => 0.0, + WeaponType::Laser => 0.0, + WeaponType::Cane => 0.0, + WeaponType::Stick => 0.0, + WeaponType::Mace => 0.0, + WeaponType::Club => 0.0, + WeaponType::Rod => 0.0, + WeaponType::Pole => 0.0, + WeaponType::Pillar => 0.0, + WeaponType::Striker => 0.0, + WeaponType::Wand => 0.0, + WeaponType::Staff => 0.0, + WeaponType::Baton => 0.0, + WeaponType::Scepter => 0.0, + + WeaponType::Sword => 0.5, + WeaponType::Gigush => 0.5, + WeaponType::Breaker => 0.5, + WeaponType::Claymore => 0.5, + WeaponType::Calibur => 0.5, + WeaponType::FlowensSword => 0.5, + WeaponType::LastSurvivor => 0.5, + WeaponType::DragonSlayer => 0.5, + WeaponType::Dagger => 0.5, + WeaponType::Knife => 0.5, + WeaponType::Blade => 0.5, + WeaponType::Edge => 0.5, + WeaponType::Ripper => 0.5, + WeaponType::BladeDance => 0.5, + WeaponType::BloodyArt => 0.5, + WeaponType::CrossScar => 0.5, + WeaponType::ZeroDivide => 0.5, + WeaponType::TwoKamui => 0.5, + WeaponType::Partisan => 0.5, + WeaponType::Halbert => 0.5, + WeaponType::Glaive => 0.5, + WeaponType::Berdys => 0.5, + WeaponType::Gungnir => 0.5, + + WeaponType::Slicer => 0.6666, + WeaponType::Spinner => 0.6666, + WeaponType::Cutter => 0.6666, + WeaponType::Sawcer => 0.6666, + WeaponType::Diska => 0.6666, + WeaponType::Mechgun => 0.6666, + WeaponType::Assault => 0.6666, + WeaponType::Repeater => 0.6666, + WeaponType::Gatling => 0.6666, + WeaponType::Vulcan => 0.6666, + WeaponType::Shot => 0.6666, + WeaponType::Spread => 0.6666, + WeaponType::Cannon => 0.6666, + WeaponType::Launcher => 0.6666, + WeaponType::Arms => 0.6666, + + _ => 1.0, + } + } } diff --git a/src/ship/monster.rs b/src/ship/monster.rs index 1bdb5b7..8dab9f1 100644 --- a/src/ship/monster.rs +++ b/src/ship/monster.rs @@ -148,6 +148,44 @@ pub enum MonsterType { Kondrieu, } +impl MonsterType { + pub fn is_boss(&self) -> bool { + matches!(self, + MonsterType::Dragon | + MonsterType::DeRolLe | + MonsterType::DeRolLeBody | + MonsterType::DeRolLeMine | + MonsterType::VolOptPartA | + MonsterType::VolOptPillar | + MonsterType::VolOptMonitor | + MonsterType::VolOptAmp | + MonsterType::VolOptCore | + MonsterType::VolOptUnused | + MonsterType::VolOpt | + MonsterType::VolOptTrap | + MonsterType::DarkFalz | + MonsterType::DarkFalz1 | + MonsterType::DarkFalz2 | + MonsterType::DarkFalz3 | + MonsterType::Darvant | + MonsterType::UltDarvant | + MonsterType::Epsiguard | // TODO: is epsilon core a boss? + MonsterType::BarbaRay | + MonsterType::PigRay | + MonsterType::GolDragon | + MonsterType::GalGryphon | + MonsterType::OlgaFlow | + MonsterType::OlgaFlow1 | + MonsterType::OlgaFlow2 | + MonsterType::Gael | + MonsterType::Giel | + MonsterType::SaintMillion | + MonsterType::Shambertin | + MonsterType::Kondrieu + ) + } +} + #[derive(serde::Deserialize, Debug)] pub struct MonsterStats { diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 6f2997e..79c8c11 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -442,19 +442,18 @@ where 0.0 }; - - let weapon_exp_ratio: f32 = { - 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 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, @@ -470,7 +469,15 @@ where } }; - let exp_gain = (monster_stats.exp as f32 * (char_special_modifier + weapon_exp_ratio)).clamp(1.0, 80.0) as u32; + 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 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); diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index 4c2e5a3..3520f74 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -561,18 +561,118 @@ async fn test_exp_steal_no_android_boost_in_vhard() { assert!(c2.character.exp == 80000010); } +#[async_std::test] +async fn test_exp_steal_multihit_penalty() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + char1.exp = 80000000; + char1.char_class = CharacterClass::HUcast; + entity_gateway.save_character(&char1).await.unwrap(); + + 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::Dagger, + 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()); + + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Mechgun, + 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_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).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::Booma { + 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 == 80000040); + + // change equipped item + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerEquipItem(PlayerEquipItem { + client: 0, + target: 0, + item_id: 0x10001, + sub_menu: 9, + unknown1: 0, + })))).await.unwrap().for_each(drop); + + 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 == 80000066); +} + #[async_std::test] async fn test_exp_steal_doesnt_exceed_100p() { assert!(false) } #[async_std::test] -async fn test_exp_steal_multihit_penalty() { - assert!(false) -} - -#[async_std::test] -async fn test_each_client_can_steal_exp_from_same_enemy() { +async fn test_each_client_can_steal_full_exp_from_same_enemy() { assert!(false) } -- 2.36.0 From ed550842f6ef9f6c33b1c5ee6d904938708c3519 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 16 May 2022 23:22:34 +0000 Subject: [PATCH 4/7] 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 | 72 ++++++++++++++-- 3 files changed, 136 insertions(+), 72 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_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 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 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 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); - - 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() + 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::PlayerLevelUp(level_up_pkt.clone())))) - }))) + (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; + + 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())))) + }))) + } + + client.character.exp += exp_gain; + entity_gateway.save_character(&client.character).await?; + + Ok(exp_pkts) } - - client.character.exp += exp_gain; - entity_gateway.save_character(&client.character).await?; - - 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 @@ -666,6 +666,73 @@ async fn test_exp_steal_multihit_penalty() { assert!(c1.character.exp == 80000066); } +#[async_std::test] +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_exp_steal_doesnt_exceed_100p() { assert!(false) @@ -676,8 +743,3 @@ async fn test_each_client_can_steal_full_exp_from_same_enemy() { assert!(false) } -#[async_std::test] -async fn test_cannot_steal_exp_from_boss() { - assert!(false) -} - -- 2.36.0 From 5a83daed48056afc5f0b2797f7732ab60a76cecb Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 17 May 2022 03:37:13 +0000 Subject: [PATCH 5/7] even more tests. also track exp stolen per client --- src/ship/map/enemy.rs | 7 ++ src/ship/map/maps.rs | 4 + src/ship/packet/handler/message.rs | 141 ++++++++++++----------- tests/test_exp_gain.rs | 178 ++++++++++++++++++++++++++++- 4 files changed, 261 insertions(+), 69 deletions(-) diff --git a/src/ship/map/enemy.rs b/src/ship/map/enemy.rs index b5efd7c..c6719bf 100644 --- a/src/ship/map/enemy.rs +++ b/src/ship/map/enemy.rs @@ -118,6 +118,7 @@ pub struct MapEnemy { pub dropped_item: bool, pub gave_exp: bool, pub shiny: bool, + pub stolen_exp: [u32; 4], // tracks total amount of exp stolen by each player } impl MapEnemy { @@ -301,6 +302,7 @@ impl MapEnemy { gave_exp: false, player_hit: [false; 4], shiny: false, + stolen_exp: [0; 4], }) } @@ -313,6 +315,7 @@ impl MapEnemy { gave_exp: false, player_hit: [false; 4], shiny: false, + stolen_exp: [0; 4], } } @@ -366,5 +369,9 @@ impl MapEnemy { } self } + + pub fn steal_exp(&mut self, exp: u32, slot: usize) { + self.stolen_exp[slot] += exp + } } diff --git a/src/ship/map/maps.rs b/src/ship/map/maps.rs index 0743c01..d75229f 100644 --- a/src/ship/map/maps.rs +++ b/src/ship/map/maps.rs @@ -298,6 +298,10 @@ impl Maps { self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id)) } + pub fn mut_enemy_by_id(&mut self, id: usize) -> Option<&mut MapEnemy> { + self.enemy_data[id].as_mut() + } + pub fn object_by_id(&self, id: usize) -> Result { self.object_data[id].ok_or(MapsError::InvalidObjectId(id)) } diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 352fc94..fe47942 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -6,6 +6,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::entity::item::ItemDetail; use crate::entity::item::esweapon::{ESWeaponSpecial}; use crate::entity::item::weapon::{WeaponSpecial}; +use crate::ship::map::MapsError; use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::items::{ItemManager, ClientItemId, ItemManagerError}; @@ -402,8 +403,6 @@ where Ok(Box::new(None.into_iter())) } -// 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: use real errors (Idunnoman) // TODO: create InventoryError::CannotGetItemHandle or something @@ -427,84 +426,94 @@ where .as_mut() .ok_or(ShipError::InvalidRoom(room_id.0 as u32))?; - let monster = room.maps.enemy_by_id(expsteal.enemy_id as usize)?; + let monster = room.maps.mut_enemy_by_id(expsteal.enemy_id as usize).ok_or(MapsError::InvalidMonsterId(expsteal.enemy_id as usize))?; 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 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 remaining_exp = monster_stats.exp - monster.stolen_exp[area_client.local_client.id() as usize]; + if remaining_exp <= 0 { + Ok(Box::new(None.into_iter())) + } else { + 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_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 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 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, + _ => 1.0, // unreachable? + } + }; - 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_earned = std::cmp::min( + ((monster_stats.exp as f32 * (char_special_modifier + special_exp_ratio)).clamp(1.0, 80.0) * (1.0 - weapon_special_reduction)) as u32, + remaining_exp); - 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())))) - })); + monster.steal_exp(exp_earned, area_client.local_client.id() as usize); + println!("monster info: {:?}", monster); - 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); - - 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() + 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_earned); + let mut exp_pkts: Box + Send> = Box::new(clients_in_area.clone().into_iter() .map(move |c| { - (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone())))) - }))) + (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_earned); + 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_earned); + + 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_earned; + entity_gateway.save_character(&client.character).await?; + + Ok(exp_pkts) } - - client.character.exp += exp_gain; - entity_gateway.save_character(&client.character).await?; - - Ok(exp_pkts) } } \ No newline at end of file diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index fe31773..bb0b875 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -448,7 +448,7 @@ async fn test_exp_steal_android_boost_in_ultimate() { #[async_std::test] async fn test_exp_steal_no_android_boost_in_vhard() { - let mut entity_gateway = InMemoryGateway::default(); + let mut entity_gateway = InMemoryGateway::default(); let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; char1.exp = 80000000; @@ -735,11 +735,183 @@ async fn test_cannot_steal_exp_from_boss() { #[async_std::test] async fn test_exp_steal_doesnt_exceed_100p() { - assert!(false) + 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::Booma { + Some(i) + } + else { + None + } + }) + }).next().unwrap(); + enemy_id + }; + + for _ in 0..10 { + 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 == 5); } #[async_std::test] async fn test_each_client_can_steal_full_exp_from_same_enemy() { - assert!(false) + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + entity_gateway.save_character(&char1).await.unwrap(); + entity_gateway.save_character(&char2).await.unwrap(); + + 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 p2_inv = Vec::new(); + p2_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(p2_inv[0].id), + armor: None, + shield: None, + unit: [None; 4], + mag: None, + }; + entity_gateway.set_character_equips(&char2.id, &equipped).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_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_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Normal).await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(2)).await; + join_room(&mut ship, ClientId(2), 0).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::Booma { + Some(i) + } + else { + None + } + }) + }).next().unwrap(); + enemy_id + }; + + for _ in 0..10 { + 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); + + ship.handle(ClientId(2), &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(); + let c2 = ship.clients.get(&ClientId(2)).unwrap(); + println!("c1 exp: {:?}, c2 exp: {:?}", c1.character.exp, c2.character.exp); + assert!(c1.character.exp == 5); + assert!(c2.character.exp == 5); + } -- 2.36.0 From f6cc869cdc3b5e71da2fddb99c140f33547c3c66 Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 17 May 2022 21:08:41 +0000 Subject: [PATCH 6/7] flip4clip --- src/ship/packet/handler/message.rs | 157 +++++++++++++++-------------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index fe47942..87bfecf 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -406,6 +406,7 @@ where // TODO: convenience function for giving exp and checking levelups (un-duplicate code here and `request_exp`) // TODO: use real errors (Idunnoman) // TODO: create InventoryError::CannotGetItemHandle or something +#[allow(clippy::too_many_arguments)] pub async fn player_steals_exp (id: ClientId, expsteal: &ExperienceSteal, entity_gateway: &mut EG, @@ -433,87 +434,87 @@ where let monster_stats = room.monster_stats.get(&monster.monster).ok_or(ShipError::UnknownMonster(monster.monster))?; let remaining_exp = monster_stats.exp - monster.stolen_exp[area_client.local_client.id() as usize]; - if remaining_exp <= 0 { - Ok(Box::new(None.into_iter())) - } else { - let char_special_modifier: f32 = if client.character.char_class.is_android() { - if room.mode.difficulty() == crate::ship::room::Difficulty::Ultimate { - 0.3 + if remaining_exp > 0 { + 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 weapon_special_reduction: f32 = { - match equipped_weapon { - ItemDetail::Weapon(weapon) => weapon.weapon.special_penalty(), - ItemDetail::ESWeapon(_esweapon) => 0.0, - _ => 1.0, // unreachable? - } - }; - - let exp_earned = std::cmp::min( - ((monster_stats.exp as f32 * (char_special_modifier + special_exp_ratio)).clamp(1.0, 80.0) * (1.0 - weapon_special_reduction)) as u32, - remaining_exp); - - monster.steal_exp(exp_earned, area_client.local_client.id() as usize); - println!("monster info: {:?}", monster); - - 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_earned); - 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_earned); - 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_earned); - - 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() + }; + + 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, + _ => 1.0, // unreachable? + } + }; + + let exp_earned = std::cmp::min( + ((monster_stats.exp as f32 * (char_special_modifier + special_exp_ratio)).clamp(1.0, 80.0) * (1.0 - weapon_special_reduction)) as u32, + remaining_exp); + + monster.steal_exp(exp_earned, area_client.local_client.id() as usize); + println!("monster info: {:?}", monster); + + 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_earned); + let mut exp_pkts: Box + Send> = Box::new(clients_in_area.clone().into_iter() .map(move |c| { - (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone())))) - }))) - } - - client.character.exp += exp_earned; - entity_gateway.save_character(&client.character).await?; - - Ok(exp_pkts) + (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_earned); + 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_earned); + + 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_earned; + entity_gateway.save_character(&client.character).await?; + + Ok(exp_pkts) + } else { + Ok(Box::new(None.into_iter())) } } } \ No newline at end of file -- 2.36.0 From 7c3ff1bea1c36fd454dde5ca45234ecc9e1c75b0 Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 17 May 2022 21:09:54 +0000 Subject: [PATCH 7/7] printing is for turbo nerds --- src/ship/packet/handler/message.rs | 1 - tests/test_exp_gain.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 87bfecf..0ac24fc 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -485,7 +485,6 @@ where remaining_exp); monster.steal_exp(exp_earned, area_client.local_client.id() as usize); - println!("monster info: {:?}", monster); 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_earned); diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index bb0b875..9a8decc 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -441,7 +441,6 @@ async fn test_exp_steal_android_boost_in_ultimate() { let c1 = ship.clients.get(&ClientId(1)).unwrap(); let c2 = ship.clients.get(&ClientId(2)).unwrap(); - println!("c1 exp: {:?}, c2 exp: {:?}", c1.character.exp, c2.character.exp); assert!(c1.character.exp == 80000080); assert!(c2.character.exp == 80000032); } @@ -556,7 +555,6 @@ async fn test_exp_steal_no_android_boost_in_vhard() { let c1 = ship.clients.get(&ClientId(1)).unwrap(); let c2 = ship.clients.get(&ClientId(2)).unwrap(); - println!("c1 exp: {:?}, c2 exp: {:?}", c1.character.exp, c2.character.exp); assert!(c1.character.exp == 80000010); assert!(c2.character.exp == 80000010); } @@ -909,7 +907,6 @@ async fn test_each_client_can_steal_full_exp_from_same_enemy() { let c1 = ship.clients.get(&ClientId(1)).unwrap(); let c2 = ship.clients.get(&ClientId(2)).unwrap(); - println!("c1 exp: {:?}, c2 exp: {:?}", c1.character.exp, c2.character.exp); assert!(c1.character.exp == 5); assert!(c2.character.exp == 5); -- 2.36.0