diff -r 4a0b06b03199 -r f4f6060b536c rust/hedgewars-server/src/core/server.rs --- a/rust/hedgewars-server/src/core/server.rs Mon Dec 23 18:55:25 2019 +0300 +++ b/rust/hedgewars-server/src/core/server.rs Mon Dec 23 23:47:06 2019 +0300 @@ -3,7 +3,7 @@ client::HwClient, indexslab::IndexSlab, room::HwRoom, - types::{ClientId, RoomId, ServerVar, TeamInfo}, + types::{ClientId, GameCfg, RoomId, ServerVar, TeamInfo}, }; use crate::{protocol::messages::HwProtocolMessage::Greeting, utils}; @@ -11,7 +11,7 @@ use bitflags::*; use log::*; use slab::Slab; -use std::{borrow::BorrowMut, collections::HashSet, iter, mem::replace}; +use std::{borrow::BorrowMut, cmp::min, collections::HashSet, iter, mem::replace}; #[derive(Debug)] pub enum CreateRoomError { @@ -40,11 +40,6 @@ } #[derive(Debug)] -pub enum LeaveRoomError { - NoRoom, -} - -#[derive(Debug)] pub struct ChangeMasterResult { pub old_master_id: Option, pub new_master_id: ClientId, @@ -80,6 +75,25 @@ } #[derive(Debug)] +pub enum SetTeamCountError { + InvalidNumber, + NotMaster, +} + +#[derive(Debug)] +pub enum SetHedgehogsError { + NoTeam, + InvalidNumber(u8), + NotMaster, +} + +#[derive(Debug)] +pub enum SetConfigError { + NotMaster, + RoomFixed, +} + +#[derive(Debug)] pub enum ModifyRoomNameError { AccessDenied, InvalidName, @@ -198,12 +212,18 @@ } #[inline] - pub fn client_and_room_mut( - &mut self, - client_id: ClientId, - room_id: RoomId, - ) -> (&HwClient, &HwRoom) { - (&self.clients[client_id], &mut self.rooms[room_id]) + fn client_and_room_mut(&mut self, client_id: ClientId) -> Option<(&mut HwClient, &mut HwRoom)> { + let client = &mut self.clients[client_id]; + if let Some(room_id) = client.room_id { + Some((client, &mut self.rooms[room_id])) + } else { + None + } + } + + #[inline] + pub fn get_room_control(&mut self, client_id: ClientId) -> Option { + HwRoomControl::new(self, client_id) } #[inline] @@ -313,226 +333,6 @@ } } - pub fn leave_room(&mut self, client_id: ClientId) -> Result { - let client = &mut self.clients[client_id]; - if let Some(room_id) = client.room_id { - let room = &mut self.rooms[room_id]; - - room.players_number -= 1; - client.room_id = None; - - let is_empty = room.players_number == 0; - let is_fixed = room.is_fixed(); - let was_master = room.master_id == Some(client_id); - let was_in_game = client.is_in_game(); - let mut removed_teams = vec![]; - - if is_empty && !is_fixed { - if client.is_ready() && room.ready_players_number > 0 { - room.ready_players_number -= 1; - } - - removed_teams = room - .client_teams(client.id) - .map(|t| t.name.clone()) - .collect(); - - for team_name in &removed_teams { - room.remove_team(team_name); - } - - if client.is_master() && !is_fixed { - client.set_is_master(false); - room.master_id = None; - } - } - - client.set_is_ready(false); - client.set_is_in_game(false); - - if !is_fixed { - if room.players_number == 0 { - self.rooms.remove(room_id); - } else if room.master_id == None { - let new_master_id = self.room_clients(room_id).next(); - if let Some(new_master_id) = new_master_id { - let room = &mut self.rooms[room_id]; - room.master_id = Some(new_master_id); - let new_master = &mut self.clients[new_master_id]; - new_master.set_is_master(true); - - if room.protocol_number < 42 { - room.name = new_master.nick.clone(); - } - - room.set_join_restriction(false); - room.set_team_add_restriction(false); - room.set_unregistered_players_restriction(true); - } - } - } - - if is_empty && !is_fixed { - Ok(LeaveRoomResult::RoomRemoved) - } else { - Ok(LeaveRoomResult::RoomRemains { - is_empty, - was_master, - was_in_game, - new_master: self.rooms[room_id].master_id, - removed_teams, - }) - } - } else { - Err(LeaveRoomError::NoRoom) - } - } - - pub fn change_master( - &mut self, - client_id: ClientId, - room_id: RoomId, - new_master_nick: String, - ) -> Result { - let client = &mut self.clients[client_id]; - let room = &mut self.rooms[room_id]; - - if client.is_admin() || room.master_id == Some(client_id) { - let new_master_id = self - .clients - .iter() - .find(|(_, c)| c.nick == new_master_nick) - .map(|(id, _)| id); - - match new_master_id { - Some(new_master_id) if new_master_id == client_id => { - Err(ChangeMasterError::AlreadyMaster) - } - Some(new_master_id) => { - let new_master = &mut self.clients[new_master_id]; - if new_master.room_id == Some(room_id) { - self.clients[new_master_id].set_is_master(true); - let old_master_id = room.master_id; - if let Some(master_id) = old_master_id { - self.clients[master_id].set_is_master(false); - } - room.master_id = Some(new_master_id); - Ok(ChangeMasterResult { - old_master_id, - new_master_id, - }) - } else { - Err(ChangeMasterError::ClientNotInRoom) - } - } - None => Err(ChangeMasterError::NoClient), - } - } else { - Err(ChangeMasterError::NoAccess) - } - } - - pub fn start_game(&mut self, room_id: RoomId) -> Result, StartGameError> { - let (room_clients, room_nicks): (Vec<_>, Vec<_>) = self - .clients - .iter() - .map(|(id, c)| (id, c.nick.clone())) - .unzip(); - - let room = &mut self.rooms[room_id]; - - if !room.has_multiple_clans() { - Err(StartGameError::NotEnoughClans) - } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { - Err(StartGameError::NotReady) - } else if room.game_info.is_some() { - Err(StartGameError::AlreadyInGame) - } else { - room.start_round(); - for id in room_clients { - let c = &mut self.clients[id]; - c.set_is_in_game(true); - c.team_indices = room.client_team_indices(c.id); - } - Ok(room_nicks) - } - } - - pub fn leave_game(&mut self, client_id: ClientId) -> Option> { - let client = &mut self.clients[client_id]; - let client_left = client.is_in_game(); - if client_left { - client.set_is_in_game(false); - let room = &mut self.rooms[client.room_id.expect("Client should've been in the game")]; - - let team_names: Vec<_> = room - .client_teams(client_id) - .map(|t| t.name.clone()) - .collect(); - - if let Some(ref mut info) = room.game_info { - info.teams_in_game -= team_names.len() as u8; - - for team_name in &team_names { - let remove_msg = - utils::to_engine_msg(std::iter::once(b'F').chain(team_name.bytes())); - if let Some(m) = &info.sync_msg { - info.msg_log.push(m.clone()); - } - if info.sync_msg.is_some() { - info.sync_msg = None - } - info.msg_log.push(remove_msg); - } - Some(team_names) - } else { - unreachable!(); - } - } else { - None - } - } - - pub fn end_game(&mut self, room_id: RoomId) -> EndGameResult { - let room = &mut self.rooms[room_id]; - room.ready_players_number = room.master_id.is_some() as u8; - - if let Some(info) = replace(&mut room.game_info, None) { - let joined_mid_game_clients = self - .clients - .iter() - .filter(|(_, c)| c.room_id == Some(room_id) && c.is_joined_mid_game()) - .map(|(_, c)| c.id) - .collect(); - - let unreadied_nicks: Vec<_> = self - .clients - .iter_mut() - .filter(|(_, c)| c.room_id == Some(room_id)) - .map(|(_, c)| { - c.set_is_ready(c.is_master()); - c.set_is_joined_mid_game(false); - c - }) - .filter_map(|c| { - if !c.is_master() { - Some(c.nick.clone()) - } else { - None - } - }) - .collect(); - - EndGameResult { - joined_mid_game_clients, - left_teams: info.left_teams.clone(), - unreadied_nicks, - } - } else { - unreachable!() - } - } - pub fn enable_super_power(&mut self, client_id: ClientId) -> bool { let client = &mut self.clients[client_id]; if client.is_admin() { @@ -541,116 +341,6 @@ client.is_admin() } - pub fn set_room_name( - &mut self, - client_id: ClientId, - room_id: RoomId, - mut name: String, - ) -> Result { - let room_exists = self.has_room(&name); - let room = &mut self.rooms[room_id]; - if room.is_fixed() || room.master_id != Some(client_id) { - Err(ModifyRoomNameError::AccessDenied) - } else if utils::is_name_illegal(&name) { - Err(ModifyRoomNameError::InvalidName) - } else if room_exists { - Err(ModifyRoomNameError::DuplicateName) - } else { - std::mem::swap(&mut room.name, &mut name); - Ok(name) - } - } - - pub fn add_team( - &mut self, - client_id: ClientId, - mut info: Box, - ) -> Result<&TeamInfo, AddTeamError> { - let client = &mut self.clients[client_id]; - if let Some(room_id) = client.room_id { - let room = &mut self.rooms[room_id]; - if room.teams.len() >= room.max_teams as usize { - Err(AddTeamError::TooManyTeams) - } else if room.addable_hedgehogs() == 0 { - Err(AddTeamError::TooManyHedgehogs) - } else if room.find_team(|t| t.name == info.name) != None { - Err(AddTeamError::TeamAlreadyExists) - } else if room.game_info.is_some() { - Err(AddTeamError::GameInProgress) - } else if room.is_team_add_restricted() { - Err(AddTeamError::Restricted) - } else { - info.owner = client.nick.clone(); - let team = room.add_team(client.id, *info, client.protocol_number < 42); - client.teams_in_game += 1; - client.clan = Some(team.color); - Ok(team) - } - } else { - unreachable!() - } - } - - pub fn remove_team( - &mut self, - client_id: ClientId, - team_name: &str, - ) -> Result<(), RemoveTeamError> { - let client = &mut self.clients[client_id]; - if let Some(room_id) = client.room_id { - let room = &mut self.rooms[room_id]; - match room.find_team_owner(team_name) { - None => Err(RemoveTeamError::NoTeam), - Some((id, _)) if id != client_id => Err(RemoveTeamError::TeamNotOwned), - Some(_) => { - client.teams_in_game -= 1; - client.clan = room.find_team_color(client.id); - room.remove_team(team_name); - Ok(()) - } - } - } else { - unreachable!(); - } - } - - pub fn set_team_color( - &mut self, - client_id: ClientId, - room_id: RoomId, - team_name: &str, - color: u8, - ) -> Result<(), ModifyTeamError> { - let client = &self.clients[client_id]; - let room = &mut self.rooms[room_id]; - if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { - if !client.is_master() { - Err(ModifyTeamError::NotMaster) - } else { - team.color = color; - self.clients[owner].clan = Some(color); - Ok(()) - } - } else { - Err(ModifyTeamError::NoTeam) - } - } - - pub fn toggle_ready(&mut self, client_id: ClientId) -> bool { - let client = &mut self.clients[client_id]; - if let Some(room_id) = client.room_id { - let room = &mut self.rooms[room_id]; - - client.set_is_ready(!client.is_ready()); - if client.is_ready() { - room.ready_players_number += 1; - } else { - room.ready_players_number -= 1; - } - } - client.is_ready() - } - #[inline] pub fn set_var(&mut self, client_id: ClientId, var: ServerVar) -> Result<(), AccessError> { if self.clients[client_id].is_admin() { @@ -790,6 +480,489 @@ } } +pub struct HwRoomControl<'a> { + server: &'a mut HwServer, + client_id: ClientId, + room_id: RoomId, +} + +impl<'a> HwRoomControl<'a> { + #[inline] + pub fn new(server: &'a mut HwServer, client_id: ClientId) -> Option { + if let Some(room_id) = server.clients[client_id].room_id { + Some(Self { + server, + client_id, + room_id, + }) + } else { + None + } + } + + #[inline] + pub fn server(&self) -> &HwServer { + self.server + } + + #[inline] + pub fn client(&self) -> &HwClient { + &self.server.clients[self.client_id] + } + + #[inline] + fn client_mut(&mut self) -> &mut HwClient { + &mut self.server.clients[self.client_id] + } + + #[inline] + pub fn room(&self) -> &HwRoom { + &self.server.rooms[self.room_id] + } + + #[inline] + fn room_mut(&mut self) -> &mut HwRoom { + &mut self.server.rooms[self.room_id] + } + + #[inline] + pub fn get(&self) -> (&HwClient, &HwRoom) { + (self.client(), self.room()) + } + + #[inline] + fn get_mut(&mut self) -> (&mut HwClient, &mut HwRoom) { + ( + &mut self.server.clients[self.client_id], + &mut self.server.rooms[self.room_id], + ) + } + + pub fn leave_room(&mut self) -> LeaveRoomResult { + let (client, room) = self.get_mut(); + room.players_number -= 1; + client.room_id = None; + + let is_empty = room.players_number == 0; + let is_fixed = room.is_fixed(); + let was_master = room.master_id == Some(client.id); + let was_in_game = client.is_in_game(); + let mut removed_teams = vec![]; + + if is_empty && !is_fixed { + if client.is_ready() && room.ready_players_number > 0 { + room.ready_players_number -= 1; + } + + removed_teams = room + .client_teams(client.id) + .map(|t| t.name.clone()) + .collect(); + + for team_name in &removed_teams { + room.remove_team(team_name); + } + + if client.is_master() && !is_fixed { + client.set_is_master(false); + room.master_id = None; + } + } + + client.set_is_ready(false); + client.set_is_in_game(false); + + if !is_fixed { + if room.players_number == 0 { + self.server.rooms.remove(self.room_id); + } else if room.master_id == None { + let protocol_number = room.protocol_number; + let new_master_id = self.server.room_clients(self.room_id).next(); + + if let Some(new_master_id) = new_master_id { + let room = self.room_mut(); + room.master_id = Some(new_master_id); + let new_master = &mut self.server.clients[new_master_id]; + new_master.set_is_master(true); + + if protocol_number < 42 { + todo!(); + let nick = new_master.nick.clone(); + self.room_mut().name = nick; + } + + let room = self.room_mut(); + room.set_join_restriction(false); + room.set_team_add_restriction(false); + room.set_unregistered_players_restriction(true); + } + } + } + + if is_empty && !is_fixed { + LeaveRoomResult::RoomRemoved + } else { + LeaveRoomResult::RoomRemains { + is_empty, + was_master, + was_in_game, + new_master: self.room().master_id, + removed_teams, + } + } + } + + pub fn change_master( + &mut self, + new_master_nick: String, + ) -> Result { + use ChangeMasterError::*; + let (client, room) = self.get_mut(); + + if client.is_admin() || room.master_id == Some(client.id) { + let new_master_id = self + .server + .clients + .iter() + .find(|(_, c)| c.nick == new_master_nick) + .map(|(id, _)| id); + + match new_master_id { + Some(new_master_id) if new_master_id == self.client_id => Err(AlreadyMaster), + Some(new_master_id) => { + let new_master = &mut self.server.clients[new_master_id]; + if new_master.room_id == Some(self.room_id) { + self.server.clients[new_master_id].set_is_master(true); + let room = self.room_mut(); + let old_master_id = self.room().master_id; + + if let Some(master_id) = old_master_id { + self.server.clients[master_id].set_is_master(false); + } + self.room_mut().master_id = Some(new_master_id); + Ok(ChangeMasterResult { + old_master_id, + new_master_id, + }) + } else { + Err(ClientNotInRoom) + } + } + None => Err(NoClient), + } + } else { + Err(NoAccess) + } + } + + pub fn vote(&mut self) { + todo!("port from the room handler") + } + + pub fn add_engine_message(&mut self) { + todo!("port from the room handler") + } + + pub fn toggle_flag(&mut self, flags: super::room::RoomFlags) -> bool { + let (client, room) = self.get_mut(); + if client.is_master() { + room.flags.toggle(flags); + } + client.is_master() + } + + pub fn fix_room(&mut self) -> Result<(), AccessError> { + let (client, room) = self.get_mut(); + if client.is_admin() { + room.set_is_fixed(true); + room.set_join_restriction(false); + room.set_team_add_restriction(false); + room.set_unregistered_players_restriction(true); + Ok(()) + } else { + Err(AccessError()) + } + } + + pub fn unfix_room(&mut self) -> Result<(), AccessError> { + let (client, room) = self.get_mut(); + if client.is_admin() { + room.set_is_fixed(false); + Ok(()) + } else { + Err(AccessError()) + } + } + + pub fn set_room_name(&mut self, mut name: String) -> Result { + use ModifyRoomNameError::*; + let room_exists = self.server.has_room(&name); + let (client, room) = self.get_mut(); + if room.is_fixed() || room.master_id != Some(client.id) { + Err(AccessDenied) + } else if utils::is_name_illegal(&name) { + Err(InvalidName) + } else if room_exists { + Err(DuplicateName) + } else { + std::mem::swap(&mut room.name, &mut name); + Ok(name) + } + } + + pub fn set_room_greeting(&mut self, greeting: Option) -> Result<(), AccessError> { + let (client, room) = self.get_mut(); + if client.is_admin() { + room.greeting = greeting.unwrap_or(String::new()); + Ok(()) + } else { + Err(AccessError()) + } + } + + pub fn set_room_max_teams(&mut self, count: u8) -> Result<(), SetTeamCountError> { + use SetTeamCountError::*; + let (client, room) = self.get_mut(); + if !client.is_master() { + Err(NotMaster) + } else if !(2..=super::room::MAX_TEAMS_IN_ROOM).contains(&count) { + Err(InvalidNumber) + } else { + room.max_teams = count; + Ok(()) + } + } + + pub fn set_team_hedgehogs_number( + &mut self, + team_name: &str, + number: u8, + ) -> Result<(), SetHedgehogsError> { + use SetHedgehogsError::*; + let (client, room) = self.get_mut(); + let addable_hedgehogs = room.addable_hedgehogs(); + if let Some((_, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { + let max_hedgehogs = min( + super::room::MAX_HEDGEHOGS_IN_ROOM, + addable_hedgehogs + team.hedgehogs_number, + ); + if !client.is_master() { + Err(NotMaster) + } else if !(1..=max_hedgehogs).contains(&number) { + Err(InvalidNumber(team.hedgehogs_number)) + } else { + team.hedgehogs_number = number; + Ok(()) + } + } else { + Err(NoTeam) + } + } + + pub fn add_team(&mut self, mut info: Box) -> Result<&TeamInfo, AddTeamError> { + use AddTeamError::*; + let (client, room) = self.get_mut(); + if room.teams.len() >= room.max_teams as usize { + Err(TooManyTeams) + } else if room.addable_hedgehogs() == 0 { + Err(TooManyHedgehogs) + } else if room.find_team(|t| t.name == info.name) != None { + Err(TeamAlreadyExists) + } else if room.game_info.is_some() { + Err(GameInProgress) + } else if room.is_team_add_restricted() { + Err(Restricted) + } else { + info.owner = client.nick.clone(); + let team = room.add_team(client.id, *info, client.protocol_number < 42); + client.teams_in_game += 1; + client.clan = Some(team.color); + Ok(team) + } + } + + pub fn remove_team(&mut self, team_name: &str) -> Result<(), RemoveTeamError> { + use RemoveTeamError::*; + let (client, room) = self.get_mut(); + match room.find_team_owner(team_name) { + None => Err(NoTeam), + Some((id, _)) if id != client.id => Err(RemoveTeamError::TeamNotOwned), + Some(_) => { + client.teams_in_game -= 1; + client.clan = room.find_team_color(client.id); + room.remove_team(team_name); + Ok(()) + } + } + } + + pub fn set_team_color(&mut self, team_name: &str, color: u8) -> Result<(), ModifyTeamError> { + use ModifyTeamError::*; + let (client, room) = self.get_mut(); + if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { + if !client.is_master() { + Err(NotMaster) + } else { + team.color = color; + self.server.clients[owner].clan = Some(color); + Ok(()) + } + } else { + Err(NoTeam) + } + } + + pub fn set_config(&mut self, cfg: GameCfg) -> Result<(), SetConfigError> { + use SetConfigError::*; + let (client, room) = self.get_mut(); + if room.is_fixed() { + Err(RoomFixed) + } else if !client.is_master() { + Err(NotMaster) + } else { + let cfg = match cfg { + GameCfg::Scheme(name, mut values) => { + if client.protocol_number == 49 && values.len() >= 2 { + let mut s = "X".repeat(50); + s.push_str(&values.pop().unwrap()); + values.push(s); + } + GameCfg::Scheme(name, values) + } + cfg => cfg, + }; + + room.set_config(cfg); + Ok(()) + } + } + + pub fn save_config(&mut self, name: String, location: String) { + self.room_mut().save_config(name, location); + } + + pub fn delete_config(&mut self, name: &str) -> bool { + self.room_mut().delete_config(name) + } + + pub fn toggle_ready(&mut self) -> bool { + let (client, room) = self.get_mut(); + client.set_is_ready(!client.is_ready()); + if client.is_ready() { + room.ready_players_number += 1; + } else { + room.ready_players_number -= 1; + } + client.is_ready() + } + + pub fn start_game(&mut self) -> Result, StartGameError> { + use StartGameError::*; + let (room_clients, room_nicks): (Vec<_>, Vec<_>) = self + .server + .clients + .iter() + .map(|(id, c)| (id, c.nick.clone())) + .unzip(); + + let room = self.room_mut(); + + if !room.has_multiple_clans() { + Err(NotEnoughClans) + } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { + Err(NotReady) + } else if room.game_info.is_some() { + Err(AlreadyInGame) + } else { + room.start_round(); + for id in room_clients { + let team_indices = self.room().client_team_indices(id); + let c = &mut self.server.clients[id]; + c.set_is_in_game(true); + c.team_indices = team_indices; + } + Ok(room_nicks) + } + } + + pub fn leave_game(&mut self) -> Option> { + let (client, room) = self.get_mut(); + let client_left = client.is_in_game(); + if client_left { + client.set_is_in_game(false); + + let team_names: Vec<_> = room + .client_teams(client.id) + .map(|t| t.name.clone()) + .collect(); + + if let Some(ref mut info) = room.game_info { + info.teams_in_game -= team_names.len() as u8; + + for team_name in &team_names { + let remove_msg = + utils::to_engine_msg(std::iter::once(b'F').chain(team_name.bytes())); + if let Some(m) = &info.sync_msg { + info.msg_log.push(m.clone()); + } + if info.sync_msg.is_some() { + info.sync_msg = None + } + info.msg_log.push(remove_msg); + } + Some(team_names) + } else { + unreachable!(); + } + } else { + None + } + } + + pub fn end_game(&mut self) -> Option { + let room = self.room_mut(); + room.ready_players_number = room.master_id.is_some() as u8; + + if let Some(info) = replace(&mut room.game_info, None) { + let room_id = room.id; + let joined_mid_game_clients = self + .server + .clients + .iter() + .filter(|(_, c)| c.room_id == Some(self.room_id) && c.is_joined_mid_game()) + .map(|(_, c)| c.id) + .collect(); + + let unreadied_nicks: Vec<_> = self + .server + .clients + .iter_mut() + .filter(|(_, c)| c.room_id == Some(room_id)) + .map(|(_, c)| { + c.set_is_ready(c.is_master()); + c.set_is_joined_mid_game(false); + c + }) + .filter_map(|c| { + if !c.is_master() { + Some(c.nick.clone()) + } else { + None + } + }) + .collect(); + + Some(EndGameResult { + joined_mid_game_clients, + left_teams: info.left_teams.clone(), + unreadied_nicks, + }) + } else { + None + } + } +} + fn allocate_room(rooms: &mut Slab) -> &mut HwRoom { let entry = rooms.vacant_entry(); let room = HwRoom::new(entry.key());