expsteal #107
| @ -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, | ||||
|  | ||||
| @ -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<EG: EntityGateway>(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<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.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<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() | ||||
|             .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) | ||||
| } | ||||
| @ -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)?; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user