# HG changeset patch # User alfadur # Date 1559059458 -10800 # Node ID c5a6e85664254169956bb9ca5c304dc773959a43 # Parent 7732013ce64c478c3e9d4d3d528bab1057cef4e9 shuffle server files diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/core.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,5 @@ +pub mod types; +pub mod indexslab; +pub mod client; +pub mod room; +pub mod server; \ No newline at end of file diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/core/client.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core/client.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,109 @@ +use super::types::ClientId; +use bitflags::*; + +bitflags! { + pub struct ClientFlags: u16 { + const IS_ADMIN = 0b0000_0001; + const IS_MASTER = 0b0000_0010; + const IS_READY = 0b0000_0100; + const IS_IN_GAME = 0b0000_1000; + const IS_JOINED_MID_GAME = 0b0001_0000; + const IS_CHECKER = 0b0010_0000; + const IS_CONTRIBUTOR = 0b0100_0000; + const HAS_SUPER_POWER = 0b1000_0000; + const IS_REGISTERED = 0b0001_0000_0000; + + const NONE = 0b0000_0000; + const DEFAULT = Self::NONE.bits; + } +} + +pub struct HWClient { + pub id: ClientId, + pub room_id: Option, + pub nick: String, + pub protocol_number: u16, + pub flags: ClientFlags, + pub teams_in_game: u8, + pub team_indices: Vec, + pub clan: Option, +} + +impl HWClient { + pub fn new(id: ClientId, protocol_number: u16, nick: String) -> HWClient { + HWClient { + id, + nick, + protocol_number, + room_id: None, + flags: ClientFlags::DEFAULT, + teams_in_game: 0, + team_indices: Vec::new(), + clan: None, + } + } + + fn contains(&self, mask: ClientFlags) -> bool { + self.flags.contains(mask) + } + + fn set(&mut self, mask: ClientFlags, value: bool) { + self.flags.set(mask, value); + } + + pub fn is_admin(&self) -> bool { + self.contains(ClientFlags::IS_ADMIN) + } + pub fn is_master(&self) -> bool { + self.contains(ClientFlags::IS_MASTER) + } + pub fn is_ready(&self) -> bool { + self.contains(ClientFlags::IS_READY) + } + pub fn is_in_game(&self) -> bool { + self.contains(ClientFlags::IS_IN_GAME) + } + pub fn is_joined_mid_game(&self) -> bool { + self.contains(ClientFlags::IS_JOINED_MID_GAME) + } + pub fn is_checker(&self) -> bool { + self.contains(ClientFlags::IS_CHECKER) + } + pub fn is_contributor(&self) -> bool { + self.contains(ClientFlags::IS_CONTRIBUTOR) + } + pub fn has_super_power(&self) -> bool { + self.contains(ClientFlags::HAS_SUPER_POWER) + } + pub fn is_registered(&self) -> bool { + self.contains(ClientFlags::IS_REGISTERED) + } + + pub fn set_is_admin(&mut self, value: bool) { + self.set(ClientFlags::IS_ADMIN, value) + } + pub fn set_is_master(&mut self, value: bool) { + self.set(ClientFlags::IS_MASTER, value) + } + pub fn set_is_ready(&mut self, value: bool) { + self.set(ClientFlags::IS_READY, value) + } + pub fn set_is_in_game(&mut self, value: bool) { + self.set(ClientFlags::IS_IN_GAME, value) + } + pub fn set_is_joined_mid_game(&mut self, value: bool) { + self.set(ClientFlags::IS_JOINED_MID_GAME, value) + } + pub fn set_is_checker(&mut self, value: bool) { + self.set(ClientFlags::IS_CHECKER, value) + } + pub fn set_is_contributor(&mut self, value: bool) { + self.set(ClientFlags::IS_CONTRIBUTOR, value) + } + pub fn set_has_super_power(&mut self, value: bool) { + self.set(ClientFlags::HAS_SUPER_POWER, value) + } + pub fn set_is_registered(&mut self, value: bool) { + self.set(ClientFlags::IS_REGISTERED, value) + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/core/indexslab.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core/indexslab.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,71 @@ +use std::{ + iter, + mem::replace, + ops::{Index, IndexMut}, +}; + +pub struct IndexSlab { + data: Vec>, +} + +impl IndexSlab { + pub fn new() -> Self { + Self { data: Vec::new() } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + data: Vec::with_capacity(capacity), + } + } + + pub fn insert(&mut self, index: usize, value: T) { + if index >= self.data.len() { + self.data.reserve(index - self.data.len() + 1); + self.data.extend((self.data.len()..index).map(|_| None)); + self.data.push(Some(value)) + } else { + self.data[index] = Some(value); + } + } + + pub fn contains(&self, index: usize) -> bool { + self.data.get(index).and_then(|x| x.as_ref()).is_some() + } + + pub fn remove(&mut self, index: usize) -> Option { + if let Some(x) = self.data.get_mut(index) { + replace(x, None) + } else { + None + } + } + + pub fn iter(&self) -> impl Iterator { + self.data + .iter() + .enumerate() + .filter_map(|(index, opt)| opt.as_ref().and_then(|x| Some((index, x)))) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.data + .iter_mut() + .enumerate() + .filter_map(|(index, opt)| opt.as_mut().and_then(|x| Some((index, x)))) + } +} + +impl Index for IndexSlab { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.data[index].as_ref().unwrap() + } +} + +impl IndexMut for IndexSlab { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.data[index].as_mut().unwrap() + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/core/room.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core/room.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,349 @@ +use super::{ + client::HWClient, + types::{ + ClientId, GameCfg, GameCfg::*, RoomConfig, RoomId, TeamInfo, Voting, MAX_HEDGEHOGS_PER_TEAM, + }, +}; +use bitflags::*; +use serde::{Deserialize, Serialize}; +use serde_derive::{Deserialize, Serialize}; +use serde_yaml; +use std::{collections::HashMap, iter}; + +pub const MAX_TEAMS_IN_ROOM: u8 = 8; +pub const MAX_HEDGEHOGS_IN_ROOM: u8 = MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM; + +fn client_teams_impl( + teams: &[(ClientId, TeamInfo)], + client_id: ClientId, +) -> impl Iterator + Clone { + teams + .iter() + .filter(move |(id, _)| *id == client_id) + .map(|(_, t)| t) +} + +pub struct GameInfo { + pub teams_in_game: u8, + pub teams_at_start: Vec<(ClientId, TeamInfo)>, + pub left_teams: Vec, + pub msg_log: Vec, + pub sync_msg: Option, + pub is_paused: bool, + config: RoomConfig, +} + +impl GameInfo { + fn new(teams: Vec<(ClientId, TeamInfo)>, config: RoomConfig) -> GameInfo { + GameInfo { + left_teams: Vec::new(), + msg_log: Vec::new(), + sync_msg: None, + is_paused: false, + teams_in_game: teams.len() as u8, + teams_at_start: teams, + config, + } + } + + pub fn client_teams(&self, client_id: ClientId) -> impl Iterator + Clone { + client_teams_impl(&self.teams_at_start, client_id) + } +} + +#[derive(Serialize, Deserialize)] +pub struct RoomSave { + pub location: String, + config: RoomConfig, +} + +bitflags! { + pub struct RoomFlags: u8 { + const FIXED = 0b0000_0001; + const RESTRICTED_JOIN = 0b0000_0010; + const RESTRICTED_TEAM_ADD = 0b0000_0100; + const RESTRICTED_UNREGISTERED_PLAYERS = 0b0000_1000; + } +} + +pub struct HWRoom { + pub id: RoomId, + pub master_id: Option, + pub name: String, + pub password: Option, + pub greeting: String, + pub protocol_number: u16, + pub flags: RoomFlags, + + pub players_number: u8, + pub default_hedgehog_number: u8, + pub max_teams: u8, + pub ready_players_number: u8, + pub teams: Vec<(ClientId, TeamInfo)>, + config: RoomConfig, + pub voting: Option, + pub saves: HashMap, + pub game_info: Option, +} + +impl HWRoom { + pub fn new(id: RoomId) -> HWRoom { + HWRoom { + id, + master_id: None, + name: String::new(), + password: None, + greeting: "".to_string(), + flags: RoomFlags::empty(), + protocol_number: 0, + players_number: 0, + default_hedgehog_number: 4, + max_teams: MAX_TEAMS_IN_ROOM, + ready_players_number: 0, + teams: Vec::new(), + config: RoomConfig::new(), + voting: None, + saves: HashMap::new(), + game_info: None, + } + } + + pub fn hedgehogs_number(&self) -> u8 { + self.teams.iter().map(|(_, t)| t.hedgehogs_number).sum() + } + + pub fn addable_hedgehogs(&self) -> u8 { + MAX_HEDGEHOGS_IN_ROOM - self.hedgehogs_number() + } + + pub fn add_team( + &mut self, + owner_id: ClientId, + mut team: TeamInfo, + preserve_color: bool, + ) -> &TeamInfo { + if !preserve_color { + team.color = iter::repeat(()) + .enumerate() + .map(|(i, _)| i as u8) + .take(u8::max_value() as usize + 1) + .find(|i| self.teams.iter().all(|(_, t)| t.color != *i)) + .unwrap_or(0u8) + }; + team.hedgehogs_number = if self.teams.is_empty() { + self.default_hedgehog_number + } else { + self.teams[0] + .1 + .hedgehogs_number + .min(self.addable_hedgehogs()) + }; + self.teams.push((owner_id, team)); + &self.teams.last().unwrap().1 + } + + pub fn remove_team(&mut self, name: &str) { + if let Some(index) = self.teams.iter().position(|(_, t)| t.name == name) { + self.teams.remove(index); + } + } + + pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec { + let mut names = Vec::new(); + let teams = match self.game_info { + Some(ref mut info) => &mut info.teams_at_start, + None => &mut self.teams, + }; + + if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM { + for (_, team) in teams.iter_mut() { + team.hedgehogs_number = n; + names.push(team.name.clone()) + } + self.default_hedgehog_number = n; + } + names + } + + pub fn find_team_and_owner_mut(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)> + where + F: Fn(&TeamInfo) -> bool, + { + self.teams + .iter_mut() + .find(|(_, t)| f(t)) + .map(|(id, t)| (*id, t)) + } + + pub fn find_team(&self, f: F) -> Option<&TeamInfo> + where + F: Fn(&TeamInfo) -> bool, + { + self.teams + .iter() + .find_map(|(_, t)| Some(t).filter(|t| f(&t))) + } + + pub fn client_teams(&self, client_id: ClientId) -> impl Iterator { + client_teams_impl(&self.teams, client_id) + } + + pub fn client_team_indices(&self, client_id: ClientId) -> Vec { + self.teams + .iter() + .enumerate() + .filter(move |(_, (id, _))| *id == client_id) + .map(|(i, _)| i as u8) + .collect() + } + + pub fn clan_team_owners(&self, color: u8) -> impl Iterator + '_ { + self.teams + .iter() + .filter(move |(_, t)| t.color == color) + .map(|(id, _)| *id) + } + + pub fn find_team_owner(&self, team_name: &str) -> Option<(ClientId, &str)> { + self.teams + .iter() + .find(|(_, t)| t.name == team_name) + .map(|(id, t)| (*id, &t.name[..])) + } + + pub fn find_team_color(&self, owner_id: ClientId) -> Option { + self.client_teams(owner_id).nth(0).map(|t| t.color) + } + + pub fn has_multiple_clans(&self) -> bool { + self.teams.iter().min_by_key(|(_, t)| t.color) + != self.teams.iter().max_by_key(|(_, t)| t.color) + } + + pub fn set_config(&mut self, cfg: GameCfg) { + self.config.set_config(cfg); + } + + pub fn start_round(&mut self) { + if self.game_info.is_none() { + self.game_info = Some(GameInfo::new(self.teams.clone(), self.config.clone())); + } + } + + pub fn is_fixed(&self) -> bool { + self.flags.contains(RoomFlags::FIXED) + } + pub fn is_join_restricted(&self) -> bool { + self.flags.contains(RoomFlags::RESTRICTED_JOIN) + } + pub fn is_team_add_restricted(&self) -> bool { + self.flags.contains(RoomFlags::RESTRICTED_TEAM_ADD) + } + pub fn are_unregistered_players_restricted(&self) -> bool { + self.flags + .contains(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS) + } + + pub fn set_is_fixed(&mut self, value: bool) { + self.flags.set(RoomFlags::FIXED, value) + } + pub fn set_join_restriction(&mut self, value: bool) { + self.flags.set(RoomFlags::RESTRICTED_JOIN, value) + } + pub fn set_team_add_restriction(&mut self, value: bool) { + self.flags.set(RoomFlags::RESTRICTED_TEAM_ADD, value) + } + pub fn set_unregistered_players_restriction(&mut self, value: bool) { + self.flags + .set(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, value) + } + + fn flags_string(&self) -> String { + let mut result = "-".to_string(); + if self.game_info.is_some() { + result += "g" + } + if self.password.is_some() { + result += "p" + } + if self.is_join_restricted() { + result += "j" + } + if self.are_unregistered_players_restricted() { + result += "r" + } + result + } + + pub fn info(&self, master: Option<&HWClient>) -> Vec { + let c = &self.config; + vec![ + self.flags_string(), + self.name.clone(), + self.players_number.to_string(), + self.teams.len().to_string(), + master.map_or("[]", |c| &c.nick).to_string(), + c.map_type.to_string(), + c.script.to_string(), + c.scheme.name.to_string(), + c.ammo.name.to_string(), + ] + } + + pub fn active_config(&self) -> &RoomConfig { + match self.game_info { + Some(ref info) => &info.config, + None => &self.config, + } + } + + pub fn map_config(&self) -> Vec { + match self.game_info { + Some(ref info) => info.config.to_map_config(), + None => self.config.to_map_config(), + } + } + + pub fn game_config(&self) -> Vec { + match self.game_info { + Some(ref info) => info.config.to_game_config(), + None => self.config.to_game_config(), + } + } + + pub fn save_config(&mut self, name: String, location: String) { + self.saves.insert( + name, + RoomSave { + location, + config: self.config.clone(), + }, + ); + } + + pub fn load_config(&mut self, name: &str) -> Option<&str> { + if let Some(save) = self.saves.get(name) { + self.config = save.config.clone(); + Some(&save.location[..]) + } else { + None + } + } + + pub fn delete_config(&mut self, name: &str) -> bool { + self.saves.remove(name).is_some() + } + + pub fn get_saves(&self) -> Result { + serde_yaml::to_string(&(&self.greeting, &self.saves)) + } + + pub fn set_saves(&mut self, text: &str) -> Result<(), serde_yaml::Error> { + serde_yaml::from_str::<(String, HashMap)>(text).map( + |(greeting, saves)| { + self.greeting = greeting; + self.saves = saves; + }, + ) + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/core/server.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core/server.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,285 @@ +use super::{ + client::HWClient, + types::{ClientId, RoomId}, + indexslab::IndexSlab, + room::HWRoom, +}; +use crate::{ + utils, + protocol::messages::HWProtocolMessage::Greeting +}; + +use bitflags::*; +use log::*; +use slab; +use std::{borrow::BorrowMut, iter, num::NonZeroU16}; + +type Slab = slab::Slab; + +pub struct HWAnteClient { + pub nick: Option, + pub protocol_number: Option, + pub server_salt: String, + pub is_checker: bool, +} + +pub struct HWAnteroom { + pub clients: IndexSlab, +} + +impl HWAnteroom { + pub fn new(clients_limit: usize) -> Self { + let clients = IndexSlab::with_capacity(clients_limit); + HWAnteroom { clients } + } + + pub fn add_client(&mut self, client_id: ClientId, salt: String) { + let client = HWAnteClient { + nick: None, + protocol_number: None, + server_salt: salt, + is_checker: false, + }; + self.clients.insert(client_id, client); + } + + pub fn remove_client(&mut self, client_id: ClientId) -> Option { + let mut client = self.clients.remove(client_id); + client + } +} + +pub struct ServerGreetings { + pub for_latest_protocol: String, + pub for_old_protocols: String, +} + +impl ServerGreetings { + fn new() -> Self { + Self { + for_latest_protocol: "\u{1f994} is watching".to_string(), + for_old_protocols: "\u{1f994} is watching".to_string(), + } + } +} + +bitflags! { + pub struct ServerFlags: u8 { + const REGISTERED_ONLY = 0b0000_1000; + } +} + +pub struct HWServer { + pub clients: IndexSlab, + pub rooms: Slab, + pub anteroom: HWAnteroom, + pub latest_protocol: u16, + pub flags: ServerFlags, + pub greetings: ServerGreetings, +} + +impl HWServer { + pub fn new(clients_limit: usize, rooms_limit: usize) -> Self { + let rooms = Slab::with_capacity(rooms_limit); + let clients = IndexSlab::with_capacity(clients_limit); + Self { + clients, + rooms, + anteroom: HWAnteroom::new(clients_limit), + greetings: ServerGreetings::new(), + latest_protocol: 58, + flags: ServerFlags::empty(), + } + } + + pub fn add_client(&mut self, client_id: ClientId, data: HWAnteClient) { + if let (Some(protocol), Some(nick)) = (data.protocol_number, data.nick) { + let mut client = HWClient::new(client_id, protocol.get(), nick); + client.set_is_checker(data.is_checker); + self.clients.insert(client_id, client); + } + } + + pub fn remove_client(&mut self, client_id: ClientId) { + self.clients.remove(client_id); + } + + pub fn get_greetings(&self, client_id: ClientId) -> &str { + if self.clients[client_id].protocol_number < self.latest_protocol { + &self.greetings.for_old_protocols + } else { + &self.greetings.for_latest_protocol + } + } + + #[inline] + pub fn create_room( + &mut self, + creator_id: ClientId, + name: String, + password: Option, + ) -> RoomId { + create_room( + &mut self.clients[creator_id], + &mut self.rooms, + name, + password, + ) + } + + #[inline] + pub fn move_to_room(&mut self, client_id: ClientId, room_id: RoomId) { + move_to_room(&mut self.clients[client_id], &mut self.rooms[room_id]) + } + + pub fn has_room(&self, name: &str) -> bool { + self.find_room(name).is_some() + } + + pub fn find_room(&self, name: &str) -> Option<&HWRoom> { + self.rooms + .iter() + .find_map(|(_, r)| Some(r).filter(|r| r.name == name)) + } + + pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HWRoom> { + self.rooms + .iter_mut() + .find_map(|(_, r)| Some(r).filter(|r| r.name == name)) + } + + pub fn find_client(&self, nick: &str) -> Option<&HWClient> { + self.clients + .iter() + .find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) + } + + pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> { + self.clients + .iter_mut() + .find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) + } + + pub fn all_clients(&self) -> impl Iterator + '_ { + self.clients.iter().map(|(id, _)| id) + } + + pub fn filter_clients<'a, F>(&'a self, f: F) -> impl Iterator + 'a + where + F: Fn(&(usize, &HWClient)) -> bool + 'a, + { + self.clients.iter().filter(f).map(|(_, c)| c.id) + } + + pub fn filter_rooms<'a, F>(&'a self, f: F) -> impl Iterator + 'a + where + F: Fn(&(usize, &HWRoom)) -> bool + 'a, + { + self.rooms.iter().filter(f).map(|(_, c)| c.id) + } + + pub fn collect_clients(&self, f: F) -> Vec + where + F: Fn(&(usize, &HWClient)) -> bool, + { + self.filter_clients(f).collect() + } + + pub fn collect_nicks(&self, f: F) -> Vec + where + F: Fn(&(usize, &HWClient)) -> bool, + { + self.clients + .iter() + .filter(f) + .map(|(_, c)| c.nick.clone()) + .collect() + } + + pub fn lobby_clients(&self) -> impl Iterator + '_ { + self.filter_clients(|(_, c)| c.room_id == None) + } + + pub fn room_clients(&self, room_id: RoomId) -> impl Iterator + '_ { + self.filter_clients(move |(_, c)| c.room_id == Some(room_id)) + } + + pub fn protocol_clients(&self, protocol: u16) -> impl Iterator + '_ { + self.filter_clients(move |(_, c)| c.protocol_number == protocol) + } + + pub fn protocol_rooms(&self, protocol: u16) -> impl Iterator + '_ { + self.filter_rooms(move |(_, r)| r.protocol_number == protocol) + } + + pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec { + let room_id = self.clients[self_id].room_id; + self.collect_clients(|(id, c)| *id != self_id && c.room_id == room_id) + } + + pub fn is_registered_only(&self) -> bool { + self.flags.contains(ServerFlags::REGISTERED_ONLY) + } + + pub fn set_is_registered_only(&mut self, value: bool) { + self.flags.set(ServerFlags::REGISTERED_ONLY, value) + } +} + +fn allocate_room(rooms: &mut Slab) -> &mut HWRoom { + let entry = rooms.vacant_entry(); + let room = HWRoom::new(entry.key()); + entry.insert(room) +} + +fn create_room( + client: &mut HWClient, + rooms: &mut Slab, + name: String, + password: Option, +) -> RoomId { + let room = allocate_room(rooms); + + room.master_id = Some(client.id); + room.name = name; + room.password = password; + room.protocol_number = client.protocol_number; + + room.players_number = 1; + room.ready_players_number = 1; + + client.room_id = Some(room.id); + client.set_is_master(true); + client.set_is_ready(true); + client.set_is_joined_mid_game(false); + + room.id +} + +fn move_to_room(client: &mut HWClient, room: &mut HWRoom) { + debug_assert!(client.room_id != Some(room.id)); + + room.players_number += 1; + + client.room_id = Some(room.id); + client.set_is_joined_mid_game(room.game_info.is_some()); + client.set_is_in_game(room.game_info.is_some()); + + if let Some(ref mut info) = room.game_info { + let teams = info.client_teams(client.id); + client.teams_in_game = teams.clone().count() as u8; + client.clan = teams.clone().next().map(|t| t.color); + let team_names: Vec<_> = teams.map(|t| t.name.clone()).collect(); + + if !team_names.is_empty() { + info.left_teams.retain(|name| !team_names.contains(&name)); + info.teams_in_game += team_names.len() as u8; + room.teams = info + .teams_at_start + .iter() + .filter(|(_, t)| !team_names.contains(&t.name)) + .cloned() + .collect(); + } + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/core/types.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core/types.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,193 @@ +use serde_derive::{Deserialize, Serialize}; + +pub type ClientId = usize; +pub type RoomId = usize; + +pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum ServerVar { + MOTDNew(String), + MOTDOld(String), + LatestProto(u16), +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum GameCfg { + FeatureSize(u32), + MapType(String), + MapGenerator(u32), + MazeSize(u32), + Seed(String), + Template(u32), + + Ammo(String, Option), + Scheme(String, Vec), + Script(String), + Theme(String), + DrawnMap(String), +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct TeamInfo { + pub owner: String, + pub name: String, + pub color: u8, + pub grave: String, + pub fort: String, + pub voice_pack: String, + pub flag: String, + pub difficulty: u8, + pub hedgehogs_number: u8, + pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize], +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct HedgehogInfo { + pub name: String, + pub hat: String, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Ammo { + pub name: String, + pub settings: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Scheme { + pub name: String, + pub settings: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct RoomConfig { + pub feature_size: u32, + pub map_type: String, + pub map_generator: u32, + pub maze_size: u32, + pub seed: String, + pub template: u32, + + pub ammo: Ammo, + pub scheme: Scheme, + pub script: String, + pub theme: String, + pub drawn_map: Option, +} + +impl RoomConfig { + pub fn new() -> RoomConfig { + RoomConfig { + feature_size: 12, + map_type: "+rnd+".to_string(), + map_generator: 0, + maze_size: 0, + seed: "seed".to_string(), + template: 0, + + ammo: Ammo { + name: "Default".to_string(), + settings: None, + }, + scheme: Scheme { + name: "Default".to_string(), + settings: Vec::new(), + }, + script: "Normal".to_string(), + theme: "\u{1f994}".to_string(), + drawn_map: None, + } + } + + pub fn set_config(&mut self, cfg: GameCfg) { + match cfg { + GameCfg::FeatureSize(s) => self.feature_size = s, + GameCfg::MapType(t) => self.map_type = t, + GameCfg::MapGenerator(g) => self.map_generator = g, + GameCfg::MazeSize(s) => self.maze_size = s, + GameCfg::Seed(s) => self.seed = s, + GameCfg::Template(t) => self.template = t, + + GameCfg::Ammo(n, s) => { + self.ammo = Ammo { + name: n, + settings: s, + } + } + GameCfg::Scheme(n, s) => { + self.scheme = Scheme { + name: n, + settings: s, + } + } + GameCfg::Script(s) => self.script = s, + GameCfg::Theme(t) => self.theme = t, + GameCfg::DrawnMap(m) => self.drawn_map = Some(m), + }; + } + + pub fn to_map_config(&self) -> Vec { + vec![ + self.feature_size.to_string(), + self.map_type.to_string(), + self.map_generator.to_string(), + self.maze_size.to_string(), + self.seed.to_string(), + self.template.to_string(), + ] + } + + pub fn to_game_config(&self) -> Vec { + use GameCfg::*; + let mut v = vec![ + Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()), + Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()), + Script(self.script.to_string()), + Theme(self.theme.to_string()), + ]; + if let Some(ref m) = self.drawn_map { + v.push(DrawnMap(m.to_string())) + } + v + } +} + +pub struct Replay { + pub config: RoomConfig, + pub teams: Vec, + pub message_log: Vec, +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum VoteType { + Kick(String), + Map(Option), + Pause, + NewSeed, + HedgehogsPerTeam(u8), +} + +pub struct Vote { + pub is_pro: bool, + pub is_forced: bool, +} + +#[derive(Clone, Debug)] +pub struct Voting { + pub ttl: u32, + pub voters: Vec, + pub votes: Vec<(ClientId, bool)>, + pub kind: VoteType, +} + +impl Voting { + pub fn new(kind: VoteType, voters: Vec) -> Voting { + Voting { + kind, + voters, + ttl: 2, + votes: Vec::new(), + } + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,402 @@ +use mio; +use std::{collections::HashMap, io, io::Write}; + +use self::{ + actions::{Destination, DestinationGroup, PendingMessage}, + inanteroom::LoginResult +}; +use crate::{ + core::{ + server::HWServer, + types::{ClientId, Replay, RoomId, GameCfg, TeamInfo}, + room::RoomSave + }, + protocol::messages::{ + server_chat, + HWProtocolMessage, + HWServerMessage, + HWServerMessage::*, + global_chat, + HWProtocolMessage::EngineMessage + }, + utils, +}; +use base64::encode; +use log::*; +use rand::{thread_rng, RngCore}; + +mod actions; +mod checker; +mod common; +mod inroom; +mod inlobby; +mod inanteroom; + +use std::fmt::{Formatter, LowerHex}; + +#[derive(PartialEq)] +pub struct Sha1Digest([u8; 20]); + +impl Sha1Digest { + pub fn new(digest: [u8; 20]) -> Self { + Self(digest) + } +} + +impl LowerHex for Sha1Digest { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +pub struct AccountInfo { + pub is_registered: bool, + pub is_admin: bool, + pub is_contributor: bool, + pub server_hash: Sha1Digest, +} + +pub enum IoTask { + GetAccount { + nick: String, + protocol: u16, + password_hash: String, + client_salt: String, + server_salt: String, + }, + GetReplay { + id: u32, + }, + SaveRoom { + room_id: RoomId, + filename: String, + contents: String, + }, + LoadRoom { + room_id: RoomId, + filename: String, + }, +} + +pub enum IoResult { + Account(Option), + Replay(Option), + SaveRoom(RoomId, bool), + LoadRoom(RoomId, Option), +} + +pub struct Response { + client_id: ClientId, + messages: Vec, + io_tasks: Vec, + removed_clients: Vec, +} + +impl Response { + pub fn new(client_id: ClientId) -> Self { + Self { + client_id, + messages: vec![], + io_tasks: vec![], + removed_clients: vec![], + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.messages.is_empty() && self.removed_clients.is_empty() && self.io_tasks.is_empty() + } + + #[inline] + pub fn len(&self) -> usize { + self.messages.len() + } + + #[inline] + pub fn client_id(&self) -> ClientId { + self.client_id + } + + #[inline] + pub fn add(&mut self, message: PendingMessage) { + self.messages.push(message) + } + + #[inline] + pub fn request_io(&mut self, task: IoTask) { + self.io_tasks.push(task) + } + + pub fn extract_messages<'a, 'b: 'a>( + &'b mut self, + server: &'a HWServer, + ) -> impl Iterator, HWServerMessage)> + 'a { + let client_id = self.client_id; + self.messages.drain(..).map(move |m| { + let ids = get_recipients(server, client_id, m.destination); + (ids, m.message) + }) + } + + pub fn remove_client(&mut self, client_id: ClientId) { + self.removed_clients.push(client_id); + } + + pub fn extract_removed_clients(&mut self) -> impl Iterator + '_ { + self.removed_clients.drain(..) + } + + pub fn extract_io_tasks(&mut self) -> impl Iterator + '_ { + self.io_tasks.drain(..) + } +} + +impl Extend for Response { + fn extend>(&mut self, iter: T) { + for msg in iter { + self.add(msg) + } + } +} + +fn get_recipients( + server: &HWServer, + client_id: ClientId, + destination: Destination, +) -> Vec { + match destination { + Destination::ToSelf => vec![client_id], + Destination::ToId(id) => vec![id], + Destination::ToIds(ids) => ids, + Destination::ToAll { group, skip_self } => { + let mut ids: Vec<_> = match group { + DestinationGroup::All => server.all_clients().collect(), + DestinationGroup::Lobby => server.lobby_clients().collect(), + DestinationGroup::Protocol(proto) => server.protocol_clients(proto).collect(), + DestinationGroup::Room(id) => server.room_clients(id).collect(), + }; + + if skip_self { + if let Some(index) = ids.iter().position(|id| *id == client_id) { + ids.remove(index); + } + } + + ids + } + } +} + +pub fn handle( + server: &mut HWServer, + client_id: ClientId, + response: &mut Response, + message: HWProtocolMessage, +) { + match message { + HWProtocolMessage::Ping => response.add(Pong.send_self()), + _ => { + if server.anteroom.clients.contains(client_id) { + match inanteroom::handle(server, client_id, response, message) { + LoginResult::Unchanged => (), + LoginResult::Complete => { + if let Some(client) = server.anteroom.remove_client(client_id) { + server.add_client(client_id, client); + common::join_lobby(server, response); + } + } + LoginResult::Exit => { + server.anteroom.remove_client(client_id); + response.remove_client(client_id); + } + } + } else if server.clients.contains(client_id) { + match message { + HWProtocolMessage::Quit(Some(msg)) => { + common::remove_client(server, response, "User quit: ".to_string() + &msg); + } + HWProtocolMessage::Quit(None) => { + common::remove_client(server, response, "User quit".to_string()); + } + HWProtocolMessage::Info(nick) => { + if let Some(client) = server.find_client(&nick) { + let admin_sign = if client.is_admin() { "@" } else { "" }; + let master_sign = if client.is_master() { "+" } else { "" }; + let room_info = match client.room_id { + Some(room_id) => { + let room = &server.rooms[room_id]; + let status = match room.game_info { + Some(_) if client.teams_in_game == 0 => "(spectating)", + Some(_) => "(playing)", + None => "", + }; + format!( + "[{}{}room {}]{}", + admin_sign, master_sign, room.name, status + ) + } + None => format!("[{}lobby]", admin_sign), + }; + + let info = vec![ + client.nick.clone(), + "[]".to_string(), + utils::protocol_version_string(client.protocol_number).to_string(), + room_info, + ]; + response.add(Info(info).send_self()) + } else { + response + .add(server_chat("Player is not online.".to_string()).send_self()) + } + } + HWProtocolMessage::ToggleServerRegisteredOnly => { + if !server.clients[client_id].is_admin() { + response.add(Warning("Access denied.".to_string()).send_self()); + } else { + server.set_is_registered_only(server.is_registered_only()); + let msg = if server.is_registered_only() { + "This server no longer allows unregistered players to join." + } else { + "This server now allows unregistered players to join." + }; + response.add(server_chat(msg.to_string()).send_all()); + } + } + HWProtocolMessage::Global(msg) => { + if !server.clients[client_id].is_admin() { + response.add(Warning("Access denied.".to_string()).send_self()); + } else { + response.add(global_chat(msg).send_all()) + } + } + HWProtocolMessage::SuperPower => { + if !server.clients[client_id].is_admin() { + response.add(Warning("Access denied.".to_string()).send_self()); + } else { + server.clients[client_id].set_has_super_power(true); + response + .add(server_chat("Super power activated.".to_string()).send_self()) + } + } + HWProtocolMessage::Watch(id) => { + #[cfg(feature = "official-server")] + { + response.request_io(IoTask::GetReplay { id }) + } + + #[cfg(not(feature = "official-server"))] + { + response.add( + Warning("This server does not support replays!".to_string()) + .send_self(), + ); + } + } + _ => match server.clients[client_id].room_id { + None => inlobby::handle(server, client_id, response, message), + Some(room_id) => { + inroom::handle(server, client_id, response, room_id, message) + } + }, + } + } + } + } +} + +pub fn handle_client_accept(server: &mut HWServer, client_id: ClientId, response: &mut Response) { + let mut salt = [0u8; 18]; + thread_rng().fill_bytes(&mut salt); + + server.anteroom.add_client(client_id, encode(&salt)); + + response.add(HWServerMessage::Connected(utils::SERVER_VERSION).send_self()); +} + +pub fn handle_client_loss(server: &mut HWServer, client_id: ClientId, response: &mut Response) { + if server.anteroom.remove_client(client_id).is_none() { + common::remove_client(server, response, "Connection reset".to_string()); + } +} + +pub fn handle_io_result( + server: &mut HWServer, + client_id: ClientId, + response: &mut Response, + io_result: IoResult, +) { + match io_result { + IoResult::Account(Some(info)) => { + if !info.is_registered && server.is_registered_only() { + response.add( + Bye("This server only allows registered users to join.".to_string()) + .send_self(), + ); + response.remove_client(client_id); + } else { + response.add(ServerAuth(format!("{:x}", info.server_hash)).send_self()); + if let Some(client) = server.anteroom.remove_client(client_id) { + server.add_client(client_id, client); + let client = &mut server.clients[client_id]; + client.set_is_registered(info.is_registered); + client.set_is_admin(info.is_admin); + client.set_is_contributor(info.is_admin) + } + } + } + IoResult::Account(None) => { + response.add(Error("Authentication failed.".to_string()).send_self()); + response.remove_client(client_id); + } + IoResult::Replay(Some(replay)) => { + let protocol = server.clients[client_id].protocol_number; + let start_msg = if protocol < 58 { + RoomJoined(vec![server.clients[client_id].nick.clone()]) + } else { + ReplayStart + }; + response.add(start_msg.send_self()); + + common::get_room_config_impl(&replay.config, client_id, response); + common::get_teams(replay.teams.iter(), client_id, response); + response.add(RunGame.send_self()); + response.add(ForwardEngineMessage(replay.message_log).send_self()); + + if protocol < 58 { + response.add(Kicked.send_self()); + } + } + IoResult::Replay(None) => { + response.add(Warning("Could't load the replay".to_string()).send_self()) + } + IoResult::SaveRoom(_, true) => { + response.add(server_chat("Room configs saved successfully.".to_string()).send_self()); + } + IoResult::SaveRoom(_, false) => { + response.add(Warning("Unable to save the room configs.".to_string()).send_self()); + } + IoResult::LoadRoom(room_id, Some(contents)) => { + if let Some(ref mut room) = server.rooms.get_mut(room_id) { + match room.set_saves(&contents) { + Ok(_) => response.add( + server_chat("Room configs loaded successfully.".to_string()).send_self(), + ), + Err(e) => { + warn!("Error while deserializing the room configs: {}", e); + response.add( + Warning("Unable to deserialize the room configs.".to_string()) + .send_self(), + ); + } + } + } + } + IoResult::LoadRoom(_, None) => { + response.add(Warning("Unable to load the room configs.".to_string()).send_self()); + } + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers/actions.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers/actions.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,115 @@ +use crate::{ + core::{ + client::HWClient, + server::HWServer, + types::{ClientId, GameCfg, RoomId, VoteType}, + room::HWRoom, + room::{GameInfo, RoomFlags} + }, + protocol::messages::{server_chat, HWProtocolMessage, HWServerMessage, HWServerMessage::*}, + utils::to_engine_msg, +}; +use rand::{distributions::Uniform, thread_rng, Rng}; +use std::{io, io::Write, iter::once, mem::replace}; + +pub enum DestinationGroup { + All, + Lobby, + Room(RoomId), + Protocol(u16), +} + +pub enum Destination { + ToId(ClientId), + ToIds(Vec), + ToSelf, + ToAll { + group: DestinationGroup, + skip_self: bool, + }, +} + +pub struct PendingMessage { + pub destination: Destination, + pub message: HWServerMessage, +} + +impl PendingMessage { + pub fn send(message: HWServerMessage, client_id: ClientId) -> PendingMessage { + PendingMessage { + destination: Destination::ToId(client_id), + message, + } + } + + pub fn send_many(message: HWServerMessage, client_ids: Vec) -> PendingMessage { + PendingMessage { + destination: Destination::ToIds(client_ids), + message, + } + } + + pub fn send_self(message: HWServerMessage) -> PendingMessage { + PendingMessage { + destination: Destination::ToSelf, + message, + } + } + + pub fn send_all(message: HWServerMessage) -> PendingMessage { + let destination = Destination::ToAll { + group: DestinationGroup::All, + skip_self: false, + }; + PendingMessage { + destination, + message, + } + } + + pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage { + if let Destination::ToAll { ref mut group, .. } = self.destination { + *group = DestinationGroup::Room(clients_room_id) + } + self + } + + pub fn in_lobby(mut self) -> PendingMessage { + if let Destination::ToAll { ref mut group, .. } = self.destination { + *group = DestinationGroup::Lobby + } + self + } + + pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage { + if let Destination::ToAll { ref mut group, .. } = self.destination { + *group = DestinationGroup::Protocol(protocol_number) + } + self + } + + pub fn but_self(mut self) -> PendingMessage { + if let Destination::ToAll { + ref mut skip_self, .. + } = self.destination + { + *skip_self = true + } + self + } +} + +impl HWServerMessage { + pub fn send(self, client_id: ClientId) -> PendingMessage { + PendingMessage::send(self, client_id) + } + pub fn send_many(self, client_ids: Vec) -> PendingMessage { + PendingMessage::send_many(self, client_ids) + } + pub fn send_self(self) -> PendingMessage { + PendingMessage::send_self(self) + } + pub fn send_all(self) -> PendingMessage { + PendingMessage::send_all(self) + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers/checker.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers/checker.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,13 @@ +use log::*; +use mio; + +use crate::{ + protocol::messages::HWProtocolMessage, + core::{server::HWServer, types::ClientId}, +}; + +pub fn handle(_server: &mut HWServer, _client_id: ClientId, message: HWProtocolMessage) { + match message { + _ => warn!("Unknown command"), + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers/common.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers/common.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,661 @@ +use crate::{ + protocol::messages::{ + server_chat, + add_flags, remove_flags, + HWProtocolMessage::{self, Rnd}, + HWServerMessage::{self, *}, + ProtocolFlags as Flags, + }, + core::{ + client::HWClient, + server::HWServer, + types::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType}, + room::HWRoom, + }, + utils::to_engine_msg, +}; + +use super::Response; + +use crate::core::types::RoomConfig; +use rand::{self, seq::SliceRandom, thread_rng, Rng}; +use std::{iter::once, mem::replace}; + +pub fn rnd_reply(options: &[String]) -> HWServerMessage { + let mut rng = thread_rng(); + + let reply = if options.is_empty() { + (*&["heads", "tails"].choose(&mut rng).unwrap()).to_string() + } else { + options.choose(&mut rng).unwrap().clone() + }; + + ChatMsg { + nick: "[random]".to_string(), + msg: reply, + } +} + +pub fn join_lobby(server: &mut HWServer, response: &mut Response) { + let client_id = response.client_id(); + + let client = &server.clients[client_id]; + let nick = vec![client.nick.clone()]; + let mut flags = vec![]; + if client.is_registered() { + flags.push(Flags::Registered) + } + if client.is_admin() { + flags.push(Flags::Admin) + } + if client.is_contributor() { + flags.push(Flags::Contributor) + } + + let all_nicks: Vec<_> = server.collect_nicks(|_| true); + + let mut flag_selectors = [ + ( + Flags::Registered, + server.collect_nicks(|(_, c)| c.is_registered()), + ), + (Flags::Admin, server.collect_nicks(|(_, c)| c.is_admin())), + ( + Flags::Contributor, + server.collect_nicks(|(_, c)| c.is_contributor()), + ), + ( + Flags::InRoom, + server.collect_nicks(|(_, c)| c.room_id.is_some()), + ), + ]; + + let server_msg = ServerMessage(server.get_greetings(client_id).to_string()); + + let rooms_msg = Rooms( + server + .rooms + .iter() + .filter(|(_, r)| r.protocol_number == client.protocol_number) + .flat_map(|(_, r)| r.info(r.master_id.map(|id| &server.clients[id]))) + .collect(), + ); + + response.add(LobbyJoined(nick).send_all().but_self()); + response.add( + ClientFlags(add_flags(&flags), all_nicks.clone()) + .send_all() + .but_self(), + ); + + response.add(LobbyJoined(all_nicks).send_self()); + for (flag, nicks) in &mut flag_selectors { + if !nicks.is_empty() { + response.add(ClientFlags(add_flags(&[*flag]), replace(nicks, vec![])).send_self()); + } + } + + response.add(server_msg.send_self()); + response.add(rooms_msg.send_self()); +} + +pub fn remove_teams( + room: &mut HWRoom, + team_names: Vec, + is_in_game: bool, + response: &mut Response, +) { + if let Some(ref mut info) = room.game_info { + for team_name in &team_names { + info.left_teams.push(team_name.clone()); + + if is_in_game { + 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(), + ); + + info.teams_in_game -= 1; + + 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()); + 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 { + room.remove_team(&team_name); + response.add(TeamRemove(team_name).send_all().in_room(room.id)); + } +} + +fn remove_client_from_room( + client: &mut HWClient, + room: &mut HWRoom, + response: &mut Response, + msg: &str, +) { + room.players_number -= 1; + if room.players_number > 0 || room.is_fixed() { + if client.is_ready() && room.ready_players_number > 0 { + room.ready_players_number -= 1; + } + + let team_names: Vec<_> = room + .client_teams(client.id) + .map(|t| t.name.clone()) + .collect(); + remove_teams(room, team_names, client.is_in_game(), response); + + if room.players_number > 0 { + response.add( + RoomLeft(client.nick.clone(), msg.to_string()) + .send_all() + .in_room(room.id) + .but_self(), + ); + } + + if client.is_master() && !room.is_fixed() { + client.set_is_master(false); + response.add( + ClientFlags( + remove_flags(&[Flags::RoomMaster]), + vec![client.nick.clone()], + ) + .send_all() + .in_room(room.id), + ); + room.master_id = None; + } + } + + client.room_id = None; + + let update_msg = if room.players_number == 0 && !room.is_fixed() { + RoomRemove(room.name.clone()) + } else { + RoomUpdated(room.name.clone(), room.info(Some(&client))) + }; + response.add(update_msg.send_all().with_protocol(room.protocol_number)); + + response.add(ClientFlags(remove_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_all()); +} + +pub fn change_master( + server: &mut HWServer, + room_id: RoomId, + new_master_id: ClientId, + response: &mut Response, +) { + let room = &mut server.rooms[room_id]; + if let Some(master_id) = room.master_id { + server.clients[master_id].set_is_master(false); + response.add( + ClientFlags( + remove_flags(&[Flags::RoomMaster]), + vec![server.clients[master_id].nick.clone()], + ) + .send_all() + .in_room(room_id), + ) + } + + room.master_id = Some(new_master_id); + server.clients[new_master_id].set_is_master(true); + + response.add( + ClientFlags( + add_flags(&[Flags::RoomMaster]), + vec![server.clients[new_master_id].nick.clone()], + ) + .send_all() + .in_room(room_id), + ); +} + +pub fn enter_room( + server: &mut HWServer, + client_id: ClientId, + room_id: RoomId, + response: &mut Response, +) { + let nick = server.clients[client_id].nick.clone(); + server.move_to_room(client_id, room_id); + + response.add(RoomJoined(vec![nick.clone()]).send_all().in_room(room_id)); + response.add(ClientFlags(add_flags(&[Flags::InRoom]), vec![nick]).send_all()); + let nicks = server.collect_nicks(|(_, c)| c.room_id == Some(room_id)); + response.add(RoomJoined(nicks).send_self()); + + get_room_teams(server, room_id, client_id, response); + + let room = &server.rooms[room_id]; + get_room_config(room, client_id, response); + + let mut flag_selectors = [ + ( + Flags::RoomMaster, + server.collect_nicks(|(_, c)| c.is_master()), + ), + (Flags::Ready, server.collect_nicks(|(_, c)| c.is_ready())), + (Flags::InGame, server.collect_nicks(|(_, c)| c.is_in_game())), + ]; + + for (flag, nicks) in &mut flag_selectors { + response.add(ClientFlags(add_flags(&[*flag]), replace(nicks, vec![])).send_self()); + } + + if !room.greeting.is_empty() { + response.add( + ChatMsg { + nick: "[greeting]".to_string(), + msg: room.greeting.clone(), + } + .send_self(), + ); + } +} + +pub fn exit_room(server: &mut HWServer, client_id: ClientId, response: &mut Response, msg: &str) { + let client = &mut server.clients[client_id]; + + if let Some(room_id) = client.room_id { + let room = &mut server.rooms[room_id]; + + remove_client_from_room(client, room, response, msg); + + if !room.is_fixed() { + if room.players_number == 0 { + server.rooms.remove(room_id); + } else if room.master_id == None { + let new_master_id = server.room_clients(room_id).next(); + if let Some(new_master_id) = new_master_id { + let new_master_nick = server.clients[new_master_id].nick.clone(); + let room = &mut server.rooms[room_id]; + room.master_id = Some(new_master_id); + server.clients[new_master_id].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); + + response.add( + ClientFlags(add_flags(&[Flags::RoomMaster]), vec![new_master_nick]) + .send_all() + .in_room(room.id), + ); + } + } + } + } +} + +pub fn remove_client(server: &mut HWServer, response: &mut Response, msg: String) { + let client_id = response.client_id(); + let client = &mut server.clients[client_id]; + let nick = client.nick.clone(); + + exit_room(server, client_id, response, &msg); + + server.remove_client(client_id); + + response.add(LobbyLeft(nick, msg.to_string()).send_all()); + response.add(Bye("User quit: ".to_string() + &msg).send_self()); + response.remove_client(client_id); +} + +pub fn get_room_update( + room_name: Option, + room: &HWRoom, + master: Option<&HWClient>, + response: &mut Response, +) { + let update_msg = RoomUpdated(room_name.unwrap_or(room.name.clone()), room.info(master)); + response.add(update_msg.send_all().with_protocol(room.protocol_number)); +} + +pub fn get_room_config_impl(config: &RoomConfig, to_client: ClientId, response: &mut Response) { + response.add(ConfigEntry("FULLMAPCONFIG".to_string(), config.to_map_config()).send(to_client)); + for cfg in config.to_game_config() { + response.add(cfg.to_server_msg().send(to_client)); + } +} + +pub fn get_room_config(room: &HWRoom, to_client: ClientId, response: &mut Response) { + get_room_config_impl(room.active_config(), to_client, response); +} + +pub fn get_teams<'a, I>(teams: I, to_client: ClientId, response: &mut Response) +where + I: Iterator, +{ + for team in teams { + response.add(TeamAdd(team.to_protocol()).send(to_client)); + response.add(TeamColor(team.name.clone(), team.color).send(to_client)); + response.add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send(to_client)); + } +} + +pub fn get_room_teams( + server: &HWServer, + room_id: RoomId, + to_client: ClientId, + response: &mut Response, +) { + let room = &server.rooms[room_id]; + let current_teams = match room.game_info { + Some(ref info) => &info.teams_at_start, + None => &room.teams, + }; + + get_teams(current_teams.iter().map(|(_, t)| t), to_client, response); +} + +pub fn get_room_flags( + server: &HWServer, + room_id: RoomId, + to_client: ClientId, + response: &mut Response, +) { + let room = &server.rooms[room_id]; + if let Some(id) = room.master_id { + response.add( + ClientFlags( + add_flags(&[Flags::RoomMaster]), + vec![server.clients[id].nick.clone()], + ) + .send(to_client), + ); + } + let nicks: Vec<_> = server + .clients + .iter() + .filter(|(_, c)| c.room_id == Some(room_id) && c.is_ready()) + .map(|(_, c)| c.nick.clone()) + .collect(); + if !nicks.is_empty() { + response.add(ClientFlags(add_flags(&[Flags::Ready]), nicks).send(to_client)); + } +} + +pub fn apply_voting_result( + server: &mut HWServer, + room_id: RoomId, + response: &mut Response, + kind: VoteType, +) { + match kind { + VoteType::Kick(nick) => { + if let Some(client) = server.find_client(&nick) { + if client.room_id == Some(room_id) { + let id = client.id; + response.add(Kicked.send(id)); + exit_room(server, id, response, "kicked"); + } + } + } + VoteType::Map(None) => (), + VoteType::Map(Some(name)) => { + if let Some(location) = server.rooms[room_id].load_config(&name) { + response.add( + server_chat(location.to_string()) + .send_all() + .in_room(room_id), + ); + let room = &server.rooms[room_id]; + let room_master = if let Some(id) = room.master_id { + Some(&server.clients[id]) + } else { + None + }; + get_room_update(None, room, room_master, response); + + for (_, client) in server.clients.iter() { + if client.room_id == Some(room_id) { + super::common::get_room_config(&server.rooms[room_id], client.id, response); + } + } + } + } + VoteType::Pause => { + if let Some(ref mut info) = server.rooms[room_id].game_info { + info.is_paused = !info.is_paused; + response.add( + server_chat("Pause toggled.".to_string()) + .send_all() + .in_room(room_id), + ); + response.add( + ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) + .send_all() + .in_room(room_id), + ); + } + } + VoteType::NewSeed => { + let seed = thread_rng().gen_range(0, 1_000_000_000).to_string(); + let cfg = GameCfg::Seed(seed); + response.add(cfg.to_server_msg().send_all().in_room(room_id)); + server.rooms[room_id].set_config(cfg); + } + VoteType::HedgehogsPerTeam(number) => { + let r = &mut server.rooms[room_id]; + let nicks = r.set_hedgehogs_number(number); + + response.extend( + nicks + .into_iter() + .map(|n| HedgehogsNumber(n, number).send_all().in_room(room_id)), + ); + } + } +} + +fn add_vote(room: &mut HWRoom, response: &mut Response, vote: Vote) -> Option { + let client_id = response.client_id; + let mut result = None; + + if let Some(ref mut voting) = room.voting { + if vote.is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) { + response.add(server_chat("Your vote has been counted.".to_string()).send_self()); + voting.votes.push((client_id, vote.is_pro)); + let i = voting.votes.iter(); + let pro = i.clone().filter(|(_, v)| *v).count(); + let contra = i.filter(|(_, v)| !*v).count(); + let success_quota = voting.voters.len() / 2 + 1; + if vote.is_forced && vote.is_pro || pro >= success_quota { + result = Some(true); + } else if vote.is_forced && !vote.is_pro || contra > voting.voters.len() - success_quota + { + result = Some(false); + } + } else { + response.add(server_chat("You already have voted.".to_string()).send_self()); + } + } else { + response.add(server_chat("There's no voting going on.".to_string()).send_self()); + } + + result +} + +pub fn submit_vote(server: &mut HWServer, vote: Vote, response: &mut Response) { + let client_id = response.client_id; + let client = &server.clients[client_id]; + + if let Some(room_id) = client.room_id { + let room = &mut server.rooms[room_id]; + + if let Some(res) = add_vote(room, response, vote) { + response.add( + server_chat("Voting closed.".to_string()) + .send_all() + .in_room(room.id), + ); + let voting = replace(&mut room.voting, None).unwrap(); + if res { + apply_voting_result(server, room_id, response, voting.kind); + } + } + } +} + +pub fn start_game(server: &mut HWServer, room_id: RoomId, response: &mut Response) { + let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server + .clients + .iter() + .map(|(id, c)| (id, c.nick.clone())) + .unzip(); + let room = &mut server.rooms[room_id]; + + if !room.has_multiple_clans() { + response.add( + Warning("The game can't be started with less than two clans!".to_string()).send_self(), + ); + } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { + response.add(Warning("Not all players are ready".to_string()).send_self()); + } else if room.game_info.is_some() { + response.add(Warning("The game is already in progress".to_string()).send_self()); + } else { + room.start_round(); + for id in room_clients { + let c = &mut server.clients[id]; + c.set_is_in_game(true); + c.team_indices = room.client_team_indices(c.id); + } + response.add(RunGame.send_all().in_room(room.id)); + response.add( + ClientFlags(add_flags(&[Flags::InGame]), room_nicks) + .send_all() + .in_room(room.id), + ); + + let room_master = if let Some(id) = room.master_id { + Some(&server.clients[id]) + } else { + None + }; + get_room_update(None, room, room_master, response); + } +} + +pub fn end_game(server: &mut HWServer, room_id: RoomId, response: &mut Response) { + let room = &mut server.rooms[room_id]; + room.ready_players_number = 1; + let room_master = if let Some(id) = room.master_id { + Some(&server.clients[id]) + } else { + None + }; + get_room_update(None, room, room_master, response); + response.add(RoundFinished.send_all().in_room(room_id)); + + if let Some(info) = replace(&mut room.game_info, None) { + for (_, client) in server.clients.iter() { + if client.room_id == Some(room_id) && client.is_joined_mid_game() { + super::common::get_room_config(room, client.id, response); + response.extend( + info.left_teams + .iter() + .map(|name| TeamRemove(name.clone()).send(client.id)), + ); + } + } + } + + let nicks: Vec<_> = 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(); + + if !nicks.is_empty() { + let msg = if room.protocol_number < 38 { + LegacyReady(false, nicks) + } else { + ClientFlags(remove_flags(&[Flags::Ready]), nicks) + }; + response.add(msg.send_all().in_room(room_id)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::messages::HWServerMessage::ChatMsg; + use crate::server::actions::PendingMessage; + + fn reply2string(r: HWServerMessage) -> String { + match r { + ChatMsg { msg: p, .. } => String::from(p), + _ => panic!("expected a ChatMsg"), + } + } + + fn run_handle_test(opts: Vec) { + let opts2 = opts.clone(); + for opt in opts { + while reply2string(rnd_reply(&opts2)) != opt {} + } + } + + /// This test terminates almost surely. + #[test] + fn test_handle_rnd_empty() { + run_handle_test(vec![]) + } + + /// This test terminates almost surely. + #[test] + fn test_handle_rnd_nonempty() { + run_handle_test(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]) + } + + /// This test terminates almost surely (strong law of large numbers) + #[test] + fn test_distribution() { + let eps = 0.000001; + let lim = 0.5; + let opts = vec![0.to_string(), 1.to_string()]; + let mut ones = 0; + let mut tries = 0; + + while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps { + tries += 1; + if reply2string(rnd_reply(&opts)) == 1.to_string() { + ones += 1; + } + } + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers/inanteroom.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers/inanteroom.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,148 @@ +use mio; + +use crate::{ + protocol::messages::{ + HWProtocolMessage::LoadRoom, + HWProtocolMessage, + HWServerMessage::*}, + core::{ + client::HWClient, + server::{HWServer, HWAnteClient, HWAnteroom}, + types::ClientId + }, + utils::is_name_illegal +}; + +use log::*; +#[cfg(feature = "official-server")] +use openssl::sha::sha1; +use std::{ + fmt::{Formatter, LowerHex}, + num::NonZeroU16, +}; + +pub enum LoginResult { + Unchanged, + Complete, + Exit, +} + +fn completion_result<'a, I>( + mut other_clients: I, + client: &mut HWAnteClient, + response: &mut super::Response, +) -> LoginResult +where + I: Iterator, +{ + let has_nick_clash = + other_clients.any(|(_, c)| !c.is_checker() && c.nick == *client.nick.as_ref().unwrap()); + + if has_nick_clash { + if client.protocol_number.unwrap().get() < 38 { + response.add(Bye("User quit: Nickname is already in use".to_string()).send_self()); + LoginResult::Exit + } else { + client.nick = None; + response.add(Notice("NickAlreadyInUse".to_string()).send_self()); + LoginResult::Unchanged + } + } else { + #[cfg(feature = "official-server")] + { + response.add(AskPassword(client.server_salt.clone()).send_self()); + LoginResult::Unchanged + } + + #[cfg(not(feature = "official-server"))] + { + LoginResult::Complete + } + } +} + +pub fn handle( + server: &mut HWServer, + client_id: ClientId, + response: &mut super::Response, + message: HWProtocolMessage, +) -> LoginResult { + match message { + HWProtocolMessage::Quit(_) => { + response.add(Bye("User quit".to_string()).send_self()); + LoginResult::Exit + } + HWProtocolMessage::Nick(nick) => { + let client = &mut server.anteroom.clients[client_id]; + + if client.nick.is_some() { + response.add(Error("Nickname already provided.".to_string()).send_self()); + LoginResult::Unchanged + } else if is_name_illegal(&nick) { + response.add(Bye("Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string()).send_self()); + LoginResult::Exit + } else { + client.nick = Some(nick.clone()); + response.add(Nick(nick).send_self()); + + if client.protocol_number.is_some() { + completion_result(server.clients.iter(), client, response) + } else { + LoginResult::Unchanged + } + } + } + HWProtocolMessage::Proto(proto) => { + let client = &mut server.anteroom.clients[client_id]; + if client.protocol_number.is_some() { + response.add(Error("Protocol already known.".to_string()).send_self()); + LoginResult::Unchanged + } else if proto == 0 { + response.add(Error("Bad number.".to_string()).send_self()); + LoginResult::Unchanged + } else { + client.protocol_number = NonZeroU16::new(proto); + response.add(Proto(proto).send_self()); + + if client.nick.is_some() { + completion_result(server.clients.iter(), client, response) + } else { + LoginResult::Unchanged + } + } + } + #[cfg(feature = "official-server")] + HWProtocolMessage::Password(hash, salt) => { + let client = &server.anteroom.clients[client_id]; + + if let (Some(nick), Some(protocol)) = (client.nick.as_ref(), client.protocol_number) { + response.request_io(super::IoTask::GetAccount { + nick: nick.clone(), + protocol: protocol.get(), + server_salt: client.server_salt.clone(), + client_salt: salt, + password_hash: hash, + }); + }; + + LoginResult::Unchanged + } + #[cfg(feature = "official-server")] + HWProtocolMessage::Checker(protocol, nick, password) => { + let client = &mut server.anteroom.clients[client_id]; + if protocol == 0 { + response.add(Error("Bad number.".to_string()).send_self()); + LoginResult::Unchanged + } else { + client.protocol_number = NonZeroU16::new(protocol); + client.nick = Some(nick); + client.is_checker = true; + LoginResult::Complete + } + } + _ => { + warn!("Incorrect command in logging-in state"); + LoginResult::Unchanged + } + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers/inlobby.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers/inlobby.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,164 @@ +use mio; + +use super::common::rnd_reply; +use crate::{ + protocol::messages::{ + add_flags, remove_flags, server_chat, HWProtocolMessage, HWServerMessage::*, + ProtocolFlags as Flags, + }, + core::{ + client::HWClient, + server::HWServer, + types::{ClientId, ServerVar}, + }, + utils::is_name_illegal, +}; +use log::*; +use std::{collections::HashSet, convert::identity}; + +pub fn handle( + server: &mut HWServer, + client_id: ClientId, + response: &mut super::Response, + message: HWProtocolMessage, +) { + use crate::protocol::messages::HWProtocolMessage::*; + match message { + CreateRoom(name, password) => { + if is_name_illegal(&name) { + response.add(Warning("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: $()*+?[]^{|}".to_string()).send_self()); + } else if server.has_room(&name) { + response.add( + Warning("A room with the same name already exists.".to_string()).send_self(), + ); + } else { + let flags_msg = ClientFlags( + add_flags(&[Flags::RoomMaster, Flags::Ready]), + vec![server.clients[client_id].nick.clone()], + ); + + let room_id = server.create_room(client_id, name, password); + let room = &server.rooms[room_id]; + let client = &server.clients[client_id]; + + response.add( + RoomAdd(room.info(Some(&client))) + .send_all() + .with_protocol(room.protocol_number), + ); + response.add(RoomJoined(vec![client.nick.clone()]).send_self()); + response.add(flags_msg.send_self()); + + response.add( + ClientFlags(add_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_self(), + ); + }; + } + Chat(msg) => { + response.add( + ChatMsg { + nick: server.clients[client_id].nick.clone(), + msg, + } + .send_all() + .in_lobby() + .but_self(), + ); + } + JoinRoom(name, _password) => { + let room = server.rooms.iter().find(|(_, r)| r.name == name); + let room_id = room.map(|(_, r)| r.id); + + let client = &mut server.clients[client_id]; + + if let Some((_, room)) = room { + if client.protocol_number != room.protocol_number { + response.add( + Warning("Room version incompatible to your Hedgewars version!".to_string()) + .send_self(), + ); + } else if room.is_join_restricted() { + response.add( + Warning( + "Access denied. This room currently doesn't allow joining.".to_string(), + ) + .send_self(), + ); + } else if room.players_number == u8::max_value() { + response.add(Warning("This room is already full".to_string()).send_self()); + } else if let Some(room_id) = room_id { + super::common::enter_room(server, client_id, room_id, response); + } + } else { + response.add(Warning("No such room.".to_string()).send_self()); + } + } + Follow(nick) => { + if let Some(HWClient { + room_id: Some(room_id), + .. + }) = server.find_client(&nick) + { + let room = &server.rooms[*room_id]; + response.add(Joining(room.name.clone()).send_self()); + super::common::enter_room(server, client_id, *room_id, response); + } + } + SetServerVar(var) => { + if !server.clients[client_id].is_admin() { + response.add(Warning("Access denied.".to_string()).send_self()); + } else { + match var { + ServerVar::MOTDNew(msg) => server.greetings.for_latest_protocol = msg, + ServerVar::MOTDOld(msg) => server.greetings.for_old_protocols = msg, + ServerVar::LatestProto(n) => server.latest_protocol = n, + } + } + } + GetServerVar => { + if !server.clients[client_id].is_admin() { + response.add(Warning("Access denied.".to_string()).send_self()); + } else { + let vars: Vec<_> = [ + ServerVar::MOTDNew(server.greetings.for_latest_protocol.clone()), + ServerVar::MOTDOld(server.greetings.for_old_protocols.clone()), + ServerVar::LatestProto(server.latest_protocol), + ] + .iter() + .flat_map(|v| v.to_protocol()) + .collect(); + response.add(ServerVars(vars).send_self()); + } + } + Rnd(v) => { + response.add(rnd_reply(&v).send_self()); + } + Stats => { + let mut protocols: HashSet<_> = server + .clients + .iter() + .map(|(_, c)| c.protocol_number) + .chain(server.rooms.iter().map(|(_, r)| r.protocol_number)) + .collect(); + let mut protocols: Vec<_> = protocols.drain().collect(); + protocols.sort(); + + let mut html = Vec::with_capacity(protocols.len() + 2); + + html.push("".to_string()); + for protocol in protocols { + html.push(format!( + "", + super::utils::protocol_version_string(protocol), + server.protocol_clients(protocol).count(), + server.protocol_rooms(protocol).count() + )); + } + html.push("
{}{}{}
".to_string()); + + response.add(Warning(html.join("")).send_self()); + } + List => warn!("Deprecated LIST message received"), + _ => warn!("Incorrect command in lobby state"), + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/handlers/inroom.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/handlers/inroom.rs Tue May 28 19:04:18 2019 +0300 @@ -0,0 +1,615 @@ +use mio; + +use super::common::rnd_reply; +use crate::utils::to_engine_msg; +use crate::{ + protocol::messages::{ + add_flags, remove_flags, server_chat, HWProtocolMessage, HWServerMessage::*, + ProtocolFlags as Flags, + }, + core::{ + server::HWServer, + types, + types::{ClientId, GameCfg, RoomId, VoteType, Voting, MAX_HEDGEHOGS_PER_TEAM}, + room::{HWRoom, RoomFlags, MAX_TEAMS_IN_ROOM}, + }, + utils::is_name_illegal, +}; +use base64::{decode, encode}; +use log::*; +use std::{cmp::min, iter::once, mem::swap}; + +#[derive(Clone)] +struct ByMsg<'a> { + messages: &'a [u8], +} + +impl<'a> Iterator for ByMsg<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option<::Item> { + if let Some(size) = self.messages.get(0) { + let (msg, next) = self.messages.split_at(*size as usize + 1); + self.messages = next; + Some(msg) + } else { + None + } + } +} + +fn by_msg(source: &[u8]) -> ByMsg { + ByMsg { messages: source } +} + +const VALID_MESSAGES: &[u8] = + b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A"; +const NON_TIMED_MESSAGES: &[u8] = b"M#hb"; + +#[cfg(canhazslicepatterns)] +fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool { + match msg { + [size, typ, body..] => { + VALID_MESSAGES.contains(typ) + && match body { + [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => { + team_indices.contains(team) + } + _ => *typ != b'h', + } + } + _ => false, + } +} + +fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool { + if let Some(typ) = msg.get(1) { + VALID_MESSAGES.contains(typ) + } else { + false + } +} + +fn is_msg_empty(msg: &[u8]) -> bool { + msg.get(1).filter(|t| **t == b'+').is_some() +} + +fn is_msg_timed(msg: &[u8]) -> bool { + msg.get(1) + .filter(|t| !NON_TIMED_MESSAGES.contains(t)) + .is_some() +} + +fn voting_description(kind: &VoteType) -> String { + format!( + "New voting started: {}", + match kind { + VoteType::Kick(nick) => format!("kick {}", nick), + VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()), + VoteType::Pause => "pause".to_string(), + VoteType::NewSeed => "new seed".to_string(), + VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number), + } + ) +} + +fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags { + use crate::protocol::messages::HWProtocolMessage::*; + match msg { + ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN, + ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD, + ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, + _ => RoomFlags::empty(), + } +} + +pub fn handle( + server: &mut HWServer, + client_id: ClientId, + response: &mut super::Response, + room_id: RoomId, + message: HWProtocolMessage, +) { + let client = &mut server.clients[client_id]; + let room = &mut server.rooms[room_id]; + + use crate::protocol::messages::HWProtocolMessage::*; + match message { + Part(msg) => { + let msg = match msg { + Some(s) => format!("part: {}", s), + None => "part".to_string(), + }; + super::common::exit_room(server, client_id, response, &msg); + } + Chat(msg) => { + response.add( + ChatMsg { + nick: client.nick.clone(), + msg, + } + .send_all() + .in_room(room_id), + ); + } + TeamChat(msg) => { + let room = &server.rooms[room_id]; + if let Some(ref info) = room.game_info { + if let Some(clan_color) = room.find_team_color(client_id) { + let client = &server.clients[client_id]; + let engine_msg = + to_engine_msg(format!("b{}]{}\x20\x20", client.nick, msg).bytes()); + let team = room.clan_team_owners(clan_color).collect(); + response.add(ForwardEngineMessage(vec![engine_msg]).send_many(team)) + } + } + } + Fix => { + 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); + } + } + Unfix => { + if client.is_admin() { + room.set_is_fixed(false); + } + } + Greeting(text) => { + if client.is_admin() || client.is_master() && !room.is_fixed() { + room.greeting = text; + } + } + MaxTeams(count) => { + if !client.is_master() { + response.add(Warning("You're not the room master!".to_string()).send_self()); + } else if !(2..=MAX_TEAMS_IN_ROOM).contains(&count) { + response + .add(Warning("/maxteams: specify number from 2 to 8".to_string()).send_self()); + } else { + server.rooms[room_id].max_teams = count; + } + } + RoomName(new_name) => { + if is_name_illegal(&new_name) { + response.add(Warning("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: $()*+?[]^{|}".to_string()).send_self()); + } else if server.has_room(&new_name) { + response.add( + Warning("A room with the same name already exists.".to_string()).send_self(), + ); + } else { + let room = &mut server.rooms[room_id]; + if room.is_fixed() || room.master_id != Some(client_id) { + response.add(Warning("Access denied.".to_string()).send_self()); + } else { + let mut old_name = new_name.clone(); + let client = &server.clients[client_id]; + swap(&mut room.name, &mut old_name); + super::common::get_room_update(Some(old_name), room, Some(&client), response); + } + } + } + ToggleReady => { + let flags = if client.is_ready() { + room.ready_players_number -= 1; + remove_flags(&[Flags::Ready]) + } else { + room.ready_players_number += 1; + add_flags(&[Flags::Ready]) + }; + + let msg = if client.protocol_number < 38 { + LegacyReady(client.is_ready(), vec![client.nick.clone()]) + } else { + ClientFlags(flags, vec![client.nick.clone()]) + }; + response.add(msg.send_all().in_room(room.id)); + client.set_is_ready(!client.is_ready()); + + if room.is_fixed() && room.ready_players_number == room.players_number { + super::common::start_game(server, room_id, response); + } + } + AddTeam(mut info) => { + if room.teams.len() >= room.max_teams as usize { + response.add(Warning("Too many teams!".to_string()).send_self()); + } else if room.addable_hedgehogs() == 0 { + response.add(Warning("Too many hedgehogs!".to_string()).send_self()); + } else if room.find_team(|t| t.name == info.name) != None { + response.add( + Warning("There's already a team with same name in the list.".to_string()) + .send_self(), + ); + } else if room.game_info.is_some() { + response.add( + Warning("Joining not possible: Round is in progress.".to_string()).send_self(), + ); + } else if room.is_team_add_restricted() { + response.add( + Warning("This room currently does not allow adding new teams.".to_string()) + .send_self(), + ); + } 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), + ); + + let room_master = if let Some(id) = room.master_id { + Some(&server.clients[id]) + } else { + None + }; + super::common::get_room_update(None, room, room_master, response); + } + } + RemoveTeam(name) => match room.find_team_owner(&name) { + None => response.add( + Warning("Error: The team you tried to remove does not exist.".to_string()) + .send_self(), + ), + Some((id, _)) if id != client_id => response + .add(Warning("You can't remove a team you don't own.".to_string()).send_self()), + Some((_, name)) => { + client.teams_in_game -= 1; + client.clan = room.find_team_color(client.id); + super::common::remove_teams( + room, + vec![name.to_string()], + client.is_in_game(), + response, + ); + + match room.game_info { + Some(ref info) if info.teams_in_game == 0 => { + super::common::end_game(server, room_id, 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) { + let max_hedgehogs = min( + MAX_HEDGEHOGS_PER_TEAM, + addable_hedgehogs + team.hedgehogs_number, + ); + if !client.is_master() { + response.add(Error("You're not the room master!".to_string()).send_self()); + } else if !(1..=max_hedgehogs).contains(&number) { + response + .add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send_self()); + } else { + team.hedgehogs_number = number; + response.add( + HedgehogsNumber(team.name.clone(), number) + .send_all() + .in_room(room_id) + .but_self(), + ); + } + } else { + response.add(Warning("No such team.".to_string()).send_self()); + } + } + SetTeamColor(team_name, color) => { + if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { + if !client.is_master() { + response.add(Error("You're not the room master!".to_string()).send_self()); + } else { + team.color = color; + response.add( + TeamColor(team.name.clone(), color) + .send_all() + .in_room(room_id) + .but_self(), + ); + server.clients[owner].clan = Some(color); + } + } else { + response.add(Warning("No such team.".to_string()).send_self()); + } + } + Cfg(cfg) => { + if room.is_fixed() { + response.add(Warning("Access denied.".to_string()).send_self()); + } else if !client.is_master() { + response.add(Error("You're not the room master!".to_string()).send_self()); + } 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, + }; + + response.add(cfg.to_server_msg().send_all().in_room(room.id).but_self()); + room.set_config(cfg); + } + } + Save(name, location) => { + response.add( + server_chat(format!("Room config saved as {}", name)) + .send_all() + .in_room(room_id), + ); + room.save_config(name, location); + } + #[cfg(feature = "official-server")] + SaveRoom(filename) => { + if client.is_admin() { + match room.get_saves() { + Ok(contents) => response.request_io(super::IoTask::SaveRoom { + room_id, + filename, + contents, + }), + Err(e) => { + warn!("Error while serializing the room configs: {}", e); + response.add( + Warning("Unable to serialize the room configs.".to_string()) + .send_self(), + ) + } + } + } + } + #[cfg(feature = "official-server")] + LoadRoom(filename) => { + if client.is_admin() { + response.request_io(super::IoTask::LoadRoom { room_id, filename }); + } + } + Delete(name) => { + if !room.delete_config(&name) { + response.add(Warning(format!("Save doesn't exist: {}", name)).send_self()); + } else { + response.add( + server_chat(format!("Room config {} has been deleted", name)) + .send_all() + .in_room(room_id), + ); + } + } + CallVote(None) => { + response.add(server_chat("Available callvote commands: kick , map , pause, newseed, hedgehogs ".to_string()) + .send_self()); + } + CallVote(Some(kind)) => { + let is_in_game = room.game_info.is_some(); + let error = match &kind { + VoteType::Kick(nick) => { + if server + .find_client(&nick) + .filter(|c| c.room_id == Some(room_id)) + .is_some() + { + None + } else { + Some("/callvote kick: No such user!".to_string()) + } + } + VoteType::Map(None) => { + let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect(); + if names.is_empty() { + Some("/callvote map: No maps saved in this room!".to_string()) + } else { + Some(format!("Available maps: {}", names.join(", "))) + } + } + VoteType::Map(Some(name)) => { + if room.saves.get(&name[..]).is_some() { + None + } else { + Some("/callvote map: No such map!".to_string()) + } + } + VoteType::Pause => { + if is_in_game { + None + } else { + Some("/callvote pause: No game in progress!".to_string()) + } + } + VoteType::NewSeed => None, + VoteType::HedgehogsPerTeam(number) => match number { + 1...MAX_HEDGEHOGS_PER_TEAM => None, + _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()), + }, + }; + + match error { + None => { + let msg = voting_description(&kind); + let voting = Voting::new(kind, server.room_clients(client_id).collect()); + let room = &mut server.rooms[room_id]; + room.voting = Some(voting); + response.add(server_chat(msg).send_all().in_room(room_id)); + super::common::submit_vote( + server, + types::Vote { + is_pro: true, + is_forced: false, + }, + response, + ); + } + Some(msg) => { + response.add(server_chat(msg).send_self()); + } + } + } + Vote(vote) => { + super::common::submit_vote( + server, + types::Vote { + is_pro: vote, + is_forced: false, + }, + response, + ); + } + ForceVote(vote) => { + let is_forced = client.is_admin(); + super::common::submit_vote( + server, + types::Vote { + is_pro: vote, + is_forced, + }, + response, + ); + } + ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => { + if client.is_master() { + room.flags.toggle(room_message_flag(&message)); + super::common::get_room_update(None, room, Some(&client), response); + } + } + StartGame => { + super::common::start_game(server, room_id, response); + } + EngineMessage(em) => { + if client.teams_in_game > 0 { + let decoding = decode(&em[..]).unwrap(); + let messages = by_msg(&decoding); + let valid = messages.filter(|m| is_msg_valid(m, &client.team_indices)); + let non_empty = valid.clone().filter(|m| !is_msg_empty(m)); + let sync_msg = valid.clone().filter(|m| is_msg_timed(m)).last().map(|m| { + if is_msg_empty(m) { + Some(encode(m)) + } else { + None + } + }); + + let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::>()); + if !em_response.is_empty() { + response.add( + ForwardEngineMessage(vec![em_response]) + .send_all() + .in_room(room.id) + .but_self(), + ); + } + let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::>()); + if let Some(ref mut info) = room.game_info { + if !em_log.is_empty() { + info.msg_log.push(em_log); + } + if let Some(msg) = sync_msg { + info.sync_msg = msg; + } + } + } + } + RoundFinished => { + let mut game_ended = false; + if client.is_in_game() { + client.set_is_in_game(false); + 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(), + ); + } + } + } + if game_ended { + super::common::end_game(server, room_id, response) + } + } + Rnd(v) => { + let result = rnd_reply(&v); + let mut echo = vec!["/rnd".to_string()]; + echo.extend(v.into_iter()); + let chat_msg = ChatMsg { + nick: server.clients[client_id].nick.clone(), + msg: echo.join(" "), + }; + response.add(chat_msg.send_all().in_room(room_id)); + response.add(result.send_all().in_room(room_id)); + } + Delegate(nick) => { + let delegate_id = server.find_client(&nick).map(|c| (c.id, c.room_id)); + let client = &server.clients[client_id]; + if !(client.is_admin() || client.is_master()) { + response.add( + Warning("You're not the room master or a server admin!".to_string()) + .send_self(), + ) + } else { + match delegate_id { + None => response.add(Warning("Player is not online.".to_string()).send_self()), + Some((id, _)) if id == client_id => response + .add(Warning("You're already the room master.".to_string()).send_self()), + Some((_, id)) if id != Some(room_id) => response + .add(Warning("The player is not in your room.".to_string()).send_self()), + Some((id, _)) => { + super::common::change_master(server, room_id, id, response); + } + } + } + } + _ => warn!("Unimplemented!"), + } +} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/main.rs --- a/rust/hedgewars-server/src/main.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/main.rs Tue May 28 19:04:18 2019 +0300 @@ -6,7 +6,9 @@ use mio::{net::*, *}; use std::{env, str::FromStr as _, time::Duration}; +mod core; mod protocol; +mod handlers; mod server; mod utils; diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/protocol.rs --- a/rust/hedgewars-server/src/protocol.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/protocol.rs Tue May 28 19:04:18 2019 +0300 @@ -1,4 +1,4 @@ -use crate::protocol::parser::message; +use self::parser::message; use log::*; use netbuf; use nom::{Err, ErrorKind, IResult}; diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/protocol/messages.rs --- a/rust/hedgewars-server/src/protocol/messages.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/protocol/messages.rs Tue May 28 19:04:18 2019 +0300 @@ -1,4 +1,4 @@ -use crate::server::coretypes::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType}; +use crate::core::types::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType}; use std::{convert::From, iter::once, ops}; #[derive(PartialEq, Eq, Clone, Debug)] @@ -183,7 +183,7 @@ impl GameCfg { pub fn to_protocol(&self) -> (String, Vec) { - use crate::server::coretypes::GameCfg::*; + use crate::core::types::GameCfg::*; match self { FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]), MapType(t) => ("MAP".to_string(), vec![t.to_string()]), diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/protocol/parser.rs --- a/rust/hedgewars-server/src/protocol/parser.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/protocol/parser.rs Tue May 28 19:04:18 2019 +0300 @@ -14,8 +14,10 @@ str::{FromStr, Utf8Error}, }; -use super::messages::{HWProtocolMessage, HWProtocolMessage::*}; -use crate::server::coretypes::{ +use super::{ + messages::{HWProtocolMessage, HWProtocolMessage::*}, +}; +use crate::core::types::{ GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType, MAX_HEDGEHOGS_PER_TEAM, }; diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/protocol/test.rs --- a/rust/hedgewars-server/src/protocol/test.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/protocol/test.rs Tue May 28 19:04:18 2019 +0300 @@ -4,7 +4,7 @@ test_runner::{Reason, TestRunner}, }; -use crate::server::coretypes::{GameCfg, HedgehogInfo, ServerVar, ServerVar::*, TeamInfo}; +use crate::core::types::{GameCfg, HedgehogInfo, ServerVar, ServerVar::*, TeamInfo}; use super::messages::{HWProtocolMessage, HWProtocolMessage::*}; @@ -75,7 +75,7 @@ type Parameters = (); fn arbitrary_with(_args: ::Parameters) -> ::Strategy { - use crate::server::coretypes::GameCfg::*; + use crate::core::types::GameCfg::*; (0..10) .no_shrink() .prop_flat_map(|i| { diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server.rs --- a/rust/hedgewars-server/src/server.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/server.rs Tue May 28 19:04:18 2019 +0300 @@ -1,12 +1,5 @@ -mod actions; -pub mod client; -pub mod core; -pub mod coretypes; #[cfg(feature = "official-server")] mod database; -mod handlers; -pub mod indexslab; #[cfg(feature = "official-server")] pub mod io; -pub mod network; -pub mod room; +pub mod network; \ No newline at end of file diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/actions.rs --- a/rust/hedgewars-server/src/server/actions.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -use super::{ - client::HWClient, - core::HWServer, - coretypes::{ClientId, GameCfg, RoomId, VoteType}, - handlers, - room::HWRoom, - room::{GameInfo, RoomFlags}, -}; -use crate::{ - protocol::messages::{server_chat, HWProtocolMessage, HWServerMessage, HWServerMessage::*}, - utils::to_engine_msg, -}; -use rand::{distributions::Uniform, thread_rng, Rng}; -use std::{io, io::Write, iter::once, mem::replace}; - -#[cfg(feature = "official-server")] -use super::database; - -pub enum DestinationGroup { - All, - Lobby, - Room(RoomId), - Protocol(u16), -} - -pub enum Destination { - ToId(ClientId), - ToIds(Vec), - ToSelf, - ToAll { - group: DestinationGroup, - skip_self: bool, - }, -} - -pub struct PendingMessage { - pub destination: Destination, - pub message: HWServerMessage, -} - -impl PendingMessage { - pub fn send(message: HWServerMessage, client_id: ClientId) -> PendingMessage { - PendingMessage { - destination: Destination::ToId(client_id), - message, - } - } - - pub fn send_many(message: HWServerMessage, client_ids: Vec) -> PendingMessage { - PendingMessage { - destination: Destination::ToIds(client_ids), - message, - } - } - - pub fn send_self(message: HWServerMessage) -> PendingMessage { - PendingMessage { - destination: Destination::ToSelf, - message, - } - } - - pub fn send_all(message: HWServerMessage) -> PendingMessage { - let destination = Destination::ToAll { - group: DestinationGroup::All, - skip_self: false, - }; - PendingMessage { - destination, - message, - } - } - - pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage { - if let Destination::ToAll { ref mut group, .. } = self.destination { - *group = DestinationGroup::Room(clients_room_id) - } - self - } - - pub fn in_lobby(mut self) -> PendingMessage { - if let Destination::ToAll { ref mut group, .. } = self.destination { - *group = DestinationGroup::Lobby - } - self - } - - pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage { - if let Destination::ToAll { ref mut group, .. } = self.destination { - *group = DestinationGroup::Protocol(protocol_number) - } - self - } - - pub fn but_self(mut self) -> PendingMessage { - if let Destination::ToAll { - ref mut skip_self, .. - } = self.destination - { - *skip_self = true - } - self - } -} - -impl HWServerMessage { - pub fn send(self, client_id: ClientId) -> PendingMessage { - PendingMessage::send(self, client_id) - } - pub fn send_many(self, client_ids: Vec) -> PendingMessage { - PendingMessage::send_many(self, client_ids) - } - pub fn send_self(self) -> PendingMessage { - PendingMessage::send_self(self) - } - pub fn send_all(self) -> PendingMessage { - PendingMessage::send_all(self) - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/client.rs --- a/rust/hedgewars-server/src/server/client.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -use super::coretypes::ClientId; -use bitflags::*; - -bitflags! { - pub struct ClientFlags: u16 { - const IS_ADMIN = 0b0000_0001; - const IS_MASTER = 0b0000_0010; - const IS_READY = 0b0000_0100; - const IS_IN_GAME = 0b0000_1000; - const IS_JOINED_MID_GAME = 0b0001_0000; - const IS_CHECKER = 0b0010_0000; - const IS_CONTRIBUTOR = 0b0100_0000; - const HAS_SUPER_POWER = 0b1000_0000; - const IS_REGISTERED = 0b0001_0000_0000; - - const NONE = 0b0000_0000; - const DEFAULT = Self::NONE.bits; - } -} - -pub struct HWClient { - pub id: ClientId, - pub room_id: Option, - pub nick: String, - pub protocol_number: u16, - pub flags: ClientFlags, - pub teams_in_game: u8, - pub team_indices: Vec, - pub clan: Option, -} - -impl HWClient { - pub fn new(id: ClientId, protocol_number: u16, nick: String) -> HWClient { - HWClient { - id, - nick, - protocol_number, - room_id: None, - flags: ClientFlags::DEFAULT, - teams_in_game: 0, - team_indices: Vec::new(), - clan: None, - } - } - - fn contains(&self, mask: ClientFlags) -> bool { - self.flags.contains(mask) - } - - fn set(&mut self, mask: ClientFlags, value: bool) { - self.flags.set(mask, value); - } - - pub fn is_admin(&self) -> bool { - self.contains(ClientFlags::IS_ADMIN) - } - pub fn is_master(&self) -> bool { - self.contains(ClientFlags::IS_MASTER) - } - pub fn is_ready(&self) -> bool { - self.contains(ClientFlags::IS_READY) - } - pub fn is_in_game(&self) -> bool { - self.contains(ClientFlags::IS_IN_GAME) - } - pub fn is_joined_mid_game(&self) -> bool { - self.contains(ClientFlags::IS_JOINED_MID_GAME) - } - pub fn is_checker(&self) -> bool { - self.contains(ClientFlags::IS_CHECKER) - } - pub fn is_contributor(&self) -> bool { - self.contains(ClientFlags::IS_CONTRIBUTOR) - } - pub fn has_super_power(&self) -> bool { - self.contains(ClientFlags::HAS_SUPER_POWER) - } - pub fn is_registered(&self) -> bool { - self.contains(ClientFlags::IS_REGISTERED) - } - - pub fn set_is_admin(&mut self, value: bool) { - self.set(ClientFlags::IS_ADMIN, value) - } - pub fn set_is_master(&mut self, value: bool) { - self.set(ClientFlags::IS_MASTER, value) - } - pub fn set_is_ready(&mut self, value: bool) { - self.set(ClientFlags::IS_READY, value) - } - pub fn set_is_in_game(&mut self, value: bool) { - self.set(ClientFlags::IS_IN_GAME, value) - } - pub fn set_is_joined_mid_game(&mut self, value: bool) { - self.set(ClientFlags::IS_JOINED_MID_GAME, value) - } - pub fn set_is_checker(&mut self, value: bool) { - self.set(ClientFlags::IS_CHECKER, value) - } - pub fn set_is_contributor(&mut self, value: bool) { - self.set(ClientFlags::IS_CONTRIBUTOR, value) - } - pub fn set_has_super_power(&mut self, value: bool) { - self.set(ClientFlags::HAS_SUPER_POWER, value) - } - pub fn set_is_registered(&mut self, value: bool) { - self.set(ClientFlags::IS_REGISTERED, value) - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/core.rs --- a/rust/hedgewars-server/src/server/core.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -use super::{ - client::HWClient, - coretypes::{ClientId, RoomId}, - indexslab::IndexSlab, - room::HWRoom, -}; -use crate::utils; - -use crate::protocol::messages::HWProtocolMessage::Greeting; -use bitflags::*; -use log::*; -use slab; -use std::{borrow::BorrowMut, iter, num::NonZeroU16}; - -type Slab = slab::Slab; - -pub struct HWAnteClient { - pub nick: Option, - pub protocol_number: Option, - pub server_salt: String, - pub is_checker: bool, -} - -pub struct HWAnteroom { - pub clients: IndexSlab, -} - -impl HWAnteroom { - pub fn new(clients_limit: usize) -> Self { - let clients = IndexSlab::with_capacity(clients_limit); - HWAnteroom { clients } - } - - pub fn add_client(&mut self, client_id: ClientId, salt: String) { - let client = HWAnteClient { - nick: None, - protocol_number: None, - server_salt: salt, - is_checker: false, - }; - self.clients.insert(client_id, client); - } - - pub fn remove_client(&mut self, client_id: ClientId) -> Option { - let mut client = self.clients.remove(client_id); - client - } -} - -pub struct ServerGreetings { - pub for_latest_protocol: String, - pub for_old_protocols: String, -} - -impl ServerGreetings { - fn new() -> Self { - Self { - for_latest_protocol: "\u{1f994} is watching".to_string(), - for_old_protocols: "\u{1f994} is watching".to_string(), - } - } -} - -bitflags! { - pub struct ServerFlags: u8 { - const REGISTERED_ONLY = 0b0000_1000; - } -} - -pub struct HWServer { - pub clients: IndexSlab, - pub rooms: Slab, - pub anteroom: HWAnteroom, - pub latest_protocol: u16, - pub flags: ServerFlags, - pub greetings: ServerGreetings, -} - -impl HWServer { - pub fn new(clients_limit: usize, rooms_limit: usize) -> Self { - let rooms = Slab::with_capacity(rooms_limit); - let clients = IndexSlab::with_capacity(clients_limit); - Self { - clients, - rooms, - anteroom: HWAnteroom::new(clients_limit), - greetings: ServerGreetings::new(), - latest_protocol: 58, - flags: ServerFlags::empty(), - } - } - - pub fn add_client(&mut self, client_id: ClientId, data: HWAnteClient) { - if let (Some(protocol), Some(nick)) = (data.protocol_number, data.nick) { - let mut client = HWClient::new(client_id, protocol.get(), nick); - client.set_is_checker(data.is_checker); - self.clients.insert(client_id, client); - } - } - - pub fn remove_client(&mut self, client_id: ClientId) { - self.clients.remove(client_id); - } - - pub fn get_greetings(&self, client_id: ClientId) -> &str { - if self.clients[client_id].protocol_number < self.latest_protocol { - &self.greetings.for_old_protocols - } else { - &self.greetings.for_latest_protocol - } - } - - #[inline] - pub fn create_room( - &mut self, - creator_id: ClientId, - name: String, - password: Option, - ) -> RoomId { - create_room( - &mut self.clients[creator_id], - &mut self.rooms, - name, - password, - ) - } - - #[inline] - pub fn move_to_room(&mut self, client_id: ClientId, room_id: RoomId) { - move_to_room(&mut self.clients[client_id], &mut self.rooms[room_id]) - } - - pub fn has_room(&self, name: &str) -> bool { - self.find_room(name).is_some() - } - - pub fn find_room(&self, name: &str) -> Option<&HWRoom> { - self.rooms - .iter() - .find_map(|(_, r)| Some(r).filter(|r| r.name == name)) - } - - pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HWRoom> { - self.rooms - .iter_mut() - .find_map(|(_, r)| Some(r).filter(|r| r.name == name)) - } - - pub fn find_client(&self, nick: &str) -> Option<&HWClient> { - self.clients - .iter() - .find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) - } - - pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> { - self.clients - .iter_mut() - .find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) - } - - pub fn all_clients(&self) -> impl Iterator + '_ { - self.clients.iter().map(|(id, _)| id) - } - - pub fn filter_clients<'a, F>(&'a self, f: F) -> impl Iterator + 'a - where - F: Fn(&(usize, &HWClient)) -> bool + 'a, - { - self.clients.iter().filter(f).map(|(_, c)| c.id) - } - - pub fn filter_rooms<'a, F>(&'a self, f: F) -> impl Iterator + 'a - where - F: Fn(&(usize, &HWRoom)) -> bool + 'a, - { - self.rooms.iter().filter(f).map(|(_, c)| c.id) - } - - pub fn collect_clients(&self, f: F) -> Vec - where - F: Fn(&(usize, &HWClient)) -> bool, - { - self.filter_clients(f).collect() - } - - pub fn collect_nicks(&self, f: F) -> Vec - where - F: Fn(&(usize, &HWClient)) -> bool, - { - self.clients - .iter() - .filter(f) - .map(|(_, c)| c.nick.clone()) - .collect() - } - - pub fn lobby_clients(&self) -> impl Iterator + '_ { - self.filter_clients(|(_, c)| c.room_id == None) - } - - pub fn room_clients(&self, room_id: RoomId) -> impl Iterator + '_ { - self.filter_clients(move |(_, c)| c.room_id == Some(room_id)) - } - - pub fn protocol_clients(&self, protocol: u16) -> impl Iterator + '_ { - self.filter_clients(move |(_, c)| c.protocol_number == protocol) - } - - pub fn protocol_rooms(&self, protocol: u16) -> impl Iterator + '_ { - self.filter_rooms(move |(_, r)| r.protocol_number == protocol) - } - - pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec { - let room_id = self.clients[self_id].room_id; - self.collect_clients(|(id, c)| *id != self_id && c.room_id == room_id) - } - - pub fn is_registered_only(&self) -> bool { - self.flags.contains(ServerFlags::REGISTERED_ONLY) - } - - pub fn set_is_registered_only(&mut self, value: bool) { - self.flags.set(ServerFlags::REGISTERED_ONLY, value) - } -} - -fn allocate_room(rooms: &mut Slab) -> &mut HWRoom { - let entry = rooms.vacant_entry(); - let room = HWRoom::new(entry.key()); - entry.insert(room) -} - -fn create_room( - client: &mut HWClient, - rooms: &mut Slab, - name: String, - password: Option, -) -> RoomId { - let room = allocate_room(rooms); - - room.master_id = Some(client.id); - room.name = name; - room.password = password; - room.protocol_number = client.protocol_number; - - room.players_number = 1; - room.ready_players_number = 1; - - client.room_id = Some(room.id); - client.set_is_master(true); - client.set_is_ready(true); - client.set_is_joined_mid_game(false); - - room.id -} - -fn move_to_room(client: &mut HWClient, room: &mut HWRoom) { - debug_assert!(client.room_id != Some(room.id)); - - room.players_number += 1; - - client.room_id = Some(room.id); - client.set_is_joined_mid_game(room.game_info.is_some()); - client.set_is_in_game(room.game_info.is_some()); - - if let Some(ref mut info) = room.game_info { - let teams = info.client_teams(client.id); - client.teams_in_game = teams.clone().count() as u8; - client.clan = teams.clone().next().map(|t| t.color); - let team_names: Vec<_> = teams.map(|t| t.name.clone()).collect(); - - if !team_names.is_empty() { - info.left_teams.retain(|name| !team_names.contains(&name)); - info.teams_in_game += team_names.len() as u8; - room.teams = info - .teams_at_start - .iter() - .filter(|(_, t)| !team_names.contains(&t.name)) - .cloned() - .collect(); - } - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/coretypes.rs --- a/rust/hedgewars-server/src/server/coretypes.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; - -pub type ClientId = usize; -pub type RoomId = usize; - -pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8; - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum ServerVar { - MOTDNew(String), - MOTDOld(String), - LatestProto(u16), -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum GameCfg { - FeatureSize(u32), - MapType(String), - MapGenerator(u32), - MazeSize(u32), - Seed(String), - Template(u32), - - Ammo(String, Option), - Scheme(String, Vec), - Script(String), - Theme(String), - DrawnMap(String), -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct TeamInfo { - pub owner: String, - pub name: String, - pub color: u8, - pub grave: String, - pub fort: String, - pub voice_pack: String, - pub flag: String, - pub difficulty: u8, - pub hedgehogs_number: u8, - pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize], -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct HedgehogInfo { - pub name: String, - pub hat: String, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct Ammo { - pub name: String, - pub settings: Option, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct Scheme { - pub name: String, - pub settings: Vec, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct RoomConfig { - pub feature_size: u32, - pub map_type: String, - pub map_generator: u32, - pub maze_size: u32, - pub seed: String, - pub template: u32, - - pub ammo: Ammo, - pub scheme: Scheme, - pub script: String, - pub theme: String, - pub drawn_map: Option, -} - -impl RoomConfig { - pub fn new() -> RoomConfig { - RoomConfig { - feature_size: 12, - map_type: "+rnd+".to_string(), - map_generator: 0, - maze_size: 0, - seed: "seed".to_string(), - template: 0, - - ammo: Ammo { - name: "Default".to_string(), - settings: None, - }, - scheme: Scheme { - name: "Default".to_string(), - settings: Vec::new(), - }, - script: "Normal".to_string(), - theme: "\u{1f994}".to_string(), - drawn_map: None, - } - } - - pub fn set_config(&mut self, cfg: GameCfg) { - match cfg { - GameCfg::FeatureSize(s) => self.feature_size = s, - GameCfg::MapType(t) => self.map_type = t, - GameCfg::MapGenerator(g) => self.map_generator = g, - GameCfg::MazeSize(s) => self.maze_size = s, - GameCfg::Seed(s) => self.seed = s, - GameCfg::Template(t) => self.template = t, - - GameCfg::Ammo(n, s) => { - self.ammo = Ammo { - name: n, - settings: s, - } - } - GameCfg::Scheme(n, s) => { - self.scheme = Scheme { - name: n, - settings: s, - } - } - GameCfg::Script(s) => self.script = s, - GameCfg::Theme(t) => self.theme = t, - GameCfg::DrawnMap(m) => self.drawn_map = Some(m), - }; - } - - pub fn to_map_config(&self) -> Vec { - vec![ - self.feature_size.to_string(), - self.map_type.to_string(), - self.map_generator.to_string(), - self.maze_size.to_string(), - self.seed.to_string(), - self.template.to_string(), - ] - } - - pub fn to_game_config(&self) -> Vec { - use crate::server::coretypes::GameCfg::*; - let mut v = vec![ - Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()), - Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()), - Script(self.script.to_string()), - Theme(self.theme.to_string()), - ]; - if let Some(ref m) = self.drawn_map { - v.push(DrawnMap(m.to_string())) - } - v - } -} - -pub struct Replay { - pub config: RoomConfig, - pub teams: Vec, - pub message_log: Vec, -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum VoteType { - Kick(String), - Map(Option), - Pause, - NewSeed, - HedgehogsPerTeam(u8), -} - -pub struct Vote { - pub is_pro: bool, - pub is_forced: bool, -} - -#[derive(Clone, Debug)] -pub struct Voting { - pub ttl: u32, - pub voters: Vec, - pub votes: Vec<(ClientId, bool)>, - pub kind: VoteType, -} - -impl Voting { - pub fn new(kind: VoteType, voters: Vec) -> Voting { - Voting { - kind, - voters, - ttl: 2, - votes: Vec::new(), - } - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/database.rs --- a/rust/hedgewars-server/src/server/database.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/server/database.rs Tue May 28 19:04:18 2019 +0300 @@ -2,8 +2,9 @@ use mysql::{error::DriverError, error::Error, from_row_opt, params}; use openssl::sha::sha1; -use super::handlers::AccountInfo; -use crate::server::handlers::Sha1Digest; +use crate::{ + handlers::{Sha1Digest, AccountInfo} +}; const GET_ACCOUNT_QUERY: &str = r"SELECT CASE WHEN users.status = 1 THEN users.pass ELSE '' END, diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/handlers.rs --- a/rust/hedgewars-server/src/server/handlers.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,396 +0,0 @@ -use mio; -use std::{collections::HashMap, io, io::Write}; - -use super::{ - actions::{Destination, DestinationGroup}, - core::HWServer, - coretypes::{ClientId, Replay, RoomId}, - room::RoomSave, -}; -use crate::{ - protocol::messages::{server_chat, HWProtocolMessage, HWServerMessage, HWServerMessage::*}, - server::actions::PendingMessage, - utils, -}; -use base64::encode; -use log::*; -use rand::{thread_rng, RngCore}; - -mod checker; -mod common; -mod inroom; -mod lobby; -mod loggingin; - -use self::loggingin::LoginResult; -use crate::protocol::messages::global_chat; -use crate::protocol::messages::HWProtocolMessage::EngineMessage; -use crate::server::coretypes::{GameCfg, TeamInfo}; -use std::fmt::{Formatter, LowerHex}; - -#[derive(PartialEq)] -pub struct Sha1Digest([u8; 20]); - -impl Sha1Digest { - pub fn new(digest: [u8; 20]) -> Self { - Self(digest) - } -} - -impl LowerHex for Sha1Digest { - fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { - for byte in &self.0 { - write!(f, "{:02x}", byte)?; - } - Ok(()) - } -} - -pub struct AccountInfo { - pub is_registered: bool, - pub is_admin: bool, - pub is_contributor: bool, - pub server_hash: Sha1Digest, -} - -pub enum IoTask { - GetAccount { - nick: String, - protocol: u16, - password_hash: String, - client_salt: String, - server_salt: String, - }, - GetReplay { - id: u32, - }, - SaveRoom { - room_id: RoomId, - filename: String, - contents: String, - }, - LoadRoom { - room_id: RoomId, - filename: String, - }, -} - -pub enum IoResult { - Account(Option), - Replay(Option), - SaveRoom(RoomId, bool), - LoadRoom(RoomId, Option), -} - -pub struct Response { - client_id: ClientId, - messages: Vec, - io_tasks: Vec, - removed_clients: Vec, -} - -impl Response { - pub fn new(client_id: ClientId) -> Self { - Self { - client_id, - messages: vec![], - io_tasks: vec![], - removed_clients: vec![], - } - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.messages.is_empty() && self.removed_clients.is_empty() && self.io_tasks.is_empty() - } - - #[inline] - pub fn len(&self) -> usize { - self.messages.len() - } - - #[inline] - pub fn client_id(&self) -> ClientId { - self.client_id - } - - #[inline] - pub fn add(&mut self, message: PendingMessage) { - self.messages.push(message) - } - - #[inline] - pub fn request_io(&mut self, task: IoTask) { - self.io_tasks.push(task) - } - - pub fn extract_messages<'a, 'b: 'a>( - &'b mut self, - server: &'a HWServer, - ) -> impl Iterator, HWServerMessage)> + 'a { - let client_id = self.client_id; - self.messages.drain(..).map(move |m| { - let ids = get_recipients(server, client_id, m.destination); - (ids, m.message) - }) - } - - pub fn remove_client(&mut self, client_id: ClientId) { - self.removed_clients.push(client_id); - } - - pub fn extract_removed_clients(&mut self) -> impl Iterator + '_ { - self.removed_clients.drain(..) - } - - pub fn extract_io_tasks(&mut self) -> impl Iterator + '_ { - self.io_tasks.drain(..) - } -} - -impl Extend for Response { - fn extend>(&mut self, iter: T) { - for msg in iter { - self.add(msg) - } - } -} - -fn get_recipients( - server: &HWServer, - client_id: ClientId, - destination: Destination, -) -> Vec { - match destination { - Destination::ToSelf => vec![client_id], - Destination::ToId(id) => vec![id], - Destination::ToIds(ids) => ids, - Destination::ToAll { group, skip_self } => { - let mut ids: Vec<_> = match group { - DestinationGroup::All => server.all_clients().collect(), - DestinationGroup::Lobby => server.lobby_clients().collect(), - DestinationGroup::Protocol(proto) => server.protocol_clients(proto).collect(), - DestinationGroup::Room(id) => server.room_clients(id).collect(), - }; - - if skip_self { - if let Some(index) = ids.iter().position(|id| *id == client_id) { - ids.remove(index); - } - } - - ids - } - } -} - -pub fn handle( - server: &mut HWServer, - client_id: ClientId, - response: &mut Response, - message: HWProtocolMessage, -) { - match message { - HWProtocolMessage::Ping => response.add(Pong.send_self()), - _ => { - if server.anteroom.clients.contains(client_id) { - match loggingin::handle(server, client_id, response, message) { - LoginResult::Unchanged => (), - LoginResult::Complete => { - if let Some(client) = server.anteroom.remove_client(client_id) { - server.add_client(client_id, client); - common::join_lobby(server, response); - } - } - LoginResult::Exit => { - server.anteroom.remove_client(client_id); - response.remove_client(client_id); - } - } - } else if server.clients.contains(client_id) { - match message { - HWProtocolMessage::Quit(Some(msg)) => { - common::remove_client(server, response, "User quit: ".to_string() + &msg); - } - HWProtocolMessage::Quit(None) => { - common::remove_client(server, response, "User quit".to_string()); - } - HWProtocolMessage::Info(nick) => { - if let Some(client) = server.find_client(&nick) { - let admin_sign = if client.is_admin() { "@" } else { "" }; - let master_sign = if client.is_master() { "+" } else { "" }; - let room_info = match client.room_id { - Some(room_id) => { - let room = &server.rooms[room_id]; - let status = match room.game_info { - Some(_) if client.teams_in_game == 0 => "(spectating)", - Some(_) => "(playing)", - None => "", - }; - format!( - "[{}{}room {}]{}", - admin_sign, master_sign, room.name, status - ) - } - None => format!("[{}lobby]", admin_sign), - }; - - let info = vec![ - client.nick.clone(), - "[]".to_string(), - utils::protocol_version_string(client.protocol_number).to_string(), - room_info, - ]; - response.add(Info(info).send_self()) - } else { - response - .add(server_chat("Player is not online.".to_string()).send_self()) - } - } - HWProtocolMessage::ToggleServerRegisteredOnly => { - if !server.clients[client_id].is_admin() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - server.set_is_registered_only(server.is_registered_only()); - let msg = if server.is_registered_only() { - "This server no longer allows unregistered players to join." - } else { - "This server now allows unregistered players to join." - }; - response.add(server_chat(msg.to_string()).send_all()); - } - } - HWProtocolMessage::Global(msg) => { - if !server.clients[client_id].is_admin() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - response.add(global_chat(msg).send_all()) - } - } - HWProtocolMessage::SuperPower => { - if !server.clients[client_id].is_admin() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - server.clients[client_id].set_has_super_power(true); - response - .add(server_chat("Super power activated.".to_string()).send_self()) - } - } - HWProtocolMessage::Watch(id) => { - #[cfg(feature = "official-server")] - { - response.request_io(IoTask::GetReplay { id }) - } - - #[cfg(not(feature = "official-server"))] - { - response.add( - Warning("This server does not support replays!".to_string()) - .send_self(), - ); - } - } - _ => match server.clients[client_id].room_id { - None => lobby::handle(server, client_id, response, message), - Some(room_id) => { - inroom::handle(server, client_id, response, room_id, message) - } - }, - } - } - } - } -} - -pub fn handle_client_accept(server: &mut HWServer, client_id: ClientId, response: &mut Response) { - let mut salt = [0u8; 18]; - thread_rng().fill_bytes(&mut salt); - - server.anteroom.add_client(client_id, encode(&salt)); - - response.add(HWServerMessage::Connected(utils::SERVER_VERSION).send_self()); -} - -pub fn handle_client_loss(server: &mut HWServer, client_id: ClientId, response: &mut Response) { - if server.anteroom.remove_client(client_id).is_none() { - common::remove_client(server, response, "Connection reset".to_string()); - } -} - -pub fn handle_io_result( - server: &mut HWServer, - client_id: ClientId, - response: &mut Response, - io_result: IoResult, -) { - match io_result { - IoResult::Account(Some(info)) => { - if !info.is_registered && server.is_registered_only() { - response.add( - Bye("This server only allows registered users to join.".to_string()) - .send_self(), - ); - response.remove_client(client_id); - } else { - response.add(ServerAuth(format!("{:x}", info.server_hash)).send_self()); - if let Some(client) = server.anteroom.remove_client(client_id) { - server.add_client(client_id, client); - let client = &mut server.clients[client_id]; - client.set_is_registered(info.is_registered); - client.set_is_admin(info.is_admin); - client.set_is_contributor(info.is_admin) - } - } - } - IoResult::Account(None) => { - response.add(Error("Authentication failed.".to_string()).send_self()); - response.remove_client(client_id); - } - IoResult::Replay(Some(replay)) => { - let protocol = server.clients[client_id].protocol_number; - let start_msg = if protocol < 58 { - RoomJoined(vec![server.clients[client_id].nick.clone()]) - } else { - ReplayStart - }; - response.add(start_msg.send_self()); - - common::get_room_config_impl(&replay.config, client_id, response); - common::get_teams(replay.teams.iter(), client_id, response); - response.add(RunGame.send_self()); - response.add(ForwardEngineMessage(replay.message_log).send_self()); - - if protocol < 58 { - response.add(Kicked.send_self()); - } - } - IoResult::Replay(None) => { - response.add(Warning("Could't load the replay".to_string()).send_self()) - } - IoResult::SaveRoom(_, true) => { - response.add(server_chat("Room configs saved successfully.".to_string()).send_self()); - } - IoResult::SaveRoom(_, false) => { - response.add(Warning("Unable to save the room configs.".to_string()).send_self()); - } - IoResult::LoadRoom(room_id, Some(contents)) => { - if let Some(ref mut room) = server.rooms.get_mut(room_id) { - match room.set_saves(&contents) { - Ok(_) => response.add( - server_chat("Room configs loaded successfully.".to_string()).send_self(), - ), - Err(e) => { - warn!("Error while deserializing the room configs: {}", e); - response.add( - Warning("Unable to deserialize the room configs.".to_string()) - .send_self(), - ); - } - } - } - } - IoResult::LoadRoom(_, None) => { - response.add(Warning("Unable to load the room configs.".to_string()).send_self()); - } - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/handlers/checker.rs --- a/rust/hedgewars-server/src/server/handlers/checker.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -use log::*; -use mio; - -use crate::{ - protocol::messages::HWProtocolMessage, - server::{core::HWServer, coretypes::ClientId}, -}; - -pub fn handle(_server: &mut HWServer, _client_id: ClientId, message: HWProtocolMessage) { - match message { - _ => warn!("Unknown command"), - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/handlers/common.rs --- a/rust/hedgewars-server/src/server/handlers/common.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,661 +0,0 @@ -use crate::{ - protocol::messages::server_chat, - protocol::messages::{ - add_flags, remove_flags, - HWProtocolMessage::{self, Rnd}, - HWServerMessage::{self, *}, - ProtocolFlags as Flags, - }, - server::{ - client::HWClient, - core::HWServer, - coretypes::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType}, - room::HWRoom, - }, - utils::to_engine_msg, -}; - -use super::Response; - -use crate::server::coretypes::RoomConfig; -use rand::{self, seq::SliceRandom, thread_rng, Rng}; -use std::{iter::once, mem::replace}; - -pub fn rnd_reply(options: &[String]) -> HWServerMessage { - let mut rng = thread_rng(); - - let reply = if options.is_empty() { - (*&["heads", "tails"].choose(&mut rng).unwrap()).to_string() - } else { - options.choose(&mut rng).unwrap().clone() - }; - - ChatMsg { - nick: "[random]".to_string(), - msg: reply, - } -} - -pub fn join_lobby(server: &mut HWServer, response: &mut Response) { - let client_id = response.client_id(); - - let client = &server.clients[client_id]; - let nick = vec![client.nick.clone()]; - let mut flags = vec![]; - if client.is_registered() { - flags.push(Flags::Registered) - } - if client.is_admin() { - flags.push(Flags::Admin) - } - if client.is_contributor() { - flags.push(Flags::Contributor) - } - - let all_nicks: Vec<_> = server.collect_nicks(|_| true); - - let mut flag_selectors = [ - ( - Flags::Registered, - server.collect_nicks(|(_, c)| c.is_registered()), - ), - (Flags::Admin, server.collect_nicks(|(_, c)| c.is_admin())), - ( - Flags::Contributor, - server.collect_nicks(|(_, c)| c.is_contributor()), - ), - ( - Flags::InRoom, - server.collect_nicks(|(_, c)| c.room_id.is_some()), - ), - ]; - - let server_msg = ServerMessage(server.get_greetings(client_id).to_string()); - - let rooms_msg = Rooms( - server - .rooms - .iter() - .filter(|(_, r)| r.protocol_number == client.protocol_number) - .flat_map(|(_, r)| r.info(r.master_id.map(|id| &server.clients[id]))) - .collect(), - ); - - response.add(LobbyJoined(nick).send_all().but_self()); - response.add( - ClientFlags(add_flags(&flags), all_nicks.clone()) - .send_all() - .but_self(), - ); - - response.add(LobbyJoined(all_nicks).send_self()); - for (flag, nicks) in &mut flag_selectors { - if !nicks.is_empty() { - response.add(ClientFlags(add_flags(&[*flag]), replace(nicks, vec![])).send_self()); - } - } - - response.add(server_msg.send_self()); - response.add(rooms_msg.send_self()); -} - -pub fn remove_teams( - room: &mut HWRoom, - team_names: Vec, - is_in_game: bool, - response: &mut Response, -) { - if let Some(ref mut info) = room.game_info { - for team_name in &team_names { - info.left_teams.push(team_name.clone()); - - if is_in_game { - 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(), - ); - - info.teams_in_game -= 1; - - 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()); - 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 { - room.remove_team(&team_name); - response.add(TeamRemove(team_name).send_all().in_room(room.id)); - } -} - -fn remove_client_from_room( - client: &mut HWClient, - room: &mut HWRoom, - response: &mut Response, - msg: &str, -) { - room.players_number -= 1; - if room.players_number > 0 || room.is_fixed() { - if client.is_ready() && room.ready_players_number > 0 { - room.ready_players_number -= 1; - } - - let team_names: Vec<_> = room - .client_teams(client.id) - .map(|t| t.name.clone()) - .collect(); - remove_teams(room, team_names, client.is_in_game(), response); - - if room.players_number > 0 { - response.add( - RoomLeft(client.nick.clone(), msg.to_string()) - .send_all() - .in_room(room.id) - .but_self(), - ); - } - - if client.is_master() && !room.is_fixed() { - client.set_is_master(false); - response.add( - ClientFlags( - remove_flags(&[Flags::RoomMaster]), - vec![client.nick.clone()], - ) - .send_all() - .in_room(room.id), - ); - room.master_id = None; - } - } - - client.room_id = None; - - let update_msg = if room.players_number == 0 && !room.is_fixed() { - RoomRemove(room.name.clone()) - } else { - RoomUpdated(room.name.clone(), room.info(Some(&client))) - }; - response.add(update_msg.send_all().with_protocol(room.protocol_number)); - - response.add(ClientFlags(remove_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_all()); -} - -pub fn change_master( - server: &mut HWServer, - room_id: RoomId, - new_master_id: ClientId, - response: &mut Response, -) { - let room = &mut server.rooms[room_id]; - if let Some(master_id) = room.master_id { - server.clients[master_id].set_is_master(false); - response.add( - ClientFlags( - remove_flags(&[Flags::RoomMaster]), - vec![server.clients[master_id].nick.clone()], - ) - .send_all() - .in_room(room_id), - ) - } - - room.master_id = Some(new_master_id); - server.clients[new_master_id].set_is_master(true); - - response.add( - ClientFlags( - add_flags(&[Flags::RoomMaster]), - vec![server.clients[new_master_id].nick.clone()], - ) - .send_all() - .in_room(room_id), - ); -} - -pub fn enter_room( - server: &mut HWServer, - client_id: ClientId, - room_id: RoomId, - response: &mut Response, -) { - let nick = server.clients[client_id].nick.clone(); - server.move_to_room(client_id, room_id); - - response.add(RoomJoined(vec![nick.clone()]).send_all().in_room(room_id)); - response.add(ClientFlags(add_flags(&[Flags::InRoom]), vec![nick]).send_all()); - let nicks = server.collect_nicks(|(_, c)| c.room_id == Some(room_id)); - response.add(RoomJoined(nicks).send_self()); - - get_room_teams(server, room_id, client_id, response); - - let room = &server.rooms[room_id]; - get_room_config(room, client_id, response); - - let mut flag_selectors = [ - ( - Flags::RoomMaster, - server.collect_nicks(|(_, c)| c.is_master()), - ), - (Flags::Ready, server.collect_nicks(|(_, c)| c.is_ready())), - (Flags::InGame, server.collect_nicks(|(_, c)| c.is_in_game())), - ]; - - for (flag, nicks) in &mut flag_selectors { - response.add(ClientFlags(add_flags(&[*flag]), replace(nicks, vec![])).send_self()); - } - - if !room.greeting.is_empty() { - response.add( - ChatMsg { - nick: "[greeting]".to_string(), - msg: room.greeting.clone(), - } - .send_self(), - ); - } -} - -pub fn exit_room(server: &mut HWServer, client_id: ClientId, response: &mut Response, msg: &str) { - let client = &mut server.clients[client_id]; - - if let Some(room_id) = client.room_id { - let room = &mut server.rooms[room_id]; - - remove_client_from_room(client, room, response, msg); - - if !room.is_fixed() { - if room.players_number == 0 { - server.rooms.remove(room_id); - } else if room.master_id == None { - let new_master_id = server.room_clients(room_id).next(); - if let Some(new_master_id) = new_master_id { - let new_master_nick = server.clients[new_master_id].nick.clone(); - let room = &mut server.rooms[room_id]; - room.master_id = Some(new_master_id); - server.clients[new_master_id].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); - - response.add( - ClientFlags(add_flags(&[Flags::RoomMaster]), vec![new_master_nick]) - .send_all() - .in_room(room.id), - ); - } - } - } - } -} - -pub fn remove_client(server: &mut HWServer, response: &mut Response, msg: String) { - let client_id = response.client_id(); - let client = &mut server.clients[client_id]; - let nick = client.nick.clone(); - - exit_room(server, client_id, response, &msg); - - server.remove_client(client_id); - - response.add(LobbyLeft(nick, msg.to_string()).send_all()); - response.add(Bye("User quit: ".to_string() + &msg).send_self()); - response.remove_client(client_id); -} - -pub fn get_room_update( - room_name: Option, - room: &HWRoom, - master: Option<&HWClient>, - response: &mut Response, -) { - let update_msg = RoomUpdated(room_name.unwrap_or(room.name.clone()), room.info(master)); - response.add(update_msg.send_all().with_protocol(room.protocol_number)); -} - -pub fn get_room_config_impl(config: &RoomConfig, to_client: ClientId, response: &mut Response) { - response.add(ConfigEntry("FULLMAPCONFIG".to_string(), config.to_map_config()).send(to_client)); - for cfg in config.to_game_config() { - response.add(cfg.to_server_msg().send(to_client)); - } -} - -pub fn get_room_config(room: &HWRoom, to_client: ClientId, response: &mut Response) { - get_room_config_impl(room.active_config(), to_client, response); -} - -pub fn get_teams<'a, I>(teams: I, to_client: ClientId, response: &mut Response) -where - I: Iterator, -{ - for team in teams { - response.add(TeamAdd(team.to_protocol()).send(to_client)); - response.add(TeamColor(team.name.clone(), team.color).send(to_client)); - response.add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send(to_client)); - } -} - -pub fn get_room_teams( - server: &HWServer, - room_id: RoomId, - to_client: ClientId, - response: &mut Response, -) { - let room = &server.rooms[room_id]; - let current_teams = match room.game_info { - Some(ref info) => &info.teams_at_start, - None => &room.teams, - }; - - get_teams(current_teams.iter().map(|(_, t)| t), to_client, response); -} - -pub fn get_room_flags( - server: &HWServer, - room_id: RoomId, - to_client: ClientId, - response: &mut Response, -) { - let room = &server.rooms[room_id]; - if let Some(id) = room.master_id { - response.add( - ClientFlags( - add_flags(&[Flags::RoomMaster]), - vec![server.clients[id].nick.clone()], - ) - .send(to_client), - ); - } - let nicks: Vec<_> = server - .clients - .iter() - .filter(|(_, c)| c.room_id == Some(room_id) && c.is_ready()) - .map(|(_, c)| c.nick.clone()) - .collect(); - if !nicks.is_empty() { - response.add(ClientFlags(add_flags(&[Flags::Ready]), nicks).send(to_client)); - } -} - -pub fn apply_voting_result( - server: &mut HWServer, - room_id: RoomId, - response: &mut Response, - kind: VoteType, -) { - match kind { - VoteType::Kick(nick) => { - if let Some(client) = server.find_client(&nick) { - if client.room_id == Some(room_id) { - let id = client.id; - response.add(Kicked.send(id)); - exit_room(server, id, response, "kicked"); - } - } - } - VoteType::Map(None) => (), - VoteType::Map(Some(name)) => { - if let Some(location) = server.rooms[room_id].load_config(&name) { - response.add( - server_chat(location.to_string()) - .send_all() - .in_room(room_id), - ); - let room = &server.rooms[room_id]; - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - get_room_update(None, room, room_master, response); - - for (_, client) in server.clients.iter() { - if client.room_id == Some(room_id) { - super::common::get_room_config(&server.rooms[room_id], client.id, response); - } - } - } - } - VoteType::Pause => { - if let Some(ref mut info) = server.rooms[room_id].game_info { - info.is_paused = !info.is_paused; - response.add( - server_chat("Pause toggled.".to_string()) - .send_all() - .in_room(room_id), - ); - response.add( - ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) - .send_all() - .in_room(room_id), - ); - } - } - VoteType::NewSeed => { - let seed = thread_rng().gen_range(0, 1_000_000_000).to_string(); - let cfg = GameCfg::Seed(seed); - response.add(cfg.to_server_msg().send_all().in_room(room_id)); - server.rooms[room_id].set_config(cfg); - } - VoteType::HedgehogsPerTeam(number) => { - let r = &mut server.rooms[room_id]; - let nicks = r.set_hedgehogs_number(number); - - response.extend( - nicks - .into_iter() - .map(|n| HedgehogsNumber(n, number).send_all().in_room(room_id)), - ); - } - } -} - -fn add_vote(room: &mut HWRoom, response: &mut Response, vote: Vote) -> Option { - let client_id = response.client_id; - let mut result = None; - - if let Some(ref mut voting) = room.voting { - if vote.is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) { - response.add(server_chat("Your vote has been counted.".to_string()).send_self()); - voting.votes.push((client_id, vote.is_pro)); - let i = voting.votes.iter(); - let pro = i.clone().filter(|(_, v)| *v).count(); - let contra = i.filter(|(_, v)| !*v).count(); - let success_quota = voting.voters.len() / 2 + 1; - if vote.is_forced && vote.is_pro || pro >= success_quota { - result = Some(true); - } else if vote.is_forced && !vote.is_pro || contra > voting.voters.len() - success_quota - { - result = Some(false); - } - } else { - response.add(server_chat("You already have voted.".to_string()).send_self()); - } - } else { - response.add(server_chat("There's no voting going on.".to_string()).send_self()); - } - - result -} - -pub fn submit_vote(server: &mut HWServer, vote: Vote, response: &mut Response) { - let client_id = response.client_id; - let client = &server.clients[client_id]; - - if let Some(room_id) = client.room_id { - let room = &mut server.rooms[room_id]; - - if let Some(res) = add_vote(room, response, vote) { - response.add( - server_chat("Voting closed.".to_string()) - .send_all() - .in_room(room.id), - ); - let voting = replace(&mut room.voting, None).unwrap(); - if res { - apply_voting_result(server, room_id, response, voting.kind); - } - } - } -} - -pub fn start_game(server: &mut HWServer, room_id: RoomId, response: &mut Response) { - let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server - .clients - .iter() - .map(|(id, c)| (id, c.nick.clone())) - .unzip(); - let room = &mut server.rooms[room_id]; - - if !room.has_multiple_clans() { - response.add( - Warning("The game can't be started with less than two clans!".to_string()).send_self(), - ); - } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { - response.add(Warning("Not all players are ready".to_string()).send_self()); - } else if room.game_info.is_some() { - response.add(Warning("The game is already in progress".to_string()).send_self()); - } else { - room.start_round(); - for id in room_clients { - let c = &mut server.clients[id]; - c.set_is_in_game(true); - c.team_indices = room.client_team_indices(c.id); - } - response.add(RunGame.send_all().in_room(room.id)); - response.add( - ClientFlags(add_flags(&[Flags::InGame]), room_nicks) - .send_all() - .in_room(room.id), - ); - - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - get_room_update(None, room, room_master, response); - } -} - -pub fn end_game(server: &mut HWServer, room_id: RoomId, response: &mut Response) { - let room = &mut server.rooms[room_id]; - room.ready_players_number = 1; - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - get_room_update(None, room, room_master, response); - response.add(RoundFinished.send_all().in_room(room_id)); - - if let Some(info) = replace(&mut room.game_info, None) { - for (_, client) in server.clients.iter() { - if client.room_id == Some(room_id) && client.is_joined_mid_game() { - super::common::get_room_config(room, client.id, response); - response.extend( - info.left_teams - .iter() - .map(|name| TeamRemove(name.clone()).send(client.id)), - ); - } - } - } - - let nicks: Vec<_> = 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(); - - if !nicks.is_empty() { - let msg = if room.protocol_number < 38 { - LegacyReady(false, nicks) - } else { - ClientFlags(remove_flags(&[Flags::Ready]), nicks) - }; - response.add(msg.send_all().in_room(room_id)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::protocol::messages::HWServerMessage::ChatMsg; - use crate::server::actions::PendingMessage; - - fn reply2string(r: HWServerMessage) -> String { - match r { - ChatMsg { msg: p, .. } => String::from(p), - _ => panic!("expected a ChatMsg"), - } - } - - fn run_handle_test(opts: Vec) { - let opts2 = opts.clone(); - for opt in opts { - while reply2string(rnd_reply(&opts2)) != opt {} - } - } - - /// This test terminates almost surely. - #[test] - fn test_handle_rnd_empty() { - run_handle_test(vec![]) - } - - /// This test terminates almost surely. - #[test] - fn test_handle_rnd_nonempty() { - run_handle_test(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]) - } - - /// This test terminates almost surely (strong law of large numbers) - #[test] - fn test_distribution() { - let eps = 0.000001; - let lim = 0.5; - let opts = vec![0.to_string(), 1.to_string()]; - let mut ones = 0; - let mut tries = 0; - - while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps { - tries += 1; - if reply2string(rnd_reply(&opts)) == 1.to_string() { - ones += 1; - } - } - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/handlers/inroom.rs --- a/rust/hedgewars-server/src/server/handlers/inroom.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,615 +0,0 @@ -use mio; - -use super::common::rnd_reply; -use crate::utils::to_engine_msg; -use crate::{ - protocol::messages::{ - add_flags, remove_flags, server_chat, HWProtocolMessage, HWServerMessage::*, - ProtocolFlags as Flags, - }, - server::{ - core::HWServer, - coretypes, - coretypes::{ClientId, GameCfg, RoomId, VoteType, Voting, MAX_HEDGEHOGS_PER_TEAM}, - room::{HWRoom, RoomFlags, MAX_TEAMS_IN_ROOM}, - }, - utils::is_name_illegal, -}; -use base64::{decode, encode}; -use log::*; -use std::{cmp::min, iter::once, mem::swap}; - -#[derive(Clone)] -struct ByMsg<'a> { - messages: &'a [u8], -} - -impl<'a> Iterator for ByMsg<'a> { - type Item = &'a [u8]; - - fn next(&mut self) -> Option<::Item> { - if let Some(size) = self.messages.get(0) { - let (msg, next) = self.messages.split_at(*size as usize + 1); - self.messages = next; - Some(msg) - } else { - None - } - } -} - -fn by_msg(source: &[u8]) -> ByMsg { - ByMsg { messages: source } -} - -const VALID_MESSAGES: &[u8] = - b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A"; -const NON_TIMED_MESSAGES: &[u8] = b"M#hb"; - -#[cfg(canhazslicepatterns)] -fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool { - match msg { - [size, typ, body..] => { - VALID_MESSAGES.contains(typ) - && match body { - [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => { - team_indices.contains(team) - } - _ => *typ != b'h', - } - } - _ => false, - } -} - -fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool { - if let Some(typ) = msg.get(1) { - VALID_MESSAGES.contains(typ) - } else { - false - } -} - -fn is_msg_empty(msg: &[u8]) -> bool { - msg.get(1).filter(|t| **t == b'+').is_some() -} - -fn is_msg_timed(msg: &[u8]) -> bool { - msg.get(1) - .filter(|t| !NON_TIMED_MESSAGES.contains(t)) - .is_some() -} - -fn voting_description(kind: &VoteType) -> String { - format!( - "New voting started: {}", - match kind { - VoteType::Kick(nick) => format!("kick {}", nick), - VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()), - VoteType::Pause => "pause".to_string(), - VoteType::NewSeed => "new seed".to_string(), - VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number), - } - ) -} - -fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags { - use crate::protocol::messages::HWProtocolMessage::*; - match msg { - ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN, - ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD, - ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, - _ => RoomFlags::empty(), - } -} - -pub fn handle( - server: &mut HWServer, - client_id: ClientId, - response: &mut super::Response, - room_id: RoomId, - message: HWProtocolMessage, -) { - let client = &mut server.clients[client_id]; - let room = &mut server.rooms[room_id]; - - use crate::protocol::messages::HWProtocolMessage::*; - match message { - Part(msg) => { - let msg = match msg { - Some(s) => format!("part: {}", s), - None => "part".to_string(), - }; - super::common::exit_room(server, client_id, response, &msg); - } - Chat(msg) => { - response.add( - ChatMsg { - nick: client.nick.clone(), - msg, - } - .send_all() - .in_room(room_id), - ); - } - TeamChat(msg) => { - let room = &server.rooms[room_id]; - if let Some(ref info) = room.game_info { - if let Some(clan_color) = room.find_team_color(client_id) { - let client = &server.clients[client_id]; - let engine_msg = - to_engine_msg(format!("b{}]{}\x20\x20", client.nick, msg).bytes()); - let team = room.clan_team_owners(clan_color).collect(); - response.add(ForwardEngineMessage(vec![engine_msg]).send_many(team)) - } - } - } - Fix => { - 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); - } - } - Unfix => { - if client.is_admin() { - room.set_is_fixed(false); - } - } - Greeting(text) => { - if client.is_admin() || client.is_master() && !room.is_fixed() { - room.greeting = text; - } - } - MaxTeams(count) => { - if !client.is_master() { - response.add(Warning("You're not the room master!".to_string()).send_self()); - } else if !(2..=MAX_TEAMS_IN_ROOM).contains(&count) { - response - .add(Warning("/maxteams: specify number from 2 to 8".to_string()).send_self()); - } else { - server.rooms[room_id].max_teams = count; - } - } - RoomName(new_name) => { - if is_name_illegal(&new_name) { - response.add(Warning("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: $()*+?[]^{|}".to_string()).send_self()); - } else if server.has_room(&new_name) { - response.add( - Warning("A room with the same name already exists.".to_string()).send_self(), - ); - } else { - let room = &mut server.rooms[room_id]; - if room.is_fixed() || room.master_id != Some(client_id) { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - let mut old_name = new_name.clone(); - let client = &server.clients[client_id]; - swap(&mut room.name, &mut old_name); - super::common::get_room_update(Some(old_name), room, Some(&client), response); - } - } - } - ToggleReady => { - let flags = if client.is_ready() { - room.ready_players_number -= 1; - remove_flags(&[Flags::Ready]) - } else { - room.ready_players_number += 1; - add_flags(&[Flags::Ready]) - }; - - let msg = if client.protocol_number < 38 { - LegacyReady(client.is_ready(), vec![client.nick.clone()]) - } else { - ClientFlags(flags, vec![client.nick.clone()]) - }; - response.add(msg.send_all().in_room(room.id)); - client.set_is_ready(!client.is_ready()); - - if room.is_fixed() && room.ready_players_number == room.players_number { - super::common::start_game(server, room_id, response); - } - } - AddTeam(mut info) => { - if room.teams.len() >= room.max_teams as usize { - response.add(Warning("Too many teams!".to_string()).send_self()); - } else if room.addable_hedgehogs() == 0 { - response.add(Warning("Too many hedgehogs!".to_string()).send_self()); - } else if room.find_team(|t| t.name == info.name) != None { - response.add( - Warning("There's already a team with same name in the list.".to_string()) - .send_self(), - ); - } else if room.game_info.is_some() { - response.add( - Warning("Joining not possible: Round is in progress.".to_string()).send_self(), - ); - } else if room.is_team_add_restricted() { - response.add( - Warning("This room currently does not allow adding new teams.".to_string()) - .send_self(), - ); - } 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), - ); - - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - super::common::get_room_update(None, room, room_master, response); - } - } - RemoveTeam(name) => match room.find_team_owner(&name) { - None => response.add( - Warning("Error: The team you tried to remove does not exist.".to_string()) - .send_self(), - ), - Some((id, _)) if id != client_id => response - .add(Warning("You can't remove a team you don't own.".to_string()).send_self()), - Some((_, name)) => { - client.teams_in_game -= 1; - client.clan = room.find_team_color(client.id); - super::common::remove_teams( - room, - vec![name.to_string()], - client.is_in_game(), - response, - ); - - match room.game_info { - Some(ref info) if info.teams_in_game == 0 => { - super::common::end_game(server, room_id, 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) { - let max_hedgehogs = min( - MAX_HEDGEHOGS_PER_TEAM, - addable_hedgehogs + team.hedgehogs_number, - ); - if !client.is_master() { - response.add(Error("You're not the room master!".to_string()).send_self()); - } else if !(1..=max_hedgehogs).contains(&number) { - response - .add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send_self()); - } else { - team.hedgehogs_number = number; - response.add( - HedgehogsNumber(team.name.clone(), number) - .send_all() - .in_room(room_id) - .but_self(), - ); - } - } else { - response.add(Warning("No such team.".to_string()).send_self()); - } - } - SetTeamColor(team_name, color) => { - if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { - if !client.is_master() { - response.add(Error("You're not the room master!".to_string()).send_self()); - } else { - team.color = color; - response.add( - TeamColor(team.name.clone(), color) - .send_all() - .in_room(room_id) - .but_self(), - ); - server.clients[owner].clan = Some(color); - } - } else { - response.add(Warning("No such team.".to_string()).send_self()); - } - } - Cfg(cfg) => { - if room.is_fixed() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else if !client.is_master() { - response.add(Error("You're not the room master!".to_string()).send_self()); - } 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, - }; - - response.add(cfg.to_server_msg().send_all().in_room(room.id).but_self()); - room.set_config(cfg); - } - } - Save(name, location) => { - response.add( - server_chat(format!("Room config saved as {}", name)) - .send_all() - .in_room(room_id), - ); - room.save_config(name, location); - } - #[cfg(feature = "official-server")] - SaveRoom(filename) => { - if client.is_admin() { - match room.get_saves() { - Ok(contents) => response.request_io(super::IoTask::SaveRoom { - room_id, - filename, - contents, - }), - Err(e) => { - warn!("Error while serializing the room configs: {}", e); - response.add( - Warning("Unable to serialize the room configs.".to_string()) - .send_self(), - ) - } - } - } - } - #[cfg(feature = "official-server")] - LoadRoom(filename) => { - if client.is_admin() { - response.request_io(super::IoTask::LoadRoom { room_id, filename }); - } - } - Delete(name) => { - if !room.delete_config(&name) { - response.add(Warning(format!("Save doesn't exist: {}", name)).send_self()); - } else { - response.add( - server_chat(format!("Room config {} has been deleted", name)) - .send_all() - .in_room(room_id), - ); - } - } - CallVote(None) => { - response.add(server_chat("Available callvote commands: kick , map , pause, newseed, hedgehogs ".to_string()) - .send_self()); - } - CallVote(Some(kind)) => { - let is_in_game = room.game_info.is_some(); - let error = match &kind { - VoteType::Kick(nick) => { - if server - .find_client(&nick) - .filter(|c| c.room_id == Some(room_id)) - .is_some() - { - None - } else { - Some("/callvote kick: No such user!".to_string()) - } - } - VoteType::Map(None) => { - let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect(); - if names.is_empty() { - Some("/callvote map: No maps saved in this room!".to_string()) - } else { - Some(format!("Available maps: {}", names.join(", "))) - } - } - VoteType::Map(Some(name)) => { - if room.saves.get(&name[..]).is_some() { - None - } else { - Some("/callvote map: No such map!".to_string()) - } - } - VoteType::Pause => { - if is_in_game { - None - } else { - Some("/callvote pause: No game in progress!".to_string()) - } - } - VoteType::NewSeed => None, - VoteType::HedgehogsPerTeam(number) => match number { - 1...MAX_HEDGEHOGS_PER_TEAM => None, - _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()), - }, - }; - - match error { - None => { - let msg = voting_description(&kind); - let voting = Voting::new(kind, server.room_clients(client_id).collect()); - let room = &mut server.rooms[room_id]; - room.voting = Some(voting); - response.add(server_chat(msg).send_all().in_room(room_id)); - super::common::submit_vote( - server, - coretypes::Vote { - is_pro: true, - is_forced: false, - }, - response, - ); - } - Some(msg) => { - response.add(server_chat(msg).send_self()); - } - } - } - Vote(vote) => { - super::common::submit_vote( - server, - coretypes::Vote { - is_pro: vote, - is_forced: false, - }, - response, - ); - } - ForceVote(vote) => { - let is_forced = client.is_admin(); - super::common::submit_vote( - server, - coretypes::Vote { - is_pro: vote, - is_forced, - }, - response, - ); - } - ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => { - if client.is_master() { - room.flags.toggle(room_message_flag(&message)); - super::common::get_room_update(None, room, Some(&client), response); - } - } - StartGame => { - super::common::start_game(server, room_id, response); - } - EngineMessage(em) => { - if client.teams_in_game > 0 { - let decoding = decode(&em[..]).unwrap(); - let messages = by_msg(&decoding); - let valid = messages.filter(|m| is_msg_valid(m, &client.team_indices)); - let non_empty = valid.clone().filter(|m| !is_msg_empty(m)); - let sync_msg = valid.clone().filter(|m| is_msg_timed(m)).last().map(|m| { - if is_msg_empty(m) { - Some(encode(m)) - } else { - None - } - }); - - let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::>()); - if !em_response.is_empty() { - response.add( - ForwardEngineMessage(vec![em_response]) - .send_all() - .in_room(room.id) - .but_self(), - ); - } - let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::>()); - if let Some(ref mut info) = room.game_info { - if !em_log.is_empty() { - info.msg_log.push(em_log); - } - if let Some(msg) = sync_msg { - info.sync_msg = msg; - } - } - } - } - RoundFinished => { - let mut game_ended = false; - if client.is_in_game() { - client.set_is_in_game(false); - 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(), - ); - } - } - } - if game_ended { - super::common::end_game(server, room_id, response) - } - } - Rnd(v) => { - let result = rnd_reply(&v); - let mut echo = vec!["/rnd".to_string()]; - echo.extend(v.into_iter()); - let chat_msg = ChatMsg { - nick: server.clients[client_id].nick.clone(), - msg: echo.join(" "), - }; - response.add(chat_msg.send_all().in_room(room_id)); - response.add(result.send_all().in_room(room_id)); - } - Delegate(nick) => { - let delegate_id = server.find_client(&nick).map(|c| (c.id, c.room_id)); - let client = &server.clients[client_id]; - if !(client.is_admin() || client.is_master()) { - response.add( - Warning("You're not the room master or a server admin!".to_string()) - .send_self(), - ) - } else { - match delegate_id { - None => response.add(Warning("Player is not online.".to_string()).send_self()), - Some((id, _)) if id == client_id => response - .add(Warning("You're already the room master.".to_string()).send_self()), - Some((_, id)) if id != Some(room_id) => response - .add(Warning("The player is not in your room.".to_string()).send_self()), - Some((id, _)) => { - super::common::change_master(server, room_id, id, response); - } - } - } - } - _ => warn!("Unimplemented!"), - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/handlers/lobby.rs --- a/rust/hedgewars-server/src/server/handlers/lobby.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -use mio; - -use super::common::rnd_reply; -use crate::{ - protocol::messages::{ - add_flags, remove_flags, server_chat, HWProtocolMessage, HWServerMessage::*, - ProtocolFlags as Flags, - }, - server::{ - client::HWClient, - core::HWServer, - coretypes::{ClientId, ServerVar}, - }, - utils::is_name_illegal, -}; -use log::*; -use std::{collections::HashSet, convert::identity}; - -pub fn handle( - server: &mut HWServer, - client_id: ClientId, - response: &mut super::Response, - message: HWProtocolMessage, -) { - use crate::protocol::messages::HWProtocolMessage::*; - match message { - CreateRoom(name, password) => { - if is_name_illegal(&name) { - response.add(Warning("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: $()*+?[]^{|}".to_string()).send_self()); - } else if server.has_room(&name) { - response.add( - Warning("A room with the same name already exists.".to_string()).send_self(), - ); - } else { - let flags_msg = ClientFlags( - add_flags(&[Flags::RoomMaster, Flags::Ready]), - vec![server.clients[client_id].nick.clone()], - ); - - let room_id = server.create_room(client_id, name, password); - let room = &server.rooms[room_id]; - let client = &server.clients[client_id]; - - response.add( - RoomAdd(room.info(Some(&client))) - .send_all() - .with_protocol(room.protocol_number), - ); - response.add(RoomJoined(vec![client.nick.clone()]).send_self()); - response.add(flags_msg.send_self()); - - response.add( - ClientFlags(add_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_self(), - ); - }; - } - Chat(msg) => { - response.add( - ChatMsg { - nick: server.clients[client_id].nick.clone(), - msg, - } - .send_all() - .in_lobby() - .but_self(), - ); - } - JoinRoom(name, _password) => { - let room = server.rooms.iter().find(|(_, r)| r.name == name); - let room_id = room.map(|(_, r)| r.id); - - let client = &mut server.clients[client_id]; - - if let Some((_, room)) = room { - if client.protocol_number != room.protocol_number { - response.add( - Warning("Room version incompatible to your Hedgewars version!".to_string()) - .send_self(), - ); - } else if room.is_join_restricted() { - response.add( - Warning( - "Access denied. This room currently doesn't allow joining.".to_string(), - ) - .send_self(), - ); - } else if room.players_number == u8::max_value() { - response.add(Warning("This room is already full".to_string()).send_self()); - } else if let Some(room_id) = room_id { - super::common::enter_room(server, client_id, room_id, response); - } - } else { - response.add(Warning("No such room.".to_string()).send_self()); - } - } - Follow(nick) => { - if let Some(HWClient { - room_id: Some(room_id), - .. - }) = server.find_client(&nick) - { - let room = &server.rooms[*room_id]; - response.add(Joining(room.name.clone()).send_self()); - super::common::enter_room(server, client_id, *room_id, response); - } - } - SetServerVar(var) => { - if !server.clients[client_id].is_admin() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - match var { - ServerVar::MOTDNew(msg) => server.greetings.for_latest_protocol = msg, - ServerVar::MOTDOld(msg) => server.greetings.for_old_protocols = msg, - ServerVar::LatestProto(n) => server.latest_protocol = n, - } - } - } - GetServerVar => { - if !server.clients[client_id].is_admin() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - let vars: Vec<_> = [ - ServerVar::MOTDNew(server.greetings.for_latest_protocol.clone()), - ServerVar::MOTDOld(server.greetings.for_old_protocols.clone()), - ServerVar::LatestProto(server.latest_protocol), - ] - .iter() - .flat_map(|v| v.to_protocol()) - .collect(); - response.add(ServerVars(vars).send_self()); - } - } - Rnd(v) => { - response.add(rnd_reply(&v).send_self()); - } - Stats => { - let mut protocols: HashSet<_> = server - .clients - .iter() - .map(|(_, c)| c.protocol_number) - .chain(server.rooms.iter().map(|(_, r)| r.protocol_number)) - .collect(); - let mut protocols: Vec<_> = protocols.drain().collect(); - protocols.sort(); - - let mut html = Vec::with_capacity(protocols.len() + 2); - - html.push("".to_string()); - for protocol in protocols { - html.push(format!( - "", - super::utils::protocol_version_string(protocol), - server.protocol_clients(protocol).count(), - server.protocol_rooms(protocol).count() - )); - } - html.push("
{}{}{}
".to_string()); - - response.add(Warning(html.join("")).send_self()); - } - List => warn!("Deprecated LIST message received"), - _ => warn!("Incorrect command in lobby state"), - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/handlers/loggingin.rs --- a/rust/hedgewars-server/src/server/handlers/loggingin.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,146 +0,0 @@ -use mio; - -use crate::protocol::messages::HWProtocolMessage::LoadRoom; -use crate::server::client::HWClient; -use crate::server::core::HWServer; -use crate::{ - protocol::messages::{HWProtocolMessage, HWServerMessage::*}, - server::{ - core::{HWAnteClient, HWAnteroom}, - coretypes::ClientId, - }, - utils::is_name_illegal, -}; -use log::*; -#[cfg(feature = "official-server")] -use openssl::sha::sha1; -use std::{ - fmt::{Formatter, LowerHex}, - num::NonZeroU16, -}; - -pub enum LoginResult { - Unchanged, - Complete, - Exit, -} - -fn completion_result<'a, I>( - mut other_clients: I, - client: &mut HWAnteClient, - response: &mut super::Response, -) -> LoginResult -where - I: Iterator, -{ - let has_nick_clash = - other_clients.any(|(_, c)| !c.is_checker() && c.nick == *client.nick.as_ref().unwrap()); - - if has_nick_clash { - if client.protocol_number.unwrap().get() < 38 { - response.add(Bye("User quit: Nickname is already in use".to_string()).send_self()); - LoginResult::Exit - } else { - client.nick = None; - response.add(Notice("NickAlreadyInUse".to_string()).send_self()); - LoginResult::Unchanged - } - } else { - #[cfg(feature = "official-server")] - { - response.add(AskPassword(client.server_salt.clone()).send_self()); - LoginResult::Unchanged - } - - #[cfg(not(feature = "official-server"))] - { - LoginResult::Complete - } - } -} - -pub fn handle( - server: &mut HWServer, - client_id: ClientId, - response: &mut super::Response, - message: HWProtocolMessage, -) -> LoginResult { - match message { - HWProtocolMessage::Quit(_) => { - response.add(Bye("User quit".to_string()).send_self()); - LoginResult::Exit - } - HWProtocolMessage::Nick(nick) => { - let client = &mut server.anteroom.clients[client_id]; - - if client.nick.is_some() { - response.add(Error("Nickname already provided.".to_string()).send_self()); - LoginResult::Unchanged - } else if is_name_illegal(&nick) { - response.add(Bye("Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string()).send_self()); - LoginResult::Exit - } else { - client.nick = Some(nick.clone()); - response.add(Nick(nick).send_self()); - - if client.protocol_number.is_some() { - completion_result(server.clients.iter(), client, response) - } else { - LoginResult::Unchanged - } - } - } - HWProtocolMessage::Proto(proto) => { - let client = &mut server.anteroom.clients[client_id]; - if client.protocol_number.is_some() { - response.add(Error("Protocol already known.".to_string()).send_self()); - LoginResult::Unchanged - } else if proto == 0 { - response.add(Error("Bad number.".to_string()).send_self()); - LoginResult::Unchanged - } else { - client.protocol_number = NonZeroU16::new(proto); - response.add(Proto(proto).send_self()); - - if client.nick.is_some() { - completion_result(server.clients.iter(), client, response) - } else { - LoginResult::Unchanged - } - } - } - #[cfg(feature = "official-server")] - HWProtocolMessage::Password(hash, salt) => { - let client = &server.anteroom.clients[client_id]; - - if let (Some(nick), Some(protocol)) = (client.nick.as_ref(), client.protocol_number) { - response.request_io(super::IoTask::GetAccount { - nick: nick.clone(), - protocol: protocol.get(), - server_salt: client.server_salt.clone(), - client_salt: salt, - password_hash: hash, - }); - }; - - LoginResult::Unchanged - } - #[cfg(feature = "official-server")] - HWProtocolMessage::Checker(protocol, nick, password) => { - let client = &mut server.anteroom.clients[client_id]; - if protocol == 0 { - response.add(Error("Bad number.".to_string()).send_self()); - LoginResult::Unchanged - } else { - client.protocol_number = NonZeroU16::new(protocol); - client.nick = Some(nick); - client.is_checker = true; - LoginResult::Complete - } - } - _ => { - warn!("Incorrect command in logging-in state"); - LoginResult::Unchanged - } - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/indexslab.rs --- a/rust/hedgewars-server/src/server/indexslab.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -use std::{ - iter, - mem::replace, - ops::{Index, IndexMut}, -}; - -pub struct IndexSlab { - data: Vec>, -} - -impl IndexSlab { - pub fn new() -> Self { - Self { data: Vec::new() } - } - - pub fn with_capacity(capacity: usize) -> Self { - Self { - data: Vec::with_capacity(capacity), - } - } - - pub fn insert(&mut self, index: usize, value: T) { - if index >= self.data.len() { - self.data.reserve(index - self.data.len() + 1); - self.data.extend((self.data.len()..index).map(|_| None)); - self.data.push(Some(value)) - } else { - self.data[index] = Some(value); - } - } - - pub fn contains(&self, index: usize) -> bool { - self.data.get(index).and_then(|x| x.as_ref()).is_some() - } - - pub fn remove(&mut self, index: usize) -> Option { - if let Some(x) = self.data.get_mut(index) { - replace(x, None) - } else { - None - } - } - - pub fn iter(&self) -> impl Iterator { - self.data - .iter() - .enumerate() - .filter_map(|(index, opt)| opt.as_ref().and_then(|x| Some((index, x)))) - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.data - .iter_mut() - .enumerate() - .filter_map(|(index, opt)| opt.as_mut().and_then(|x| Some((index, x)))) - } -} - -impl Index for IndexSlab { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - self.data[index].as_ref().unwrap() - } -} - -impl IndexMut for IndexSlab { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.data[index].as_mut().unwrap() - } -} diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/io.rs --- a/rust/hedgewars-server/src/server/io.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/server/io.rs Tue May 28 19:04:18 2019 +0300 @@ -5,8 +5,8 @@ thread, }; -use crate::server::{ - database::Database, +use crate::{ + server::database::Database, handlers::{IoResult, IoTask}, }; use log::*; diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/network.rs --- a/rust/hedgewars-server/src/server/network.rs Tue May 28 17:49:04 2019 +0200 +++ b/rust/hedgewars-server/src/server/network.rs Tue May 28 19:04:18 2019 +0300 @@ -17,8 +17,12 @@ use netbuf; use slab::Slab; -use super::{core::HWServer, coretypes::ClientId, handlers}; use crate::{ + core::{ + server::HWServer, + types::ClientId + }, + handlers, protocol::{messages::*, ProtocolDecoder}, utils, }; @@ -26,8 +30,11 @@ #[cfg(feature = "official-server")] use super::io::{IOThread, RequestId}; -use crate::protocol::messages::HWServerMessage::Redirect; -use crate::server::handlers::{IoResult, IoTask}; +use crate::{ + protocol::messages::HWServerMessage::Redirect, + handlers::{IoResult, IoTask} +}; + #[cfg(feature = "tls-connections")] use openssl::{ error::ErrorStack, diff -r 7732013ce64c -r c5a6e8566425 rust/hedgewars-server/src/server/room.rs --- a/rust/hedgewars-server/src/server/room.rs Tue May 28 17:49:04 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,349 +0,0 @@ -use crate::server::{ - client::HWClient, - coretypes::{ - ClientId, GameCfg, GameCfg::*, RoomConfig, RoomId, TeamInfo, Voting, MAX_HEDGEHOGS_PER_TEAM, - }, -}; -use bitflags::*; -use serde::{Deserialize, Serialize}; -use serde_derive::{Deserialize, Serialize}; -use serde_yaml; -use std::{collections::HashMap, iter}; - -pub const MAX_TEAMS_IN_ROOM: u8 = 8; -pub const MAX_HEDGEHOGS_IN_ROOM: u8 = MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM; - -fn client_teams_impl( - teams: &[(ClientId, TeamInfo)], - client_id: ClientId, -) -> impl Iterator + Clone { - teams - .iter() - .filter(move |(id, _)| *id == client_id) - .map(|(_, t)| t) -} - -pub struct GameInfo { - pub teams_in_game: u8, - pub teams_at_start: Vec<(ClientId, TeamInfo)>, - pub left_teams: Vec, - pub msg_log: Vec, - pub sync_msg: Option, - pub is_paused: bool, - config: RoomConfig, -} - -impl GameInfo { - fn new(teams: Vec<(ClientId, TeamInfo)>, config: RoomConfig) -> GameInfo { - GameInfo { - left_teams: Vec::new(), - msg_log: Vec::new(), - sync_msg: None, - is_paused: false, - teams_in_game: teams.len() as u8, - teams_at_start: teams, - config, - } - } - - pub fn client_teams(&self, client_id: ClientId) -> impl Iterator + Clone { - client_teams_impl(&self.teams_at_start, client_id) - } -} - -#[derive(Serialize, Deserialize)] -pub struct RoomSave { - pub location: String, - config: RoomConfig, -} - -bitflags! { - pub struct RoomFlags: u8 { - const FIXED = 0b0000_0001; - const RESTRICTED_JOIN = 0b0000_0010; - const RESTRICTED_TEAM_ADD = 0b0000_0100; - const RESTRICTED_UNREGISTERED_PLAYERS = 0b0000_1000; - } -} - -pub struct HWRoom { - pub id: RoomId, - pub master_id: Option, - pub name: String, - pub password: Option, - pub greeting: String, - pub protocol_number: u16, - pub flags: RoomFlags, - - pub players_number: u8, - pub default_hedgehog_number: u8, - pub max_teams: u8, - pub ready_players_number: u8, - pub teams: Vec<(ClientId, TeamInfo)>, - config: RoomConfig, - pub voting: Option, - pub saves: HashMap, - pub game_info: Option, -} - -impl HWRoom { - pub fn new(id: RoomId) -> HWRoom { - HWRoom { - id, - master_id: None, - name: String::new(), - password: None, - greeting: "".to_string(), - flags: RoomFlags::empty(), - protocol_number: 0, - players_number: 0, - default_hedgehog_number: 4, - max_teams: MAX_TEAMS_IN_ROOM, - ready_players_number: 0, - teams: Vec::new(), - config: RoomConfig::new(), - voting: None, - saves: HashMap::new(), - game_info: None, - } - } - - pub fn hedgehogs_number(&self) -> u8 { - self.teams.iter().map(|(_, t)| t.hedgehogs_number).sum() - } - - pub fn addable_hedgehogs(&self) -> u8 { - MAX_HEDGEHOGS_IN_ROOM - self.hedgehogs_number() - } - - pub fn add_team( - &mut self, - owner_id: ClientId, - mut team: TeamInfo, - preserve_color: bool, - ) -> &TeamInfo { - if !preserve_color { - team.color = iter::repeat(()) - .enumerate() - .map(|(i, _)| i as u8) - .take(u8::max_value() as usize + 1) - .find(|i| self.teams.iter().all(|(_, t)| t.color != *i)) - .unwrap_or(0u8) - }; - team.hedgehogs_number = if self.teams.is_empty() { - self.default_hedgehog_number - } else { - self.teams[0] - .1 - .hedgehogs_number - .min(self.addable_hedgehogs()) - }; - self.teams.push((owner_id, team)); - &self.teams.last().unwrap().1 - } - - pub fn remove_team(&mut self, name: &str) { - if let Some(index) = self.teams.iter().position(|(_, t)| t.name == name) { - self.teams.remove(index); - } - } - - pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec { - let mut names = Vec::new(); - let teams = match self.game_info { - Some(ref mut info) => &mut info.teams_at_start, - None => &mut self.teams, - }; - - if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM { - for (_, team) in teams.iter_mut() { - team.hedgehogs_number = n; - names.push(team.name.clone()) - } - self.default_hedgehog_number = n; - } - names - } - - pub fn find_team_and_owner_mut(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)> - where - F: Fn(&TeamInfo) -> bool, - { - self.teams - .iter_mut() - .find(|(_, t)| f(t)) - .map(|(id, t)| (*id, t)) - } - - pub fn find_team(&self, f: F) -> Option<&TeamInfo> - where - F: Fn(&TeamInfo) -> bool, - { - self.teams - .iter() - .find_map(|(_, t)| Some(t).filter(|t| f(&t))) - } - - pub fn client_teams(&self, client_id: ClientId) -> impl Iterator { - client_teams_impl(&self.teams, client_id) - } - - pub fn client_team_indices(&self, client_id: ClientId) -> Vec { - self.teams - .iter() - .enumerate() - .filter(move |(_, (id, _))| *id == client_id) - .map(|(i, _)| i as u8) - .collect() - } - - pub fn clan_team_owners(&self, color: u8) -> impl Iterator + '_ { - self.teams - .iter() - .filter(move |(_, t)| t.color == color) - .map(|(id, _)| *id) - } - - pub fn find_team_owner(&self, team_name: &str) -> Option<(ClientId, &str)> { - self.teams - .iter() - .find(|(_, t)| t.name == team_name) - .map(|(id, t)| (*id, &t.name[..])) - } - - pub fn find_team_color(&self, owner_id: ClientId) -> Option { - self.client_teams(owner_id).nth(0).map(|t| t.color) - } - - pub fn has_multiple_clans(&self) -> bool { - self.teams.iter().min_by_key(|(_, t)| t.color) - != self.teams.iter().max_by_key(|(_, t)| t.color) - } - - pub fn set_config(&mut self, cfg: GameCfg) { - self.config.set_config(cfg); - } - - pub fn start_round(&mut self) { - if self.game_info.is_none() { - self.game_info = Some(GameInfo::new(self.teams.clone(), self.config.clone())); - } - } - - pub fn is_fixed(&self) -> bool { - self.flags.contains(RoomFlags::FIXED) - } - pub fn is_join_restricted(&self) -> bool { - self.flags.contains(RoomFlags::RESTRICTED_JOIN) - } - pub fn is_team_add_restricted(&self) -> bool { - self.flags.contains(RoomFlags::RESTRICTED_TEAM_ADD) - } - pub fn are_unregistered_players_restricted(&self) -> bool { - self.flags - .contains(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS) - } - - pub fn set_is_fixed(&mut self, value: bool) { - self.flags.set(RoomFlags::FIXED, value) - } - pub fn set_join_restriction(&mut self, value: bool) { - self.flags.set(RoomFlags::RESTRICTED_JOIN, value) - } - pub fn set_team_add_restriction(&mut self, value: bool) { - self.flags.set(RoomFlags::RESTRICTED_TEAM_ADD, value) - } - pub fn set_unregistered_players_restriction(&mut self, value: bool) { - self.flags - .set(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, value) - } - - fn flags_string(&self) -> String { - let mut result = "-".to_string(); - if self.game_info.is_some() { - result += "g" - } - if self.password.is_some() { - result += "p" - } - if self.is_join_restricted() { - result += "j" - } - if self.are_unregistered_players_restricted() { - result += "r" - } - result - } - - pub fn info(&self, master: Option<&HWClient>) -> Vec { - let c = &self.config; - vec![ - self.flags_string(), - self.name.clone(), - self.players_number.to_string(), - self.teams.len().to_string(), - master.map_or("[]", |c| &c.nick).to_string(), - c.map_type.to_string(), - c.script.to_string(), - c.scheme.name.to_string(), - c.ammo.name.to_string(), - ] - } - - pub fn active_config(&self) -> &RoomConfig { - match self.game_info { - Some(ref info) => &info.config, - None => &self.config, - } - } - - pub fn map_config(&self) -> Vec { - match self.game_info { - Some(ref info) => info.config.to_map_config(), - None => self.config.to_map_config(), - } - } - - pub fn game_config(&self) -> Vec { - match self.game_info { - Some(ref info) => info.config.to_game_config(), - None => self.config.to_game_config(), - } - } - - pub fn save_config(&mut self, name: String, location: String) { - self.saves.insert( - name, - RoomSave { - location, - config: self.config.clone(), - }, - ); - } - - pub fn load_config(&mut self, name: &str) -> Option<&str> { - if let Some(save) = self.saves.get(name) { - self.config = save.config.clone(); - Some(&save.location[..]) - } else { - None - } - } - - pub fn delete_config(&mut self, name: &str) -> bool { - self.saves.remove(name).is_some() - } - - pub fn get_saves(&self) -> Result { - serde_yaml::to_string(&(&self.greeting, &self.saves)) - } - - pub fn set_saves(&mut self, text: &str) -> Result<(), serde_yaml::Error> { - serde_yaml::from_str::<(String, HashMap)>(text).map( - |(greeting, saves)| { - self.greeting = greeting; - self.saves = saves; - }, - ) - } -}