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 @@ -562,17 +562,117 @@ async fn test_exp_steal_no_android_boost_in_vhard() { } #[async_std::test] -async fn test_exp_steal_doesnt_exceed_100p() { - assert!(false) +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_multihit_penalty() { +async fn test_exp_steal_doesnt_exceed_100p() { 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) }