diff --git a/src/ship/map/area.rs b/src/ship/map/area.rs index 7989cba..eb6bb68 100644 --- a/src/ship/map/area.rs +++ b/src/ship/map/area.rs @@ -3,6 +3,7 @@ use serde::{Serialize, Deserialize}; use std::collections::HashMap; use thiserror::Error; use crate::ship::room::Episode; +use std::fmt; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum MapArea { @@ -256,6 +257,60 @@ impl MapArea { MapArea::SaintMillion => Episode::Four, } } + + pub fn as_string(&self) -> &str{ + match self { + MapArea::Pioneer2Ep1 => "Pioneer 2", + MapArea::Forest1 => "Forest 1", + MapArea::Forest2 => "Forest 2", + MapArea::Caves1 => "Caves 1", + MapArea::Caves2 => "Caves 2", + MapArea::Caves3 => "Caves 3", + MapArea::Mines1 => "Mines 1", + MapArea::Mines2 => "Mines 2", + MapArea::Ruins1 => "Ruins 1", + MapArea::Ruins2 => "Ruins 2", + MapArea::Ruins3 => "Ruins 3", + MapArea::Dragon => "Dragon", + MapArea::DeRolLe => "De Rol Le", + MapArea::VolOpt => "Vol Opt", + MapArea::DarkFalz => "Dark Falz", + MapArea::Pioneer2Ep2 => "Pioneer 2", + MapArea::VrTempleAlpha => "Vr Temple Alpha", + MapArea::VrTempleBeta => "Vr Temple Beta", + MapArea::VrSpaceshipAlpha => "Vr Spaceship Alpha", + MapArea::VrSpaceshipBeta => "Vr Spaceship Beta", + MapArea::Cca => "CCA", + MapArea::JungleAreaNorth => "Jungle Area North", + MapArea::JungleAreaEast => "Jungle Area East", + MapArea::Mountain => "Mountain", + MapArea::Seaside => "Seaside", + MapArea::SeabedUpper => "Seabed Upper", + MapArea::SeabedLower => "Seabed Lower", + MapArea::GalGryphon => "Gal Gryphon", + MapArea::OlgaFlow => "Olga Flow", + MapArea::BarbaRay => "Barba Ray", + MapArea::GolDragon => "Gol Dragon", + MapArea::SeasideNight => "Seaside Night", + MapArea::Tower => "Tower", + MapArea::Pioneer2Ep4 => "Pioneer 2", + MapArea::CraterEast => "Crater East", + MapArea::CraterWest => "Crater West", + MapArea::CraterSouth => "Crater South", + MapArea::CraterNorth => "Crater North", + MapArea::CraterInterior => "Crater Interior", + MapArea::SubDesert1 => "Sub Desert 1", + MapArea::SubDesert2 => "Sub Desert 2", + MapArea::SubDesert3 => "Sub Desert 3", + MapArea::SaintMillion => "Saint Million", + } + } +} + +impl fmt::Display for MapArea { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_string()) + } } diff --git a/src/ship/packet/handler/lobby.rs b/src/ship/packet/handler/lobby.rs index 50839fd..2e1625c 100644 --- a/src/ship/packet/handler/lobby.rs +++ b/src/ship/packet/handler/lobby.rs @@ -3,11 +3,11 @@ use crate::common::serverstate::ClientId; use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms}; use crate::ship::character::{FullCharacterBytesBuilder}; -use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationError}; -//use crate::ship::items::; +use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationError, RoomId}; use crate::ship::packet; use crate::ship::items::state::ItemState; use crate::entity::gateway::EntityGateway; +use crate::ship::map::MapArea; // this function needs a better home pub fn block_selected(id: ClientId, @@ -129,3 +129,30 @@ pub fn remove_from_lobby(id: ClientId, (n.client, leave_lobby_pkt.clone()) }).collect()) } + +pub fn get_room_tab_info(id: ClientId, + pkt: &MenuDetail, + client_location: &mut ClientLocation, + clients: &Clients, + rooms: &mut Rooms) + -> Result, anyhow::Error> { + let room_id = RoomId(pkt.item as usize); + if let Some(_room) = rooms.get(pkt.item as usize).ok_or(ShipError::InvalidRoom(pkt.item))? { + let mut room_info = String::new(); + let clients_in_room = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + for client in clients_in_room { + let cs = clients.get(&client.client).ok_or(ShipError::ClientNotFound(client.client))?; + let gc = cs.user.guildcard; + let name = &cs.character.name; + let cc = cs.character.char_class; + let leveltable = CharacterLevelTable::default(); + let lv = leveltable.get_level_from_exp(cc, cs.character.exp); + let floor = cs.area.unwrap_or(MapArea::Pioneer2Ep1); + + room_info += format!("{} Lv{} {}\n{} {}\n", gc,lv,name,cc,floor).as_str(); + } + Ok(vec![(id, SendShipPacket::SmallLeftDialog(SmallLeftDialog::new(room_info)))]) + } else { + Ok(vec![(id, SendShipPacket::SmallLeftDialog(SmallLeftDialog::new("Game is no longer active".into())))]) + } +} diff --git a/src/ship/packet/handler/room.rs b/src/ship/packet/handler/room.rs index bb04c3b..9d5eb57 100644 --- a/src/ship/packet/handler/room.rs +++ b/src/ship/packet/handler/room.rs @@ -86,62 +86,66 @@ pub fn join_room(id: ClientId, -> Result + Send>, ShipError> { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let level = level_table.get_level_from_exp(client.character.char_class, client.character.exp); - let room = rooms.get(pkt.item as usize).ok_or(ShipError::InvalidRoom(pkt.item))?.as_ref().unwrap(); // clippy look what you made me do + // let room = rooms.get(pkt.item as usize).ok_or(ShipError::InvalidRoom(pkt.item))?.as_ref().unwrap(); // clippy look what you made me do + if let Some(room) = rooms.get(pkt.item as usize).ok_or(ShipError::InvalidRoom(pkt.item))? { - match room.mode.difficulty() { - room::Difficulty::Ultimate => { - if level < 80 { - return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto join Ultimate rooms.".into())))].into_iter())) - } - }, - room::Difficulty::VeryHard => { - if level < 40 { - return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 40 \nto join Very Hard rooms.".into())))].into_iter())) - } - }, - room::Difficulty::Hard => { - if level < 20 { - return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 20 \nto join Hard rooms.".into())))].into_iter())) - } - }, - _ => {}, - }; - - let original_area = client_location.get_area(id).unwrap(); - let original_neighbors = client_location.get_client_neighbors(id).unwrap(); - if room.bursting { - return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("player is bursting\nplease wait".into())))].into_iter())) - } - let room_id = RoomId(pkt.item as usize); - let original_room_clients = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; - client_location.add_client_to_room(id, room_id).unwrap(); // TODO: show room full error or whatever - - let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; - - item_state.add_character_to_room(room_id, &client.character, area_client); - - let leader = client_location.get_room_leader(room_id).map_err(|err| -> ClientLocationError { err.into() })?; - let join_room = builder::room::join_room(id, clients, client_location, room_id, room)?; - let add_to = builder::room::add_to_room(id, client, &area_client, &leader, item_state, level_table, room_id)?; - - let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap(); - room.bursting = true; - - let mut result: Box + Send> = Box::new( - vec![(id, SendShipPacket::JoinRoom(join_room))] - .into_iter() - .chain(original_room_clients.into_iter() - .map(move |c| (c.client, SendShipPacket::AddToRoom(add_to.clone()))) - )); + match room.mode.difficulty() { + room::Difficulty::Ultimate => { + if level < 80 { + return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto join Ultimate rooms.".into())))].into_iter())) + } + }, + room::Difficulty::VeryHard => { + if level < 40 { + return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 40 \nto join Very Hard rooms.".into())))].into_iter())) + } + }, + room::Difficulty::Hard => { + if level < 20 { + return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 20 \nto join Hard rooms.".into())))].into_iter())) + } + }, + _ => {}, + }; - if let Ok(leader) = client_location.get_area_leader(original_area) { - let leave_lobby = SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id())); - result = Box::new(result.chain(original_neighbors.into_iter() - .map(move |c| (c.client, leave_lobby.clone())))) + let original_area = client_location.get_area(id).unwrap(); + let original_neighbors = client_location.get_client_neighbors(id).unwrap(); + if room.bursting { + return Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("player is bursting\nplease wait".into())))].into_iter())) + } + let room_id = RoomId(pkt.item as usize); + let original_room_clients = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + client_location.add_client_to_room(id, room_id).unwrap(); // TODO: show room full error or whatever + + let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; + let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; + + item_state.add_character_to_room(room_id, &client.character, area_client); + + let leader = client_location.get_room_leader(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + let join_room = builder::room::join_room(id, clients, client_location, room_id, room)?; + let add_to = builder::room::add_to_room(id, client, &area_client, &leader, item_state, level_table, room_id)?; + + let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap(); + room.bursting = true; + + let mut result: Box + Send> = Box::new( + vec![(id, SendShipPacket::JoinRoom(join_room))] + .into_iter() + .chain(original_room_clients.into_iter() + .map(move |c| (c.client, SendShipPacket::AddToRoom(add_to.clone()))) + )); + + if let Ok(leader) = client_location.get_area_leader(original_area) { + let leave_lobby = SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id())); + result = Box::new(result.chain(original_neighbors.into_iter() + .map(move |c| (c.client, leave_lobby.clone())))) + } + + Ok(result) + } else { + Ok(Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Game is no longer active".into())))].into_iter())) } - - Ok(result) } pub fn done_bursting(id: ClientId, diff --git a/src/ship/ship.rs b/src/ship/ship.rs index c1ec3d8..773be42 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -205,6 +205,7 @@ pub enum SendShipPacket { DirectMessage(DirectMessage), PlayerChat(PlayerChat), SmallDialog(SmallDialog), + SmallLeftDialog(SmallLeftDialog), JoinRoom(JoinRoom), AddToRoom(AddToRoom), LeaveLobby(LeaveLobby), @@ -246,6 +247,7 @@ impl SendServerPacket for SendShipPacket { SendShipPacket::DirectMessage(pkt) => pkt.as_bytes(), SendShipPacket::PlayerChat(pkt) => pkt.as_bytes(), SendShipPacket::SmallDialog(pkt) => pkt.as_bytes(), + SendShipPacket::SmallLeftDialog(pkt) => pkt.as_bytes(), SendShipPacket::JoinRoom(pkt) => pkt.as_bytes(), SendShipPacket::AddToRoom(pkt) => pkt.as_bytes(), SendShipPacket::LeaveLobby(pkt) => pkt.as_bytes(), @@ -665,9 +667,9 @@ impl ServerState for ShipServerState { let block = self.blocks.with_client(id, &self.clients)?; handler::quest::player_chose_quest(id, questmenuselect, &mut self.clients, &block.client_location, &mut block.rooms)? }, - RecvShipPacket::MenuDetail(_menudetail) => { - //unreachable!(); - Box::new(Vec::new().into_iter()) + RecvShipPacket::MenuDetail(menudetail) => { + let block = self.blocks.with_client(id, &self.clients)?; + Box::new(handler::lobby::get_room_tab_info(id, menudetail, &mut block.client_location, &self.clients, &mut block.rooms)?.into_iter()) }, RecvShipPacket::RoomPasswordReq(room_password_req) => { let block = self.blocks.with_client(id, &self.clients)?; diff --git a/tests/test_rooms.rs b/tests/test_rooms.rs index f435c45..96311fe 100644 --- a/tests/test_rooms.rs +++ b/tests/test_rooms.rs @@ -156,4 +156,71 @@ async fn test_set_invalid_quest_group() { }, _ => panic!("Wrong quest category"), } -} \ No newline at end of file +} + +#[async_std::test] +async fn test_get_room_info() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, mut _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + _char1.name = String::from("GODmar"); + entity_gateway.save_character(&_char1).await.unwrap(); + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + let _expectedmsg = String::from("1 Lv1 GODmar\nHUmar Pioneer 2\n"); + let packets = ship.handle(ClientId(2), &RecvShipPacket::MenuDetail(MenuDetail{menu: 3, item: 0})).await.unwrap().collect::>(); + assert!(matches!(&packets[0], (ClientId(2), SendShipPacket::SmallLeftDialog(SmallLeftDialog{ + padding: [17664, 1157645568], + msg: _expectedmsg, + })))); +} + +#[async_std::test] +async fn test_cannot_get_room_info_after_room_is_closed() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + leave_room(&mut ship, ClientId(1)).await; + let _expectedmsg = String::from("Game is no longer active!\0"); + let packets = ship.handle(ClientId(2), &RecvShipPacket::MenuDetail(MenuDetail{menu: 3, item: 0})).await.unwrap().collect::>(); + assert!(matches!(&packets[0], (ClientId(2), SendShipPacket::SmallLeftDialog(SmallLeftDialog{ + padding: [17664, 1157645568], + msg: _expectedmsg, + })))); +} + +#[async_std::test] +async fn test_cannot_join_room_after_its_closed() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + leave_room(&mut ship, ClientId(1)).await; + let _expectedmsg = String::from("This room no longer exists!\0"); + let packets = ship.handle(ClientId(2), &RecvShipPacket::MenuSelect(MenuSelect{menu: 3, item: 0})).await.unwrap().collect::>(); + assert!(matches!(&packets[0], (ClientId(2), SendShipPacket::SmallDialog(SmallDialog{ + padding: [0,0], + msg: _expectedmsg, // wow yes cool rust is so great literally the best i can't put a String::from() directly in here. + })))); +}