diff --git a/src/ship/drops/mod.rs b/src/ship/drops/mod.rs index e5037af..c006167 100644 --- a/src/ship/drops/mod.rs +++ b/src/ship/drops/mod.rs @@ -186,6 +186,10 @@ impl DropTable { pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option { self.box_table.get_drop(map_area, object, &mut self.rng) } + + pub fn get_rare_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option { + self.rare_table.get_rare_drop(map_area, monster, &mut self.rng) + } } @@ -205,4 +209,11 @@ mod test { .into_iter().choose(&mut rng).unwrap(); DropTable::::new(episode, difficulty, section_id); } + + #[test] + fn test_sjs_drop() { + let mut drop_table = DropTable::::new(Episode::Two, Difficulty::Ultimate, SectionID::Skyly); + let drop = drop_table.get_rare_drop(&MapArea::Seaside, &MonsterType::GiGue); + println!("drop: {:?}", drop); + } } diff --git a/src/ship/drops/rare_drop_table.rs b/src/ship/drops/rare_drop_table.rs index d8350e5..376fc6e 100644 --- a/src/ship/drops/rare_drop_table.rs +++ b/src/ship/drops/rare_drop_table.rs @@ -167,4 +167,14 @@ impl RareDropTable { }).next() }) } + + pub fn get_rare_drop(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option { + self.rates.get(monster) + .and_then(|drop_rates| { + drop_rates.iter() + .filter_map(|drop_rate| { + Some(self.apply_item_stats(map_area, drop_rate.item, rng)) + }).next() + }) + } } diff --git a/src/ship/map/maps.rs b/src/ship/map/maps.rs index 0743c01..e5e64d9 100644 --- a/src/ship/map/maps.rs +++ b/src/ship/map/maps.rs @@ -320,6 +320,16 @@ impl Maps { self.object_data = objects; } + pub fn get_enemy_id_by_monster_type(&self, monster: MonsterType) -> Option { + let (id, _) = self.enemy_data + .iter() + .enumerate() + .filter(|(_i, &m)| m.is_some()) + .find(|(_i, &m)| m.unwrap().monster == monster)?; + + Some(id as u16) + } + pub fn get_rare_monster_list(&self) -> Vec { let mut rare_monsters = vec![0xFFFF; 16]; let shiny: Vec<(usize, &Option)> = self.enemy_data.iter() diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index a417c6f..030e45e 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -79,6 +79,7 @@ pub async fn request_item(id: ClientId, where EG: EntityGateway { + println!("src::ship::packet::handler::request_item() - requesting an item!"); 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))? @@ -86,7 +87,9 @@ where .ok_or(ShipError::InvalidRoom(room_id.0 as u32))?; let monster = room.maps.enemy_by_id(request_item.enemy_id as usize)?; + println!("room id: {:?}, monster: {:?}", room_id, monster); if monster.dropped_item { + println!("monster {:?} already dropped an item!", monster); return Err(ShipError::MonsterAlreadyDroppedItem(id, request_item.enemy_id).into()) } @@ -94,9 +97,16 @@ where let client_and_drop = clients_in_area.into_iter() .filter_map(|area_client| { - room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| { - (area_client, item_drop_type) - }) + if room.redbox { + println!("red box mode is currently enabled. dropping red box!"); + room.drop_table.get_rare_drop(&monster.map_area, &monster.monster).map(|item_drop_type| { + (area_client, item_drop_type) + }) + } else { + room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| { + (area_client, item_drop_type) + }) + } }); let mut item_drop_packets = Vec::new(); diff --git a/src/ship/room.rs b/src/ship/room.rs index 0ed4421..7c38223 100644 --- a/src/ship/room.rs +++ b/src/ship/room.rs @@ -203,6 +203,7 @@ pub struct RoomState { pub rare_monster_table: Box, pub quest_group: QuestCategoryType, pub quests: Vec, + pub redbox: bool, // items on ground // enemy info } @@ -316,6 +317,11 @@ impl RoomState { map_areas: MapAreaLookup::new(&room_mode.episode()), quest_group: QuestCategoryType::Standard, quests: room_quests, + redbox: false, }) } + + pub fn toggle_redbox_mode(&mut self) { + self.redbox = !self.redbox; + } } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index db33110..2c07ebd 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -542,6 +542,7 @@ impl ShipServerState { handler::direct_message::guildcard_send(id, guildcard_send, target, &block.client_location, &self.clients) }, GameMessage::RequestItem(request_item) => { + println!("someone requested an item from the ship!"); handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await? }, GameMessage::PickupItem(pickup_item) => { diff --git a/tests/common.rs b/tests/common.rs index a44679f..6dbfc8a 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -3,8 +3,8 @@ use elseware::common::serverstate::{ClientId, ServerState}; use elseware::entity::gateway::EntityGateway; use elseware::entity::account::{UserAccountEntity, NewUserAccountEntity, NewUserSettingsEntity}; -use elseware::entity::character::{CharacterEntity, NewCharacterEntity}; use elseware::entity::item::{Meseta, BankName}; +use elseware::entity::character::{CharacterEntity, NewCharacterEntity, SectionID}; use elseware::ship::ship::{ShipServerState, RecvShipPacket}; use elseware::ship::room::Difficulty; @@ -34,6 +34,26 @@ pub async fn new_user_character(entity_gateway: &mut EG, user (user, character) } +pub async fn new_user_character_with_sid(entity_gateway: &mut EG, username: &str, password: &str, sid: SectionID) -> (UserAccountEntity, CharacterEntity) { + let new_user = NewUserAccountEntity { + email: format!("{}@pso.com", username), + username: username.into(), + password: bcrypt::hash(password, 5).unwrap(), + guildcard: 1, + activated: true, + ..NewUserAccountEntity::default() + }; + + let user = entity_gateway.create_user(new_user).await.unwrap(); + let new_settings = NewUserSettingsEntity::new(user.id); + let _settings = entity_gateway.create_user_settings(new_settings).await.unwrap(); + let mut new_character = NewCharacterEntity::new(user.id); + new_character.section_id = sid; + let character = entity_gateway.create_character(new_character).await.unwrap(); + + (user, character) +} + pub async fn log_in_char(ship: &mut ShipServerState, id: ClientId, username: &str, password: &str) { let username = username.to_string(); let password = password.to_string(); @@ -84,6 +104,21 @@ pub async fn create_room_with_difficulty(ship: &mut ShipServe ship.handle(id, &RecvShipPacket::DoneBursting(DoneBursting {})).await.unwrap().for_each(drop); } +pub async fn create_ep2_room_with_difficulty(ship: &mut ShipServerState, id: ClientId, name: &str, password: &str, difficulty: Difficulty) { + ship.handle(id, &RecvShipPacket::CreateRoom(CreateRoom { + unknown: [0; 2], + name: utf8_to_utf16_array!(name, 16), + password: utf8_to_utf16_array!(password, 16), + difficulty: difficulty.into(), + battle: 0, + challenge: 0, + episode: 2, + single_player: 0, + padding: [0; 3], + })).await.unwrap().for_each(drop); + ship.handle(id, &RecvShipPacket::DoneBursting(DoneBursting {})).await.unwrap().for_each(drop); +} + pub async fn join_room(ship: &mut ShipServerState, id: ClientId, room_id: u32) { ship.handle(id, &RecvShipPacket::MenuSelect(MenuSelect { menu: ROOM_MENU_ID, diff --git a/tests/test_item_actions.rs b/tests/test_item_actions.rs index ab91b2f..c8776a8 100644 --- a/tests/test_item_actions.rs +++ b/tests/test_item_actions.rs @@ -34,6 +34,7 @@ async fn test_equip_unit_from_equip_menu() { item::unit::Unit{ unit: item::unit::UnitType::KnightPower, modifier: None, + kills: None, }), }).await.unwrap()); @@ -43,6 +44,7 @@ async fn test_equip_unit_from_equip_menu() { item::unit::Unit{ unit: item::unit::UnitType::KnightPower, modifier: Some(item::unit::UnitModifier::Plus), + kills: None, }), }).await.unwrap()); @@ -111,6 +113,7 @@ async fn test_unequip_armor_with_units() { item::unit::Unit{ unit: item::unit::UnitType::KnightPower, modifier: None, + kills: None, }), }).await.unwrap()); @@ -120,6 +123,7 @@ async fn test_unequip_armor_with_units() { item::unit::Unit{ unit: item::unit::UnitType::KnightPower, modifier: Some(item::unit::UnitModifier::Plus), + kills: None, }), }).await.unwrap()); @@ -179,6 +183,7 @@ async fn test_sort_items() { item::unit::Unit{ unit: item::unit::UnitType::KnightPower, modifier: None, + kills: None, }), }).await.unwrap()); @@ -188,6 +193,7 @@ async fn test_sort_items() { item::unit::Unit{ unit: item::unit::UnitType::KnightPower, modifier: Some(item::unit::UnitModifier::Plus), + kills: None, }), }).await.unwrap()); diff --git a/tests/test_unseal_items.rs b/tests/test_unseal_items.rs new file mode 100644 index 0000000..238d203 --- /dev/null +++ b/tests/test_unseal_items.rs @@ -0,0 +1,131 @@ +/* TODO: +1. test to check if sjs/lame/limiter drop with Some() kill counter enabled +2. test to make sure other items drop with None kill counter +3. test kill counters get incremented per kill +4. test unsealing item: + - client item id does not change + - unsealed item no longer has kill counter +5. test reject unsealing item if not enough kills (can this even happen?) +*/ + +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::item; +use elseware::ship::ship::{ShipServerState, RecvShipPacket}; +use elseware::entity::character::{SectionID}; +use elseware::ship::room::{Difficulty}; +use elseware::ship::map::area::{MapArea}; +use elseware::ship::monster::{MonsterType}; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; + +#[path = "common.rs"] +mod common; +use common::*; + +#[async_std::test] +async fn test_item_drops_with_kill_counter() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, _char1) = new_user_character_with_sid(&mut entity_gateway, "a1", "a", SectionID::Skyly).await; + 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_ep2_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await; + + // ship.handle(ClientId(1), &RecvShipPacket()).await.unwrap().for_each(drop); + + // ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerWarpingToFloor(PlayerWarpingToFloor{ + // client: 0, + // target: 0, + // area: 9, // seaside + // data: 0, + // })))).await.unwrap().for_each(drop); + + + // ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerWarping(PlayerWarping{ + // client: 0, + // target: 0, + // })))).await.unwrap().for_each(drop); + + // ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerLoadedIn(PlayerLoadedIn{ + // client: 0, + // target: 0, + // unknown1: [0,0], + // rotation: 0, + // area: 9, + // room: 1, + // x: 100.0, + // y: 10.0, + // z: -20.0, + // })))).await.unwrap().for_each(drop); + + // ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerChangedMap2(PlayerChangedMap2{ + // client: 0, + // target: 0, + // map_area: 9, + // _unknown1: 0, + // })))).await.unwrap().for_each(drop); + + // ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSpawnedIntoArea(PlayerSpawnedIntoArea{ + // client: 0, + // target: 0, + // })))).await.unwrap().for_each(drop); + + // ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDoneChangingMap(PlayerDoneChangingMap{ + // client: 0, + // target: 0, + // })))).await.unwrap().for_each(drop); + + let room = ship.blocks.0[0].rooms[0].as_mut().unwrap(); + room.toggle_redbox_mode(); // enable redbox mode for sjs + + println!("room redbox mode: {:?}", room.redbox); + println!("room.mode: {:?}", room.mode); + println!("killing gigue for sjs!"); + let gigue_id = room.maps.get_enemy_id_by_monster_type(MonsterType::GiGue).unwrap(); + println!("found gigue id: {:?}!", gigue_id); + let pkts = ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::RequestItem(RequestItem { + client: 0, + target: 0, + map_area: 9, // seaside + pt_index: 55, // gigue ? + enemy_id: gigue_id, + x: 0.0, + y: 0.0, + z: 0.0, + })))) + .await + .unwrap() + .collect::>(); // this should return 1 packet (ItemDrop)? + + println!("packets returned: {:?}", pkts); + assert!(false); +} + +// #[async_std::test] +// async fn test_all_equipped_kill_counters_increase_per_kill() { +// let mut entity_gateway = InMemoryGateway::default(); + +// let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + +// } + +// #[async_std::test] +// async fn test_non_equipped_kill_counter_does_not_increase() { +// let mut entity_gateway = InMemoryGateway::default(); + +// let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + +// } + +// #[async_std::test] +// async fn test_kill_counter_increase_only_for_final_hit() { // don't share kills among players +// let mut entity_gateway = InMemoryGateway::default(); + +// let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + +// }