# HG changeset patch # User alfadur # Date 1576960430 -10800 # Node ID b3157d218ae23432bb6770656329da28a1cbd045 # Parent e705d30e0f1082960bd41b31655af8476630881b disallow mutable clients to leave the server diff -r e705d30e0f10 -r b3157d218ae2 rust/hedgewars-server/src/core/server.rs --- a/rust/hedgewars-server/src/core/server.rs Sat Dec 21 00:26:17 2019 +0300 +++ b/rust/hedgewars-server/src/core/server.rs Sat Dec 21 23:33:50 2019 +0300 @@ -2,10 +2,11 @@ client::HwClient, indexslab::IndexSlab, room::HwRoom, - types::{ClientId, RoomId, ServerVar}, + types::{ClientId, RoomId, ServerVar, TeamInfo}, }; use crate::{protocol::messages::HwProtocolMessage::Greeting, utils}; +use bitflags::_core::hint::unreachable_unchecked; use bitflags::*; use chrono::{offset, DateTime}; use log::*; @@ -58,6 +59,21 @@ } #[derive(Debug)] +pub enum AddTeamError { + TooManyTeams, + TooManyHedgehogs, + TeamAlreadyExists, + GameInProgress, + Restricted, +} + +#[derive(Debug)] +pub enum RemoveTeamError { + NoTeam, + TeamNotOwned, +} + +#[derive(Debug)] pub enum ModifyTeamError { NoTeam, NotMaster, @@ -229,6 +245,16 @@ } #[inline] + pub fn has_client(&self, client_id: ClientId) -> bool { + self.clients.contains(client_id) + } + + #[inline] + pub fn iter_clients(&self) -> impl Iterator { + self.clients.iter().map(|(_, c)| c) + } + + #[inline] pub fn room(&self, room_id: RoomId) -> &HwRoom { &self.rooms[room_id] } @@ -248,8 +274,8 @@ &mut self, client_id: ClientId, room_id: RoomId, - ) -> (&mut HwClient, &mut HwRoom) { - (&mut self.clients[client_id], &mut self.rooms[room_id]) + ) -> (&HwClient, &mut HwRoom) { + (&self.clients[client_id], &mut self.rooms[room_id]) } #[inline] @@ -504,9 +530,44 @@ } } + 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 = 1; + 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 @@ -572,7 +633,58 @@ } } - pub fn add_team(&mut self, client_id: ClientId) {} + 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, diff -r e705d30e0f10 -r b3157d218ae2 rust/hedgewars-server/src/handlers.rs --- a/rust/hedgewars-server/src/handlers.rs Sat Dec 21 00:26:17 2019 +0300 +++ b/rust/hedgewars-server/src/handlers.rs Sat Dec 21 23:33:50 2019 +0300 @@ -259,7 +259,7 @@ response.remove_client(client_id); } } - } else if server.clients.contains(client_id) { + } else if server.has_client(client_id) { match message { HwProtocolMessage::Quit(Some(msg)) => { common::remove_client(server, response, "User quit: ".to_string() + &msg); diff -r e705d30e0f10 -r b3157d218ae2 rust/hedgewars-server/src/handlers/common.rs --- a/rust/hedgewars-server/src/handlers/common.rs Sat Dec 21 00:26:17 2019 +0300 +++ b/rust/hedgewars-server/src/handlers/common.rs Sat Dec 21 23:33:50 2019 +0300 @@ -392,7 +392,7 @@ }; get_room_update(None, room, room_master, response); - for (_, client) in server.clients.iter() { + for client in server.iter_clients() { if client.room_id == Some(room_id) { super::common::get_room_config(&server.rooms[room_id], client.id, response); } diff -r e705d30e0f10 -r b3157d218ae2 rust/hedgewars-server/src/handlers/inroom.rs --- a/rust/hedgewars-server/src/handlers/inroom.rs Sat Dec 21 00:26:17 2019 +0300 +++ b/rust/hedgewars-server/src/handlers/inroom.rs Sat Dec 21 23:33:50 2019 +0300 @@ -1,4 +1,6 @@ use super::{common::rnd_reply, strings::*}; +use crate::core::room::GameInfo; +use crate::core::server::AddTeamError; use crate::{ core::{ room::{HwRoom, RoomFlags, MAX_TEAMS_IN_ROOM}, @@ -206,76 +208,69 @@ super::common::get_start_game_data(server, room_id, result, response); } } - AddTeam(mut info) => { - if room.teams.len() >= room.max_teams as usize { - response.warn("Too many teams!"); - } else if room.addable_hedgehogs() == 0 { - response.warn("Too many hedgehogs!"); - } else if room.find_team(|t| t.name == info.name) != None { - response.warn("There's already a team with same name in the list."); - } else if room.game_info.is_some() { - response.warn("Joining not possible: Round is in progress."); - } else if room.is_team_add_restricted() { - response.warn("This room currently does not allow adding new teams."); - } 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); - response.add(TeamAccepted(team.name.clone()).send_self()); - response.add( - TeamAdd(team.to_protocol()) - .send_all() - .in_room(room_id) - .but_self(), - ); - response.add( - TeamColor(team.name.clone(), team.color) - .send_all() - .in_room(room_id), - ); - response.add( - HedgehogsNumber(team.name.clone(), team.hedgehogs_number) - .send_all() - .in_room(room_id), - ); + AddTeam(info) => { + use crate::core::server::AddTeamError; + match server.add_team(client_id, info) { + Ok(team) => { + response.add(TeamAccepted(team.name.clone()).send_self()); + response.add( + TeamAdd(team.to_protocol()) + .send_all() + .in_room(room_id) + .but_self(), + ); + response.add( + TeamColor(team.name.clone(), team.color) + .send_all() + .in_room(room_id), + ); + response.add( + HedgehogsNumber(team.name.clone(), team.hedgehogs_number) + .send_all() + .in_room(room_id), + ); - let room = server.room(room_id); - let room_master = if let Some(id) = room.master_id { - Some(server.client(id)) - } else { - None - }; - super::common::get_room_update(None, room, room_master, response); + let room = server.room(room_id); + let room_master = if let Some(id) = room.master_id { + Some(server.client(id)) + } else { + None + }; + super::common::get_room_update(None, room, room_master, response); + } + Err(AddTeamError::TooManyTeams) => response.warn(TOO_MANY_TEAMS), + Err(AddTeamError::TooManyHedgehogs) => response.warn(TOO_MANY_HEDGEHOGS), + Err(AddTeamError::TeamAlreadyExists) => response.warn(TEAM_EXISTS), + Err(AddTeamError::GameInProgress) => response.warn(ROUND_IN_PROGRESS), + Err(AddTeamError::Restricted) => response.warn(TEAM_ADD_RESTRICTED), } } - RemoveTeam(name) => match room.find_team_owner(&name) { - None => response.warn("Error: The team you tried to remove does not exist."), - Some((id, _)) if id != client_id => { - response.warn("You can't remove a team you don't own.") + RemoveTeam(name) => { + use crate::core::server::RemoveTeamError; + match server.remove_team(client_id, &name) { + Ok(()) => { + let (client, room) = server.client_and_room(client_id, room_id); + + let removed_teams = vec![name]; + super::common::get_remove_teams_data( + room_id, + client.is_in_game(), + removed_teams, + response, + ); + + match room.game_info { + Some(ref info) if info.teams_in_game == 0 => { + let result = server.end_game(room_id); + super::common::get_end_game_result(server, room_id, result, response); + } + _ => (), + } + } + Err(RemoveTeamError::NoTeam) => response.warn(NO_TEAM_TO_REMOVE), + Err(RemoveTeamError::TeamNotOwned) => response.warn(TEAM_NOT_OWNED), } - Some((_, name)) => { - let name = name.to_string(); - client.teams_in_game -= 1; - client.clan = room.find_team_color(client.id); - room.remove_team(&name); - let removed_teams = vec![name]; - super::common::get_remove_teams_data( - room_id, - client.is_in_game(), - removed_teams, - response, - ); - - match room.game_info { - Some(ref info) if info.teams_in_game == 0 => { - let result = server.end_game(room_id); - super::common::get_end_game_result(server, room_id, result, response); - } - _ => (), - } - } - }, + } SetHedgehogsNumber(team_name, number) => { let addable_hedgehogs = room.addable_hedgehogs(); if let Some((_, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { @@ -510,54 +505,31 @@ } } RoundFinished => { - let mut game_ended = false; - if client.is_in_game() { - client.set_is_in_game(false); + if let Some(team_names) = server.leave_game(client_id) { + let (client, room) = server.client_and_room(client_id, room_id); response.add( ClientFlags(remove_flags(&[Flags::InGame]), vec![client.nick.clone()]) .send_all() .in_room(room.id), ); - 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; - if info.teams_in_game == 0 { - game_ended = true; - } - - for team_name in team_names { - let msg = once(b'F').chain(team_name.bytes()); - response.add( - ForwardEngineMessage(vec![to_engine_msg(msg)]) - .send_all() - .in_room(room_id) - .but_self(), - ); - let remove_msg = to_engine_msg(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.clone()); - response.add( - ForwardEngineMessage(vec![remove_msg]) - .send_all() - .in_room(room_id) - .but_self(), - ); - } + for team_name in team_names { + let msg = once(b'F').chain(team_name.bytes()); + response.add( + ForwardEngineMessage(vec![to_engine_msg(msg)]) + .send_all() + .in_room(room_id) + .but_self(), + ); } - } - if game_ended { - let result = server.end_game(room_id); - super::common::get_end_game_result(server, room_id, result, response); + + if let Some(GameInfo { + teams_in_game: 0, .. + }) = room.game_info + { + let result = server.end_game(room_id); + super::common::get_end_game_result(server, room_id, result, response); + } } } Rnd(v) => { diff -r e705d30e0f10 -r b3157d218ae2 rust/hedgewars-server/src/handlers/strings.rs --- a/rust/hedgewars-server/src/handlers/strings.rs Sat Dec 21 00:26:17 2019 +0300 +++ b/rust/hedgewars-server/src/handlers/strings.rs Sat Dec 21 23:33:50 2019 +0300 @@ -3,6 +3,7 @@ pub const ILLEGAL_ROOM_NAME: &str = "Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}"; pub const NO_ROOM: &str = "No such room."; pub const NO_TEAM: &str = "No such team."; +pub const NO_TEAM_TO_REMOVE: &str = "Error: The team you tried to remove does not exist."; pub const NO_USER: &str = "No such user."; pub const NOT_MASTER: &str = "You're not the room master!"; pub const REPLAY_LOAD_FAILED: &str = "Could't load the replay"; @@ -19,7 +20,13 @@ pub const ROOM_EXISTS: &str = "A room with the same name already exists."; pub const ROOM_FULL: &str = "This room is already full."; pub const ROOM_JOIN_RESTRICTED: &str = "Access denied. This room currently doesn't allow joining."; +pub const ROUND_IN_PROGRESS: &str = "Joining not possible: Round is in progress."; pub const SUPER_POWER: &str = "Super power activated."; +pub const TEAM_EXISTS: &str = "There's already a team with same name in the list."; +pub const TEAM_NOT_OWNED: &str = "You can't remove a team you don't own."; +pub const TEAM_ADD_RESTRICTED: &str = "This room currently does not allow adding new teams."; +pub const TOO_MANY_HEDGEHOGS: &str = "Too many hedgehogs!"; +pub const TOO_MANY_TEAMS: &str = "Too many teams!"; pub const USER_OFFLINE: &str = "Player is not online."; pub const VARIABLE_UPDATED: &str = "Server variable has been updated."; pub const WRONG_PROTOCOL: &str = "Room version incompatible to your Hedgewars version!";