use std::convert::{TryFrom, Into}; use futures::stream::StreamExt; use async_std::sync::Arc; use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::common::serverstate::ClientId; use crate::common::leveltable::LEVEL_TABLE; use crate::entity::gateway::EntityGateway; use crate::entity::character::SectionID; use crate::entity::room::{NewRoomEntity, RoomEntityMode, RoomNote}; use crate::ship::drops::DropTable; use crate::ship::ship::{SendShipPacket, Clients, ShipEvent}; use crate::ship::room::{Rooms, Episode, Difficulty, RoomState, RoomMode}; use crate::ship::map::Maps; use crate::ship::location::{ClientLocation, RoomId, RoomLobby, GetAreaError}; use crate::ship::packet::builder; use crate::ship::items::state::ItemState; #[allow(clippy::too_many_arguments)] pub async fn create_room(id: ClientId, create_room: CreateRoom, entity_gateway: &mut EG, client_location: &mut ClientLocation, clients: &Clients, item_state: &mut ItemState, rooms: &Rooms, map_builder: Arc Maps + Send + Sync>>, drop_table_builder: Arc DropTable + Send + Sync>>, event: ShipEvent) -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { let level = clients.with(id, |client| Box::pin(async move { LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) })).await?; let difficulty = Difficulty::try_from(create_room.difficulty)?; match difficulty { Difficulty::Ultimate if level < 80 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto create Ultimate rooms.".into())))]) }, Difficulty::VeryHard if level < 40 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 40 \nto create Very Hard rooms.".into())))]) }, Difficulty::Hard if level < 20 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 20 \nto create Hard rooms.".into())))]) }, _ => {}, }; let area = client_location.get_area(id).await?; let old_area_client = client_location.get_local_client(id).await?; let lobby_neighbors = client_location.get_client_neighbors(id).await?; let room_id = client_location.create_new_room(id).await?; let new_area_client = client_location.get_local_client(id).await?; let name = String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).to_string(); let mode = match (create_room.battle, create_room.challenge, create_room.single_player) { (1, 0, 0) => RoomEntityMode::Battle, (0, 1, 0) => RoomEntityMode::Challenge, (0, 0, 1) => RoomEntityMode::Single, _ => RoomEntityMode::Multi, }; let episode = create_room.episode.try_into()?; let difficulty = create_room.difficulty.try_into()?; let room = clients.with(id, |client| { let mut item_state = item_state.clone(); let mut entity_gateway = entity_gateway.clone(); Box::pin(async move { item_state.add_character_to_room(room_id, &client.character, new_area_client).await; let room_entity = entity_gateway.create_room(NewRoomEntity { name: name.clone(), section_id: client.character.section_id, mode, episode, difficulty, }).await?; entity_gateway.add_room_note(room_entity.id, RoomNote::Create { character_id: client.character.id, }).await?; let mut room = RoomState::new(room_entity.id, mode, episode, difficulty, client.character.section_id, name, create_room.password, event, map_builder, drop_table_builder)?; room.bursting = true; Ok::<_, anyhow::Error>(room) })}).await??; let join_room = builder::room::join_room(id, clients, client_location, room_id, &room, event).await?; rooms.add(room_id, room).await?; let mut result = vec![(id, SendShipPacket::JoinRoom(join_room))]; if let Ok(leader) = client_location.get_area_leader(area).await { let leave_lobby = SendShipPacket::LeaveLobby(LeaveLobby::new(old_area_client.local_client.id(), leader.local_client.id())); result.extend(lobby_neighbors .into_iter() .map(move |c| { (c.client, leave_lobby.clone()) })); } Ok(result) } pub async fn room_name_request(id: ClientId, client_location: &ClientLocation, rooms: &Rooms) -> Result, anyhow::Error> { let area = client_location.get_area(id).await?; match area { RoomLobby::Room(room) => { rooms.with(room, |room| Box::pin(async move { vec![(id, SendShipPacket::RoomNameResponse(RoomNameResponse { name: room.name.clone() }))] })).await }, RoomLobby::Lobby(_) => Err(GetAreaError::NotInRoom.into()) } } #[allow(clippy::too_many_arguments)] pub async fn join_room(id: ClientId, pkt: MenuSelect, entity_gateway: &mut EG, client_location: &mut ClientLocation, clients: &Clients, item_state: &mut ItemState, rooms: &Rooms, event: ShipEvent) -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { let room_id = RoomId(pkt.item as usize); if !rooms.exists(room_id).await { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("This room no longer exists!".into())))]) } let level = clients.with(id, |client| Box::pin(async move { LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) })).await?; let (difficulty, bursting, room_entity_id) = rooms.with(room_id, |room| Box::pin(async move { (room.mode.difficulty(), room.bursting, room.room_id) })).await?; match difficulty { Difficulty::Ultimate if level < 80 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto join Ultimate rooms.".into())))]) }, Difficulty::VeryHard if level < 40 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 40 \nto join Very Hard rooms.".into())))]) }, Difficulty::Hard if level < 20 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 20 \nto join Hard rooms.".into())))]) }, _ => {}, } if bursting { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("player is bursting\nplease wait".into())))]) } let original_area = client_location.get_area(id).await?; let original_neighbors = client_location.get_client_neighbors(id).await?; let original_room_clients = client_location.get_clients_in_room(room_id).await?; client_location.add_client_to_room(id, room_id).await?; let area_client = client_location.get_local_client(id).await?; let room_leader = client_location.get_room_leader(room_id).await?; clients.with(id, |client| { let mut item_state = item_state.clone(); let mut entity_gateway = entity_gateway.clone(); Box::pin(async move { entity_gateway.add_room_note(room_entity_id, RoomNote::PlayerJoin { character_id: client.character.id, }).await?; item_state.add_character_to_room(room_id, &client.character, area_client).await; Ok::<_, anyhow::Error>(()) })}).await??; let join_room = rooms.with(room_id, |room| { let clients = clients.clone(); let client_location = client_location.clone(); Box::pin(async move { builder::room::join_room(id, &clients, &client_location, room_id, room, event).await })}).await??; let add_to = clients.with(id, |client| { let item_state = item_state.clone(); Box::pin(async move { builder::room::add_to_room(id, client, &area_client, &room_leader, &item_state, event).await })}).await??; rooms.with_mut(room_id, |room| Box::pin(async move { room.bursting = true; })).await?; Ok(vec![(id, SendShipPacket::JoinRoom(join_room))] .into_iter() .chain(original_room_clients.into_iter() .map(move |c| (c.client, SendShipPacket::AddToRoom(add_to.clone())))) .chain(futures::stream::iter(original_neighbors.into_iter()) .filter_map(|c| { let client_location = client_location.clone(); async move { client_location.get_area_leader(original_area).await.ok().map(|leader| { let leave_lobby = SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id())); (c.client, leave_lobby) }) } }) .collect::>() .await ) .collect()) } pub async fn done_bursting(id: ClientId, client_location: &ClientLocation, rooms: &Rooms) -> Result, anyhow::Error> { let room_id = client_location.get_room(id).await?; let rare_monster_list = rooms.with_mut(room_id, |room| Box::pin(async move { room.bursting = false; room.maps.get_rare_monster_list() })).await?; let area_client = client_location.get_local_client(id).await?; Ok(client_location.get_client_neighbors(id).await?.into_iter() .map(move |client| { (client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone { client: area_client.local_client.id(), target: 0 })))) }) .chain(std::iter::once_with(move || { (id, SendShipPacket::RareMonsterList(builder::room::build_rare_monster_list(rare_monster_list))) })) .collect()) } pub async fn request_room_list(id: ClientId, client_location: &ClientLocation, rooms: &Rooms) -> Vec<(ClientId, SendShipPacket)> { let active_room_list = rooms.stream() .enumerate() .filter_map(|(i, r)| async move { r.as_ref().map(|room| { let difficulty = room.get_difficulty_for_room_list(); let name = libpso::utf8_to_utf16_array!(room.name, 16); let episode = room.get_episode_for_room_list(); let flags = room.get_flags_for_room_list(); async move { RoomList { menu_id: ROOM_MENU_ID, item_id: i as u32, difficulty, players: client_location.get_clients_in_room(RoomId(i)).await.unwrap().len() as u8, name, episode, flags, } }}) }); let baseroom: RoomList = RoomList { menu_id: ROOM_MENU_ID, item_id: ROOM_MENU_ID, difficulty: 0x00, players: 0x00, name: libpso::utf8_to_utf16_array!("Room list menu", 16), episode: 0, flags: 0, }; vec![(id, SendShipPacket::RoomListResponse(RoomListResponse { baseroom, rooms: futures::future::join_all(active_room_list.collect::>().await).await }))] } pub async fn cool_62(id: ClientId, cool_62: Like62ButCooler, client_location: &ClientLocation) -> Result, anyhow::Error> { let target = cool_62.flag as u8; let cool_62 = cool_62.clone(); Ok(client_location .get_client_neighbors(id) .await? .into_iter() .filter(move |client| client.local_client.id() == target) .map(move |client| { (client.client, SendShipPacket::Like62ButCooler(cool_62.clone())) }) .collect()) }