prevent exp steal from bosses and friends
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		
							parent
							
								
									23a2df136b
								
							
						
					
					
						commit
						ed550842f6
					
				| @ -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, | ||||
|  | ||||
| @ -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<EG> (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<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_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<dyn Iterator<Item = _> + 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) | ||||
| } | ||||
| @ -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) | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user