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)?;