Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7c3ff1bea1 | |||
| f6cc869cdc | |||
| 5a83daed48 | |||
| ed550842f6 | |||
| 23a2df136b | |||
| 66037d5e5b | |||
| 9f54e3f3eb | 
| @ -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, | ||||
| @ -136,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,], | ||||
| @ -152,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,], | ||||
|  | ||||
| @ -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, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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<MapObject, MapsError> { | ||||
|         self.object_data[id].ok_or(MapsError::InvalidObjectId(id)) | ||||
|     } | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -3,9 +3,13 @@ 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::map::MapsError; | ||||
| 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<EG: EntityGateway>(id: ClientId, | ||||
| @ -398,3 +402,118 @@ where | ||||
|     // TODO: send the packet to other clients
 | ||||
|     Ok(Box::new(None.into_iter())) | ||||
| } | ||||
| 
 | ||||
| // 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<EG> (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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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.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 remaining_exp = monster_stats.exp - monster.stolen_exp[area_client.local_client.id() as usize]; | ||||
|             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 | ||||
|                 }; | ||||
|     
 | ||||
|                 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); | ||||
|     
 | ||||
|                 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<dyn Iterator<Item = _> + 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() | ||||
|                         .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())) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -520,6 +520,10 @@ 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::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)?; | ||||
|  | ||||
| @ -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,722 @@ 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(); | ||||
|     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(); | ||||
|     assert!(c1.character.exp == 80000010); | ||||
|     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_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() { | ||||
|     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() { | ||||
|     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(); | ||||
|     assert!(c1.character.exp == 5); | ||||
|     assert!(c2.character.exp == 5); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user