From f4fae162f0d03761c04fb001e70268ccfc21d214 Mon Sep 17 00:00:00 2001 From: jake Date: Thu, 3 Dec 2020 15:04:48 -0700 Subject: [PATCH] tekking! --- src/ship/items/manager.rs | 35 ++++++++ src/ship/packet/builder/message.rs | 11 +++ src/ship/packet/handler/direct_message.rs | 105 +++++++++++++++++++++- src/ship/packet/handler/message.rs | 67 ++++++++------ src/ship/ship.rs | 12 ++- 5 files changed, 200 insertions(+), 30 deletions(-) diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index d8aac3d..ff554ea 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -7,6 +7,7 @@ use crate::entity::item::{ItemDetail, ItemLocation, BankName}; use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, EquippedEntity, InventoryEntity, BankItemEntity, BankEntity}; use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::unit; +use crate::entity::item::weapon; use crate::ship::map::MapArea; use crate::ship::ship::ItemDropLocation; use crate::ship::drops::{ItemDrop, ItemDropType}; @@ -888,4 +889,38 @@ impl ItemManager { entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(()) } + + pub async fn replace_item_with_tekked(&mut self, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: ClientItemId, + tek: weapon::WeaponModifier) + -> Result { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + + let item = inventory.remove_by_id(item_id) + .ok_or(ItemManagerError::NoSuchItemId(item_id))?; + let individual = item + .individual() + .ok_or(ItemManagerError::WrongItemType(item_id))?; + + let entity_id = individual.entity_id; + let mut weapon = individual + .weapon() + .ok_or(ItemManagerError::WrongItemType(item_id))? + .clone(); + + weapon.apply_modifier(&tek); + entity_gateway.add_weapon_modifier(&entity_id, tek).await?; + + inventory.add_item(InventoryItem::Individual(IndividualInventoryItem { + entity_id: entity_id, + item_id: item_id, + item: ItemDetail::Weapon(weapon.clone()), + })); + + entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + + Ok(weapon) + } } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index c95f312..91d4149 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -163,3 +163,14 @@ pub fn shop_list(shop_type: u8, items: &Vec) -> ShopList { items: items, } } + +pub fn tek_preview(id: ClientItemId, weapon: &item::weapon::Weapon) -> Result { + let bytes = weapon.as_bytes(); + Ok(TekPreview { + client: 0x79, + target: 0, + item_bytes: bytes[0..12].try_into()?, + item_id: id.0, + item_bytes2: bytes[12..16].try_into()?, + }) +} diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index 7337a73..44a8fbf 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -1,4 +1,6 @@ use log::warn; +use rand::Rng; +use rand::seq::SliceRandom; use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::common::leveltable::CharacterLevelTable; @@ -6,8 +8,9 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::drops::ItemDrop; -use crate::ship::items::{ItemManager, ClientItemId, TriggerCreateItem, FloorItem, FloorType}; +use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType}; use crate::entity::gateway::EntityGateway; +use crate::entity::item; use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem}; @@ -24,6 +27,13 @@ const BANK_MESETA_CAPACITY: u32 = 999999; //const BANK_ACTION_: u8 = 1; +#[derive(thiserror::Error, Debug)] +#[error("")] +pub enum MessageError { + InvalidTek(ClientItemId), + MismatchedTekIds(ClientItemId, ClientItemId), +} + fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation) -> Box + Send> { Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() @@ -395,3 +405,96 @@ where }))) } + + +const TEK_SPECIAL_MODIFIER: [item::weapon::TekSpecialModifier; 3] = [item::weapon::TekSpecialModifier::Plus, + item::weapon::TekSpecialModifier::Neutral, + item::weapon::TekSpecialModifier::Minus]; +const TEK_PERCENT_MODIFIER: [item::weapon::TekPercentModifier; 5] = [item::weapon::TekPercentModifier::PlusPlus, + item::weapon::TekPercentModifier::Plus, + item::weapon::TekPercentModifier::Neutral, + item::weapon::TekPercentModifier::Minus, + item::weapon::TekPercentModifier::MinusMinus]; + +pub async fn request_tek_item(id: ClientId, + tek_request: &TekRequest, + entity_gateway: &mut EG, + clients: &mut Clients, + item_manager: &mut ItemManager) + -> Result + Send>, anyhow::Error> +where + EG: EntityGateway +{ + let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; + + let (grind_mod, special_mod, percent_mod) = { + let mut rng = rand::thread_rng(); + + let grind_mod = rng.gen_range(-4, 4); + let special_mod = TEK_SPECIAL_MODIFIER.choose(&mut rng).cloned().unwrap(); + let percent_mod = TEK_PERCENT_MODIFIER.choose(&mut rng).cloned().unwrap(); + (grind_mod, special_mod, percent_mod) + }; + + client.tek = Some((ClientItemId(tek_request.item_id), special_mod, percent_mod, grind_mod)); + + let inventory = item_manager.get_character_inventory(&client.character)?; + let item = inventory.get_item_by_id(ClientItemId(tek_request.item_id)) + .ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?; + let mut weapon = item.individual() + .ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))? + .weapon() + .ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))? + .clone(); + + weapon.apply_modifier(&item::weapon::WeaponModifier::Tekked { + special: special_mod, + percent: percent_mod, + grind: grind_mod, + }); + + client.character.meseta -= 100; + entity_gateway.save_character(&client.character).await?; + + let preview_pkt = builder::message::tek_preview(ClientItemId(tek_request.item_id), &weapon)?; + + Ok(Box::new(vec![(id, SendShipPacket::Message(Message::new(GameMessage::TekPreview(preview_pkt))))].into_iter())) +} + +pub async fn accept_tek_item(id: ClientId, + tek_accept: &TekAccept, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + item_manager: &mut ItemManager) + -> 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() })?; + + if let Some((item_id, special_mod, percent_mod, grind_mod)) = client.tek { + if item_id.0 != tek_accept.item_id { + return Err(MessageError::MismatchedTekIds(item_id, ClientItemId(tek_accept.item_id)).into()); + } + + let modifier = item::weapon::WeaponModifier::Tekked { + special: special_mod, + percent: percent_mod, + grind: grind_mod, + }; + let weapon = item_manager.replace_item_with_tekked(entity_gateway, &client.character, item_id, modifier).await?; + + let create_item_pkt = builder::message::create_item(area_client, item_id, &item::ItemDetail::Weapon(weapon))?; + + let neighbors = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?; + Ok(Box::new(neighbors.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item_pkt.clone())))) + }))) + } + else { + Err(MessageError::InvalidTek(ClientItemId(tek_accept.item_id)).into()) + } +} diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 39ab2f4..0ce969a 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -114,48 +114,59 @@ pub fn drop_coordinates(id: ClientId, Ok(Box::new(None.into_iter())) } -pub async fn split_item_stack(id: ClientId, - no_longer_has_item: &PlayerNoLongerHasItem, - entity_gateway: &mut EG, - client_location: &ClientLocation, - clients: &mut Clients, - item_manager: &mut ItemManager) - -> Result + Send>, anyhow::Error> +pub async fn no_longer_has_item(id: ClientId, + no_longer_has_item: &PlayerNoLongerHasItem, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + item_manager: &mut ItemManager) + -> 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 drop_location = client.item_drop_location.ok_or(ShipError::ItemDropLocationNotSet)?; + if let Some(drop_location) = client.item_drop_location { + if drop_location.item_id.0 != no_longer_has_item.item_id { + return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id).into()); + } - if drop_location.item_id.0 != no_longer_has_item.item_id { - return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id).into()); - } + if no_longer_has_item.item_id == 0xFFFFFFFF { + let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, no_longer_has_item.amount as u32).await?; + + let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?; + client.item_drop_location = None; - if no_longer_has_item.item_id == 0xFFFFFFFF { - let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, no_longer_has_item.amount as u32).await?; + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + Ok(Box::new(clients_in_area.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_meseta_pkt.clone())))) + }))) + } + else { + let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, no_longer_has_item.amount as usize).await?; - let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?; - client.item_drop_location = None; + let dropped_item_pkt = builder::message::drop_split_stack(area_client, dropped_item)?; + client.item_drop_location = None; - let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; - Ok(Box::new(clients_in_area.into_iter() + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + Ok(Box::new(clients_in_area.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_item_pkt.clone())))) + }))) + } + } + else if let Some(_tek) = client.tek { + let neighbors = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?; + let no_longer_has_item = no_longer_has_item.clone(); + Ok(Box::new(neighbors.into_iter() .map(move |c| { - (c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_meseta_pkt.clone())))) + (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(no_longer_has_item.clone())))) }))) } else { - let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, no_longer_has_item.amount as usize).await?; - - let dropped_item_pkt = builder::message::drop_split_stack(area_client, dropped_item)?; - client.item_drop_location = None; - - let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; - Ok(Box::new(clients_in_area.into_iter() - .map(move |c| { - (c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_item_pkt.clone())))) - }))) + Err(ShipError::InvalidItem(ClientItemId(no_longer_has_item.item_id)).into()) } } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index b61fbf5..f8f08a6 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -23,6 +23,7 @@ use crate::login::character::SHIP_MENU_ID; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::account::{UserAccountEntity, UserSettingsEntity}; use crate::entity::character::{CharacterEntity, SectionID}; +use crate::entity::item; use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocationError, GetNeighborError, GetClientsError, GetAreaError}; @@ -71,6 +72,7 @@ pub enum ShipError { UnknownMonster(crate::ship::monster::MonsterType), InvalidShip(usize), InvalidBlock(usize), + InvalidItem(items::ClientItemId), } #[derive(Debug)] @@ -254,6 +256,7 @@ pub struct ClientState { pub weapon_shop: Vec, pub tool_shop: Vec, pub armor_shop: Vec, + pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>, } impl ClientState { @@ -273,6 +276,7 @@ impl ClientState { weapon_shop: Vec::new(), tool_shop: Vec::new(), armor_shop: Vec::new(), + tek: None, } } } @@ -445,7 +449,7 @@ impl ShipServerState { }, GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::split_item_stack(id, no_longer_has_item, &mut self.entity_gateway, &mut block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &mut block.client_location, &mut self.clients, &mut self.item_manager).await? }, GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) | GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) | @@ -516,6 +520,12 @@ impl ShipServerState { GameMessage::BuyItem(buy_item) => { handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? }, + GameMessage::TekRequest(tek_request) => { + handler::direct_message::request_tek_item(id, tek_request, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await? + }, + GameMessage::TekAccept(tek_accept) => { + handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + }, _ => { let cmsg = msg.clone(); Box::new(block.client_location.get_all_clients_by_client(id).unwrap().into_iter()