Move rust server into rust folder
authorunc0rr
Mon, 10 Dec 2018 22:44:46 +0100
changeset 14415 06672690d71b
parent 14414 6843c4551cde
child 14416 96624a6cdb93
Move rust server into rust folder
gameServer2/Cargo.toml
gameServer2/src/main.rs
gameServer2/src/protocol.rs
gameServer2/src/protocol/messages.rs
gameServer2/src/protocol/parser.rs
gameServer2/src/protocol/test.rs
gameServer2/src/server.rs
gameServer2/src/server/actions.rs
gameServer2/src/server/client.rs
gameServer2/src/server/core.rs
gameServer2/src/server/coretypes.rs
gameServer2/src/server/handlers.rs
gameServer2/src/server/handlers/checker.rs
gameServer2/src/server/handlers/common.rs
gameServer2/src/server/handlers/inroom.rs
gameServer2/src/server/handlers/lobby.rs
gameServer2/src/server/handlers/loggingin.rs
gameServer2/src/server/io.rs
gameServer2/src/server/network.rs
gameServer2/src/server/room.rs
gameServer2/src/utils.rs
rust/hedgewars-server/Cargo.toml
rust/hedgewars-server/src/main.rs
rust/hedgewars-server/src/protocol.rs
rust/hedgewars-server/src/protocol/messages.rs
rust/hedgewars-server/src/protocol/parser.rs
rust/hedgewars-server/src/protocol/test.rs
rust/hedgewars-server/src/server.rs
rust/hedgewars-server/src/server/actions.rs
rust/hedgewars-server/src/server/client.rs
rust/hedgewars-server/src/server/core.rs
rust/hedgewars-server/src/server/coretypes.rs
rust/hedgewars-server/src/server/handlers.rs
rust/hedgewars-server/src/server/handlers/checker.rs
rust/hedgewars-server/src/server/handlers/common.rs
rust/hedgewars-server/src/server/handlers/inroom.rs
rust/hedgewars-server/src/server/handlers/lobby.rs
rust/hedgewars-server/src/server/handlers/loggingin.rs
rust/hedgewars-server/src/server/io.rs
rust/hedgewars-server/src/server/network.rs
rust/hedgewars-server/src/server/room.rs
rust/hedgewars-server/src/utils.rs
--- a/gameServer2/Cargo.toml	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-[package]
-edition = "2018"
-name = "hedgewars-server"
-version = "0.0.1"
-authors = [ "Andrey Korotaev <a.korotaev@hedgewars.org>" ]
-
-[features]
-official-server = ["openssl"]
-tls-connections = ["openssl"]
-default = []
-
-[dependencies]
-rand = "0.5"
-mio = "0.6"
-slab = "0.4"
-netbuf = "0.4"
-nom = "4.1"
-env_logger = "0.6"
-log = "0.4"
-base64 = "0.10"
-bitflags = "1.0"
-serde = "1.0"
-serde_yaml = "0.8"
-serde_derive = "1.0"
-openssl = { version = "0.10", optional = true }
-
-[dev-dependencies]
-proptest = "0.8"
\ No newline at end of file
--- a/gameServer2/src/main.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-#![allow(unused_imports)]
-#![deny(bare_trait_objects)]
-
-//use std::io::*;
-//use rand::Rng;
-//use std::cmp::Ordering;
-use mio::net::*;
-use mio::*;
-use log::*;
-
-mod utils;
-mod server;
-mod protocol;
-
-use crate::server::network::NetworkLayer;
-use std::time::Duration;
-
-fn main() {
-    env_logger::init();
-
-    info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION);
-
-    let address = "0.0.0.0:46631".parse().unwrap();
-    let listener = TcpListener::bind(&address).unwrap();
-
-    let poll = Poll::new().unwrap();
-    let mut hw_network = NetworkLayer::new(listener, 1024, 512);
-    hw_network.register_server(&poll).unwrap();
-
-    let mut events = Events::with_capacity(1024);
-
-    loop {
-        let timeout = if hw_network.has_pending_operations() {
-            Some(Duration::from_millis(1))
-        } else {
-            None
-        };
-        poll.poll(&mut events, timeout).unwrap();
-
-        for event in events.iter() {
-            if event.readiness() & Ready::readable() == Ready::readable() {
-                match event.token() {
-                    utils::SERVER => hw_network.accept_client(&poll).unwrap(),
-                    Token(tok) => hw_network.client_readable(&poll, tok).unwrap(),
-                }
-            }
-            if event.readiness() & Ready::writable() == Ready::writable() {
-                match event.token() {
-                    utils::SERVER => unreachable!(),
-                    Token(tok) => hw_network.client_writable(&poll, tok).unwrap(),
-                }
-            }
-//            if event.kind().is_hup() || event.kind().is_error() {
-//                match event.token() {
-//                    utils::SERVER => unreachable!(),
-//                    Token(tok) => server.client_error(&poll, tok).unwrap(),
-//                }
-//            }
-        }
-        hw_network.on_idle(&poll).unwrap();
-    }
-}
--- a/gameServer2/src/protocol.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-use netbuf;
-use std::{
-    io::{Read, Result}
-};
-use nom::{
-    IResult, Err
-};
-
-pub mod messages;
-#[cfg(test)]
-pub mod test;
-mod parser;
-
-pub struct ProtocolDecoder {
-    buf: netbuf::Buf,
-    consumed: usize,
-}
-
-impl ProtocolDecoder {
-    pub fn new() -> ProtocolDecoder {
-        ProtocolDecoder {
-            buf: netbuf::Buf::new(),
-            consumed: 0,
-        }
-    }
-
-    pub fn read_from<R: Read>(&mut self, stream: &mut R) -> Result<usize> {
-        self.buf.read_from(stream)
-    }
-
-    pub fn extract_messages(&mut self) -> Vec<messages::HWProtocolMessage> {
-        let parse_result = parser::extract_messages(&self.buf[..]);
-        match parse_result {
-            Ok((tail, msgs)) => {
-                self.consumed = self.buf.len() - self.consumed - tail.len();
-                msgs
-            },
-            Err(Err::Incomplete(_)) => unreachable!(),
-            Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(),
-        }
-    }
-
-    pub fn sweep(&mut self) {
-        self.buf.consume(self.consumed);
-        self.consumed = 0;
-    }
-}
--- a/gameServer2/src/protocol/messages.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,316 +0,0 @@
-use crate::server::coretypes::{
-    ServerVar, GameCfg, TeamInfo,
-    HedgehogInfo, VoteType
-};
-use std::{ops, convert::From, iter::once};
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum HWProtocolMessage {
-    // core
-    Ping,
-    Pong,
-    Quit(Option<String>),
-    //Cmd(String, Vec<String>),
-    Global(String),
-    Watch(String),
-    ToggleServerRegisteredOnly,
-    SuperPower,
-    Info(String),
-    // not entered state
-    Nick(String),
-    Proto(u16),
-    Password(String, String),
-    Checker(u16, String, String),
-    // lobby
-    List,
-    Chat(String),
-    CreateRoom(String, Option<String>),
-    JoinRoom(String, Option<String>),
-    Follow(String),
-    Rnd(Vec<String>),
-    Kick(String),
-    Ban(String, String, u32),
-    BanIP(String, String, u32),
-    BanNick(String, String, u32),
-    BanList,
-    Unban(String),
-    SetServerVar(ServerVar),
-    GetServerVar,
-    RestartServer,
-    Stats,
-    // in room
-    Part(Option<String>),
-    Cfg(GameCfg),
-    AddTeam(Box<TeamInfo>),
-    RemoveTeam(String),
-    SetHedgehogsNumber(String, u8),
-    SetTeamColor(String, u8),
-    ToggleReady,
-    StartGame,
-    EngineMessage(String),
-    RoundFinished,
-    ToggleRestrictJoin,
-    ToggleRestrictTeams,
-    ToggleRegisteredOnly,
-    RoomName(String),
-    Delegate(String),
-    TeamChat(String),
-    MaxTeams(u8),
-    Fix,
-    Unfix,
-    Greeting(String),
-    CallVote(Option<VoteType>),
-    Vote(bool),
-    ForceVote(bool),
-    Save(String, String),
-    Delete(String),
-    SaveRoom(String),
-    LoadRoom(String),
-    Malformed,
-    Empty,
-}
-
-#[derive(Debug)]
-pub enum HWServerMessage {
-    Ping,
-    Pong,
-    Bye(String),
-    Nick(String),
-    Proto(u16),
-    ServerAuth(String),
-    LobbyLeft(String, String),
-    LobbyJoined(Vec<String>),
-    ChatMsg {nick: String, msg: String},
-    ClientFlags(String, Vec<String>),
-    Rooms(Vec<String>),
-    RoomAdd(Vec<String>),
-    RoomJoined(Vec<String>),
-    RoomLeft(String, String),
-    RoomRemove(String),
-    RoomUpdated(String, Vec<String>),
-    TeamAdd(Vec<String>),
-    TeamRemove(String),
-    TeamAccepted(String),
-    TeamColor(String, u8),
-    HedgehogsNumber(String, u8),
-    ConfigEntry(String, Vec<String>),
-    Kicked,
-    RunGame,
-    ForwardEngineMessage(Vec<String>),
-    RoundFinished,
-
-    ServerMessage(String),
-    Notice(String),
-    Warning(String),
-    Error(String),
-    Connected(u32),
-    Unreachable,
-
-    //Deprecated messages
-    LegacyReady(bool, Vec<String>)
-}
-
-pub fn server_chat(msg: String) -> HWServerMessage  {
-    HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg }
-}
-
-impl GameCfg {
-    pub fn to_protocol(&self) -> (String, Vec<String>) {
-        use crate::server::coretypes::GameCfg::*;
-        match self {
-            FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]),
-            MapType(t) => ("MAP".to_string(), vec![t.to_string()]),
-            MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]),
-            MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]),
-            Seed(s) => ("SEED".to_string(), vec![s.to_string()]),
-            Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]),
-
-            Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]),
-            Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]),
-            Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]),
-            Scheme(n, s) => ("SCHEME".to_string(), {
-                let mut v = vec![n.to_string()];
-                v.extend(s.clone().into_iter());
-                v
-            }),
-            Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]),
-            Theme(t) => ("THEME".to_string(), vec![t.to_string()]),
-            DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()])
-        }
-    }
-
-    pub fn to_server_msg(&self) -> HWServerMessage {
-        use self::HWServerMessage::ConfigEntry;
-        let (name, args) = self.to_protocol();
-        HWServerMessage::ConfigEntry(name, args)
-    }
-}
-
-macro_rules! const_braces {
-    ($e: expr) => { "{}\n" }
-}
-
-macro_rules! msg {
-    [$($part: expr),*] => {
-        format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
-    };
-}
-
-#[cfg(test)]
-macro_rules! several {
-    [$part: expr] => { once($part) };
-    [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) };
-}
-
-impl HWProtocolMessage {
-    /** Converts the message to a raw `String`, which can be sent over the network.
-     *
-     * This is the inverse of the `message` parser.
-     */
-    #[cfg(test)]
-    pub(crate) fn to_raw_protocol(&self) -> String {
-        use self::HWProtocolMessage::*;
-        match self {
-            Ping => msg!["PING"],
-            Pong => msg!["PONG"],
-            Quit(None) => msg!["QUIT"],
-            Quit(Some(msg)) => msg!["QUIT", msg],
-            Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)],
-            Watch(name) => msg!["CMD", format!("WATCH {}", name)],
-            ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
-            SuperPower =>  msg!["CMD", "SUPER_POWER"],
-            Info(info) => msg!["CMD", format!("INFO {}", info)],
-            Nick(nick) => msg!("NICK", nick),
-            Proto(version) => msg!["PROTO", version],
-            Password(p, s) => msg!["PASSWORD", p, s],
-            Checker(i, n, p) => msg!["CHECKER", i, n, p],
-            List => msg!["LIST"],
-            Chat(msg) => msg!["CHAT", msg],
-            CreateRoom(name, None) => msg!["CREATE_ROOM", name],
-            CreateRoom(name, Some(password)) =>
-                msg!["CREATE_ROOM", name, password],
-            JoinRoom(name, None) => msg!["JOIN_ROOM", name],
-            JoinRoom(name, Some(password)) =>
-                msg!["JOIN_ROOM", name, password],
-            Follow(name) => msg!["FOLLOW", name],
-            Rnd(args) => if args.is_empty() {
-                msg!["CMD", "RND"]
-            } else {
-                msg!["CMD", format!("RND {}", args.join(" "))]
-            },
-            Kick(name) => msg!["KICK", name],
-            Ban(name, reason, time) => msg!["BAN", name, reason, time],
-            BanIP(ip, reason, time) => msg!["BAN_IP", ip, reason, time],
-            BanNick(nick, reason, time) =>
-                msg!("BAN_NICK", nick, reason, time),
-            BanList => msg!["BANLIST"],
-            Unban(name) => msg!["UNBAN", name],
-            //SetServerVar(ServerVar), ???
-            GetServerVar => msg!["GET_SERVER_VAR"],
-            RestartServer => msg!["CMD", "RESTART_SERVER YES"],
-            Stats => msg!["CMD", "STATS"],
-            Part(None) => msg!["PART"],
-            Part(Some(msg)) => msg!["PART", msg],
-            Cfg(config) => {
-                let (name, args) = config.to_protocol();
-                msg!["CFG", name, args.join("\n")]
-            },
-            AddTeam(info) =>
-                msg!["ADD_TEAM", info.name, info.color, info.grave, info.fort,
-                     info.voice_pack, info.flag, info.difficulty,
-                     info.hedgehogs.iter()
-                        .flat_map(|h| several![&h.name[..], &h.hat[..]])
-                        .collect::<Vec<_>>().join("\n")],
-            RemoveTeam(name) => msg!["REMOVE_TEAM", name],
-            SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number],
-            SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color],
-            ToggleReady => msg!["TOGGLE_READY"],
-            StartGame => msg!["START_GAME"],
-            EngineMessage(msg) => msg!["EM", msg],
-            RoundFinished => msg!["ROUNDFINISHED"],
-            ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"],
-            ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"],
-            ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"],
-            RoomName(name) => msg!["ROOM_NAME", name],
-            Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)],
-            TeamChat(msg) => msg!["TEAMCHAT", msg],
-            MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)] ,
-            Fix => msg!["CMD", "FIX"],
-            Unfix => msg!["CMD", "UNFIX"],
-            Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)],
-            //CallVote(Option<(String, Option<String>)>) =>, ??
-            Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg {"YES"} else {"NO"})],
-            ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg {"YES"} else {"NO"})],
-            Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)],
-            Delete(name) => msg!["CMD", format!("DELETE {}", name)],
-            SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)],
-            LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)],
-            Malformed => msg!["A", "QUICK", "BROWN", "HOG", "JUMPS", "OVER", "THE", "LAZY", "DOG"],
-            Empty => msg![""],
-            _ => panic!("Protocol message not yet implemented")
-        }
-    }
-}
-
-fn construct_message(header: &[&str], msg: &[String]) -> String {
-    let mut v: Vec<_> = header.iter().cloned().collect();
-    v.extend(msg.iter().map(|s| &s[..]));
-    v.push("\n");
-    v.join("\n")
-}
-
-impl HWServerMessage {
-    pub fn to_raw_protocol(&self) -> String {
-        use self::HWServerMessage::*;
-        match self {
-            Ping => msg!["PING"],
-            Pong => msg!["PONG"],
-            Connected(protocol_version) => msg![
-                "CONNECTED",
-                "Hedgewars server https://www.hedgewars.org/",
-                protocol_version],
-            Bye(msg) => msg!["BYE", msg],
-            Nick(nick) => msg!["NICK", nick],
-            Proto(proto) => msg!["PROTO", proto],
-            ServerAuth(hash) => msg!["SERVER_AUTH", hash],
-            LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg],
-            LobbyJoined(nicks) =>
-                construct_message(&["LOBBY:JOINED"], &nicks),
-            ClientFlags(flags, nicks) =>
-                construct_message(&["CLIENT_FLAGS", flags], &nicks),
-            Rooms(info) =>
-                construct_message(&["ROOMS"], &info),
-            RoomAdd(info) =>
-                construct_message(&["ROOM", "ADD"], &info),
-            RoomJoined(nicks) =>
-                construct_message(&["JOINED"], &nicks),
-            RoomLeft(nick, msg) => msg!["LEFT", nick, msg],
-            RoomRemove(name) => msg!["ROOM", "DEL", name],
-            RoomUpdated(name, info) =>
-                construct_message(&["ROOM", "UPD", name], &info),
-            TeamAdd(info) =>
-                construct_message(&["ADD_TEAM"], &info),
-            TeamRemove(name) => msg!["REMOVE_TEAM", name],
-            TeamAccepted(name) => msg!["TEAM_ACCEPTED", name],
-            TeamColor(name, color) => msg!["TEAM_COLOR", name, color],
-            HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
-            ConfigEntry(name, values) =>
-                construct_message(&["CFG", name], &values),
-            Kicked => msg!["KICKED"],
-            RunGame => msg!["RUN_GAME"],
-            ForwardEngineMessage(em) =>
-                construct_message(&["EM"], &em),
-            RoundFinished => msg!["ROUND_FINISHED"],
-            ChatMsg {nick, msg} => msg!["CHAT", nick, msg],
-            ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
-            Notice(msg) => msg!["NOTICE", msg],
-            Warning(msg) => msg!["WARNING", msg],
-            Error(msg) => msg!["ERROR", msg],
-
-            LegacyReady(is_ready, nicks) =>
-                construct_message(&[if *is_ready {"READY"} else {"NOT_READY"}], &nicks),
-
-            _ => msg!["ERROR", "UNIMPLEMENTED"],
-        }
-    }
-}
--- a/gameServer2/src/protocol/parser.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-/** The parsers for the chat and multiplayer protocol. The main parser is `message`.
- * # Protocol
- * All messages consist of `\n`-separated strings. The end of a message is
- * indicated by a double newline - `\n\n`.
- *
- * For example, a nullary command like PING will be actually sent as `PING\n\n`.
- * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
- */
-
-use nom::*;
-
-use std::{
-    str, str::FromStr,
-    ops::Range
-};
-use super::{
-    messages::{HWProtocolMessage, HWProtocolMessage::*}
-};
-#[cfg(test)]
-use {
-    super::test::gen_proto_msg,
-    proptest::{proptest, proptest_helper}
-};
-use crate::server::coretypes::{
-    HedgehogInfo, TeamInfo, GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM
-};
-
-named!(end_of_message, tag!("\n\n"));
-named!(str_line<&[u8],   &str>, map_res!(not_line_ending, str::from_utf8));
-named!(  a_line<&[u8], String>, map!(str_line, String::from));
-named!(cmd_arg<&[u8], String>,
-    map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from));
-named!( u8_line<&[u8],     u8>, map_res!(str_line, FromStr::from_str));
-named!(u16_line<&[u8],    u16>, map_res!(str_line, FromStr::from_str));
-named!(u32_line<&[u8],    u32>, map_res!(str_line, FromStr::from_str));
-named!(yes_no_line<&[u8], bool>, alt!(
-      do_parse!(tag_no_case!("YES") >> (true))
-    | do_parse!(tag_no_case!("NO") >> (false))));
-named!(opt_param<&[u8], Option<String> >, alt!(
-      do_parse!(peek!(tag!("\n\n")) >> (None))
-    | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string())))));
-named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" ")));
-named!(opt_space_param<&[u8], Option<String> >, alt!(
-      do_parse!(peek!(tag!("\n\n")) >> (None))
-    | do_parse!(spaces >> s: str_line >> (Some(s.to_string())))));
-named!(hog_line<&[u8], HedgehogInfo>,
-    do_parse!(name: str_line >> eol >> hat: str_line >>
-        (HedgehogInfo{name: name.to_string(), hat: hat.to_string()})));
-named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>,
-    do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >>
-              h3: hog_line >> eol >> h4: hog_line >> eol >>
-              h5: hog_line >> eol >> h6: hog_line >> eol >>
-              h7: hog_line >> eol >> h8: hog_line >>
-              ([h1, h2, h3, h4, h5, h6, h7, h8])));
-named!(voting<&[u8], VoteType>, alt!(
-      do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >>
-        (VoteType::Kick(n)))
-    | do_parse!(tag_no_case!("MAP") >>
-        n: opt!(preceded!(spaces, a_line)) >>
-        (VoteType::Map(n)))
-    | do_parse!(tag_no_case!("PAUSE") >>
-        (VoteType::Pause))
-    | do_parse!(tag_no_case!("NEWSEED") >>
-        (VoteType::NewSeed))
-    | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >>
-        (VoteType::HedgehogsPerTeam(n)))));
-
-/** Recognizes messages which do not take any parameters */
-named!(basic_message<&[u8], HWProtocolMessage>, alt!(
-      do_parse!(tag!("PING") >> (Ping))
-    | do_parse!(tag!("PONG") >> (Pong))
-    | do_parse!(tag!("LIST") >> (List))
-    | do_parse!(tag!("BANLIST")        >> (BanList))
-    | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
-    | do_parse!(tag!("TOGGLE_READY")   >> (ToggleReady))
-    | do_parse!(tag!("START_GAME")     >> (StartGame))
-    | do_parse!(tag!("ROUNDFINISHED")  >> _m: opt_param >> (RoundFinished))
-    | do_parse!(tag!("TOGGLE_RESTRICT_JOINS")  >> (ToggleRestrictJoin))
-    | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS")  >> (ToggleRestrictTeams))
-    | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
-));
-
-/** Recognizes messages which take exactly one parameter */
-named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
-      do_parse!(tag!("NICK")    >> eol >> n: a_line >> (Nick(n)))
-    | do_parse!(tag!("INFO")    >> eol >> n: a_line >> (Info(n)))
-    | do_parse!(tag!("CHAT")    >> eol >> m: a_line >> (Chat(m)))
-    | do_parse!(tag!("PART")    >> msg: opt_param   >> (Part(msg)))
-    | do_parse!(tag!("FOLLOW")  >> eol >> n: a_line >> (Follow(n)))
-    | do_parse!(tag!("KICK")    >> eol >> n: a_line >> (Kick(n)))
-    | do_parse!(tag!("UNBAN")   >> eol >> n: a_line >> (Unban(n)))
-    | do_parse!(tag!("EM")      >> eol >> m: a_line >> (EngineMessage(m)))
-    | do_parse!(tag!("TEAMCHAT")    >> eol >> m: a_line >> (TeamChat(m)))
-    | do_parse!(tag!("ROOM_NAME")   >> eol >> n: a_line >> (RoomName(n)))
-    | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
-
-    | do_parse!(tag!("PROTO")   >> eol >> d: u16_line >> (Proto(d)))
-
-    | do_parse!(tag!("QUIT")   >> msg: opt_param >> (Quit(msg)))
-));
-
-/** Recognizes messages preceded with CMD */
-named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
-      do_parse!(tag_no_case!("STATS") >> (Stats))
-    | do_parse!(tag_no_case!("FIX")   >> (Fix))
-    | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
-    | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer))
-    | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
-    | do_parse!(tag_no_case!("SUPER_POWER")     >> (SuperPower))
-    | do_parse!(tag_no_case!("PART")     >> m: opt_space_param >> (Part(m)))
-    | do_parse!(tag_no_case!("QUIT")     >> m: opt_space_param >> (Quit(m)))
-    | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line  >> (Delegate(n)))
-    | do_parse!(tag_no_case!("SAVE")     >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l)))
-    | do_parse!(tag_no_case!("DELETE")   >> spaces >> n: a_line  >> (Delete(n)))
-    | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line  >> (SaveRoom(r)))
-    | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line  >> (LoadRoom(r)))
-    | do_parse!(tag_no_case!("GLOBAL")   >> spaces >> m: a_line  >> (Global(m)))
-    | do_parse!(tag_no_case!("WATCH")    >> spaces >> i: a_line  >> (Watch(i)))
-    | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line  >> (Greeting(m)))
-    | do_parse!(tag_no_case!("VOTE")     >> spaces >> m: yes_no_line >> (Vote(m)))
-    | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: yes_no_line >> (ForceVote(m)))
-    | do_parse!(tag_no_case!("INFO")     >> spaces >> n: a_line  >> (Info(n)))
-    | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n)))
-    | do_parse!(tag_no_case!("CALLVOTE") >>
-        v: opt!(preceded!(spaces, voting)) >> (CallVote(v)))
-    | do_parse!(
-        tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >>
-        v: str_line >>
-        (Rnd(v.split_whitespace().map(String::from).collect())))
-)));
-
-named!(complex_message<&[u8], HWProtocolMessage>, alt!(
-      do_parse!(tag!("PASSWORD")  >> eol >>
-                    p: a_line     >> eol >>
-                    s: a_line     >>
-                    (Password(p, s)))
-    | do_parse!(tag!("CHECKER")   >> eol >>
-                    i: u16_line   >> eol >>
-                    n: a_line     >> eol >>
-                    p: a_line     >>
-                    (Checker(i, n, p)))
-    | do_parse!(tag!("CREATE_ROOM") >> eol >>
-                    n: a_line       >>
-                    p: opt_param    >>
-                    (CreateRoom(n, p)))
-    | do_parse!(tag!("JOIN_ROOM")   >> eol >>
-                    n: a_line       >>
-                    p: opt_param    >>
-                    (JoinRoom(n, p)))
-    | do_parse!(tag!("ADD_TEAM")    >> eol >>
-                    name: a_line    >> eol >>
-                    color: u8_line  >> eol >>
-                    grave: a_line   >> eol >>
-                    fort: a_line    >> eol >>
-                    voice_pack: a_line >> eol >>
-                    flag: a_line    >> eol >>
-                    difficulty: u8_line >> eol >>
-                    hedgehogs: _8_hogs >>
-                    (AddTeam(Box::new(TeamInfo{
-                        name, color, grave, fort,
-                        voice_pack, flag, difficulty,
-                        hedgehogs, hedgehogs_number: 0
-                     }))))
-    | do_parse!(tag!("HH_NUM")    >> eol >>
-                    n: a_line     >> eol >>
-                    c: u8_line    >>
-                    (SetHedgehogsNumber(n, c)))
-    | do_parse!(tag!("TEAM_COLOR")    >> eol >>
-                    n: a_line     >> eol >>
-                    c: u8_line    >>
-                    (SetTeamColor(n, c)))
-    | do_parse!(tag!("BAN")    >> eol >>
-                    n: a_line     >> eol >>
-                    r: a_line     >> eol >>
-                    t: u32_line   >>
-                    (Ban(n, r, t)))
-    | do_parse!(tag!("BAN_IP")    >> eol >>
-                    n: a_line     >> eol >>
-                    r: a_line     >> eol >>
-                    t: u32_line   >>
-                    (BanIP(n, r, t)))
-    | do_parse!(tag!("BAN_NICK")    >> eol >>
-                    n: a_line     >> eol >>
-                    r: a_line     >> eol >>
-                    t: u32_line   >>
-                    (BanNick(n, r, t)))
-));
-
-named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!(
-      do_parse!(tag!("THEME")    >> eol >>
-                name: a_line     >>
-                (GameCfg::Theme(name)))
-    | do_parse!(tag!("SCRIPT")   >> eol >>
-                name: a_line     >>
-                (GameCfg::Script(name)))
-    | do_parse!(tag!("AMMO")     >> eol >>
-                name: a_line     >>
-                value: opt_param >>
-                (GameCfg::Ammo(name, value)))
-    | do_parse!(tag!("SCHEME")   >> eol >>
-                name: a_line     >>
-                values: opt!(preceded!(eol, separated_list!(eol, a_line))) >>
-                (GameCfg::Scheme(name, values.unwrap_or_default())))
-    | do_parse!(tag!("FEATURE_SIZE") >> eol >>
-                value: u32_line    >>
-                (GameCfg::FeatureSize(value)))
-    | do_parse!(tag!("MAP")      >> eol >>
-                value: a_line    >>
-                (GameCfg::MapType(value)))
-    | do_parse!(tag!("MAPGEN")   >> eol >>
-                value: u32_line  >>
-                (GameCfg::MapGenerator(value)))
-    | do_parse!(tag!("MAZE_SIZE") >> eol >>
-                value: u32_line   >>
-                (GameCfg::MazeSize(value)))
-    | do_parse!(tag!("SEED")     >> eol >>
-                value: a_line    >>
-                (GameCfg::Seed(value)))
-    | do_parse!(tag!("TEMPLATE") >> eol >>
-                value: u32_line  >>
-                (GameCfg::Template(value)))
-    | do_parse!(tag!("DRAWNMAP") >> eol >>
-                value: a_line    >>
-                (GameCfg::DrawnMap(value)))
-), Cfg)));
-
-named!(malformed_message<&[u8], HWProtocolMessage>,
-    do_parse!(separated_list!(eol, a_line) >> (Malformed)));
-
-named!(empty_message<&[u8], HWProtocolMessage>,
-    do_parse!(alt!(end_of_message | eol) >> (Empty)));
-
-named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
-    alt!(
-          basic_message
-        | one_param_message
-        | cmd_message
-        | complex_message
-        | cfg_message
-        ), end_of_message
-    )
-    | terminated!(malformed_message, end_of_message)
-    | empty_message
-    )
-);
-
-named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message)));
-
-#[cfg(test)]
-proptest! {
-    #[test]
-    fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
-        println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
-        assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
-    }
-}
-
-#[test]
-fn parse_test() {
-    assert_eq!(message(b"PING\n\n"),          Ok((&b""[..], Ping)));
-    assert_eq!(message(b"START_GAME\n\n"),    Ok((&b""[..], StartGame)));
-    assert_eq!(message(b"NICK\nit's me\n\n"), Ok((&b""[..], Nick("it's me".to_string()))));
-    assert_eq!(message(b"PROTO\n51\n\n"),     Ok((&b""[..], Proto(51))));
-    assert_eq!(message(b"QUIT\nbye-bye\n\n"), Ok((&b""[..], Quit(Some("bye-bye".to_string())))));
-    assert_eq!(message(b"QUIT\n\n"),          Ok((&b""[..], Quit(None))));
-    assert_eq!(message(b"CMD\nwatch demo\n\n"), Ok((&b""[..], Watch("demo".to_string()))));
-    assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77))));
-
-    assert_eq!(message(b"CMD\nPART\n\n"),      Ok((&b""[..], Part(None))));
-    assert_eq!(message(b"CMD\nPART _msg_\n\n"), Ok((&b""[..], Part(Some("_msg_".to_string())))));
-
-    assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![]))));
-    assert_eq!(
-        message(b"CMD\nRND A B\n\n"),
-        Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")])))
-    );
-
-    assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"),    Ok((&b""[..], vec![Malformed])));
-
-    assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), Ok((&b"P"[..], vec![Ping, Ping])));
-    assert_eq!(extract_messages(b"SING\n\nPING\n\n"),  Ok((&b""[..],  vec![Malformed, Ping])));
-    assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"),  Ok((&b""[..],  vec![Empty, Empty, Ping])));
-    assert_eq!(extract_messages(b"\n\n\nPING\n\n"),    Ok((&b""[..],  vec![Empty, Empty, Ping])));
-}
--- a/gameServer2/src/protocol/test.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-use proptest::{
-    test_runner::{TestRunner, Reason},
-    arbitrary::{any, any_with, Arbitrary, StrategyFor},
-    strategy::{Strategy, BoxedStrategy, Just, Map}
-};
-
-use crate::server::coretypes::{GameCfg, TeamInfo, HedgehogInfo};
-
-use super::messages::{
-    HWProtocolMessage, HWProtocolMessage::*
-};
-
-// Due to inability to define From between Options
-trait Into2<T>: Sized { fn into2(self) -> T; }
-impl <T> Into2<T> for T { fn into2(self) -> T { self } }
-impl Into2<Vec<String>> for Vec<Ascii> {
-    fn into2(self) -> Vec<String> {
-        self.into_iter().map(|x| x.0).collect()
-    }
-}
-impl Into2<String> for Ascii { fn into2(self) -> String { self.0 } }
-impl Into2<Option<String>> for Option<Ascii>{
-    fn into2(self) -> Option<String> { self.map(|x| {x.0}) }
-}
-
-macro_rules! proto_msg_case {
-    ($val: ident()) =>
-        (Just($val));
-    ($val: ident($arg: ty)) =>
-        (any::<$arg>().prop_map(|v| {$val(v.into2())}));
-    ($val: ident($arg1: ty, $arg2: ty)) =>
-        (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())}));
-    ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) =>
-        (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())}));
-}
-
-macro_rules! proto_msg_match {
-    ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => (
-        match $var {
-            $($num => (proto_msg_case!($constr $res)).boxed()),*,
-            _ => Just($default).boxed()
-        }
-    )
-}
-
-/// Wrapper type for generating non-empty strings
-#[derive(Debug)]
-struct Ascii(String);
-
-impl Arbitrary for Ascii {
-    type Parameters = <String as Arbitrary>::Parameters;
-
-    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-        "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
-    }
-
-    type Strategy = BoxedStrategy<Ascii>;
-}
-
-impl Arbitrary for GameCfg {
-    type Parameters = ();
-
-    fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
-        use crate::server::coretypes::GameCfg::*;
-        (0..10).no_shrink().prop_flat_map(|i| {
-            proto_msg_match!(i, def = FeatureSize(0),
-            0 => FeatureSize(u32),
-            1 => MapType(Ascii),
-            2 => MapGenerator(u32),
-            3 => MazeSize(u32),
-            4 => Seed(Ascii),
-            5 => Template(u32),
-            6 => Ammo(Ascii, Option<Ascii>),
-            7 => Scheme(Ascii, Vec<Ascii>),
-            8 => Script(Ascii),
-            9 => Theme(Ascii),
-            10 => DrawnMap(Ascii))
-        }).boxed()
-    }
-
-    type Strategy = BoxedStrategy<GameCfg>;
-}
-
-impl Arbitrary for TeamInfo {
-    type Parameters = ();
-
-    fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
-        ("[a-z]+", 0u8..127u8, "[a-z]+", "[a-z]+", "[a-z]+", "[a-z]+",  0u8..127u8)
-            .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| {
-                fn hog(n: u8) -> HedgehogInfo {
-                    HedgehogInfo { name: format!("hog{}", n), hat: format!("hat{}", n)}
-                }
-                let hedgehogs = [hog(1), hog(2), hog(3), hog(4), hog(5), hog(6), hog(7), hog(8)];
-                TeamInfo {
-                    name, color, grave, fort,
-                    voice_pack, flag,difficulty,
-                    hedgehogs, hedgehogs_number: 0
-                }
-            }).boxed()
-    }
-
-    type Strategy = BoxedStrategy<TeamInfo>;
-}
-
-pub fn gen_proto_msg() -> BoxedStrategy<HWProtocolMessage> where {
-    let res = (0..58).no_shrink().prop_flat_map(|i| {
-        proto_msg_match!(i, def = Malformed,
-        0 => Ping(),
-        1 => Pong(),
-        2 => Quit(Option<Ascii>),
-        //3 => Cmd
-        4 => Global(Ascii),
-        5 => Watch(Ascii),
-        6 => ToggleServerRegisteredOnly(),
-        7 => SuperPower(),
-        8 => Info(Ascii),
-        9 => Nick(Ascii),
-        10 => Proto(u16),
-        11 => Password(Ascii, Ascii),
-        12 => Checker(u16, Ascii, Ascii),
-        13 => List(),
-        14 => Chat(Ascii),
-        15 => CreateRoom(Ascii, Option<Ascii>),
-        16 => JoinRoom(Ascii, Option<Ascii>),
-        17 => Follow(Ascii),
-        18 => Rnd(Vec<Ascii>),
-        19 => Kick(Ascii),
-        20 => Ban(Ascii, Ascii, u32),
-        21 => BanIP(Ascii, Ascii, u32),
-        22 => BanNick(Ascii, Ascii, u32),
-        23 => BanList(),
-        24 => Unban(Ascii),
-        //25 => SetServerVar(ServerVar),
-        26 => GetServerVar(),
-        27 => RestartServer(),
-        28 => Stats(),
-        29 => Part(Option<Ascii>),
-        30 => Cfg(GameCfg),
-        31 => AddTeam(Box<TeamInfo>),
-        32 => RemoveTeam(Ascii),
-        33 => SetHedgehogsNumber(Ascii, u8),
-        34 => SetTeamColor(Ascii, u8),
-        35 => ToggleReady(),
-        36 => StartGame(),
-        37 => EngineMessage(Ascii),
-        38 => RoundFinished(),
-        39 => ToggleRestrictJoin(),
-        40 => ToggleRestrictTeams(),
-        41 => ToggleRegisteredOnly(),
-        42 => RoomName(Ascii),
-        43 => Delegate(Ascii),
-        44 => TeamChat(Ascii),
-        45 => MaxTeams(u8),
-        46 => Fix(),
-        47 => Unfix(),
-        48 => Greeting(Ascii),
-        //49 => CallVote(Option<(String, Option<String>)>),
-        50 => Vote(bool),
-        51 => ForceVote(bool),
-        52 => Save(Ascii, Ascii),
-        53 => Delete(Ascii),
-        54 => SaveRoom(Ascii),
-        55 => LoadRoom(Ascii),
-        56 => Malformed(),
-        57 => Empty()
-    )});
-    res.boxed()
-}
--- a/gameServer2/src/server.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-pub mod core;
-pub mod client;
-pub mod io;
-pub mod room;
-pub mod network;
-pub mod coretypes;
-mod actions;
-mod handlers;
--- a/gameServer2/src/server/actions.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,631 +0,0 @@
-use std::{
-    io, io::Write,
-    iter::once,
-    mem::replace
-};
-use super::{
-    core::HWServer,
-    room::{GameInfo, RoomFlags},
-    client::HWClient,
-    coretypes::{ClientId, RoomId, GameCfg, VoteType},
-    room::HWRoom,
-    handlers
-};
-use crate::{
-    protocol::messages::{
-        HWProtocolMessage,
-        HWServerMessage,
-        HWServerMessage::*,
-        server_chat
-    },
-    utils::to_engine_msg
-};
-use rand::{thread_rng, Rng, distributions::Uniform};
-
-pub enum Destination {
-    ToId(ClientId),
-    ToSelf,
-    ToAll {
-        room_id: Option<RoomId>,
-        protocol: Option<u16>,
-        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_self(message: HWServerMessage) -> PendingMessage {
-        PendingMessage{ destination: Destination::ToSelf, message }
-    }
-
-    pub fn send_all(message: HWServerMessage) -> PendingMessage {
-        let destination = Destination::ToAll {
-            room_id: None,
-            protocol: None,
-            skip_self: false,
-        };
-        PendingMessage{ destination, message }
-    }
-
-    pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage {
-        if let Destination::ToAll {ref mut room_id, ..} = self.destination {
-            *room_id = Some(clients_room_id)
-        }
-        self
-    }
-
-    pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage {
-        if let Destination::ToAll {ref mut protocol, ..} = self.destination {
-            *protocol = Some(protocol_number)
-        }
-        self
-    }
-
-    pub fn but_self(mut self) -> PendingMessage {
-        if let Destination::ToAll {ref mut skip_self, ..} = self.destination {
-            *skip_self = true
-        }
-        self
-    }
-
-    pub fn action(self) -> Action { Send(self) }
-}
-
-impl Into<Action> for PendingMessage {
-    fn into(self) -> Action { self.action() }
-}
-
-impl HWServerMessage {
-    pub fn send(self, client_id: ClientId) -> PendingMessage { PendingMessage::send(self, client_id) }
-    pub fn send_self(self) -> PendingMessage { PendingMessage::send_self(self) }
-    pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) }
-}
-
-pub enum Action {
-    Send(PendingMessage),
-    RemoveClient,
-    ByeClient(String),
-    ReactProtocolMessage(HWProtocolMessage),
-    CheckRegistered,
-    JoinLobby,
-    AddRoom(String, Option<String>),
-    RemoveRoom(RoomId),
-    MoveToRoom(RoomId),
-    MoveToLobby(String),
-    ChangeMaster(RoomId, Option<ClientId>),
-    RemoveTeam(String),
-    RemoveClientTeams,
-    SendRoomUpdate(Option<String>),
-    StartRoomGame(RoomId),
-    SendTeamRemovalMessage(String),
-    FinishRoomGame(RoomId),
-    SendRoomData{to: ClientId, teams: bool, config: bool, flags: bool},
-    AddVote{vote: bool, is_forced: bool},
-    ApplyVoting(VoteType, RoomId),
-    Warn(String),
-    ProtocolError(String)
-}
-
-use self::Action::*;
-
-pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) {
-    match action {
-        Send(msg) => server.send(client_id, &msg.destination, msg.message),
-        ByeClient(msg) => {
-            let c = &server.clients[client_id];
-            let nick = c.nick.clone();
-
-            if let Some(id) = c.room_id{
-                if id != server.lobby_id {
-                    server.react(client_id, vec![
-                        MoveToLobby(format!("quit: {}", msg.clone()))]);
-                }
-            }
-
-            server.react(client_id, vec![
-                LobbyLeft(nick, msg.clone()).send_all().action(),
-                Bye(msg).send_self().action(),
-                RemoveClient]);
-        },
-        RemoveClient => {
-            server.removed_clients.push(client_id);
-            if server.clients.contains(client_id) {
-                server.clients.remove(client_id);
-            }
-        },
-        ReactProtocolMessage(msg) =>
-            handlers::handle(server, client_id, msg),
-        CheckRegistered => {
-            let client = &server.clients[client_id];
-            if client.protocol_number > 0 && client.nick != "" {
-                let has_nick_clash = server.clients.iter().any(
-                    |(id, c)| id != client_id && c.nick == client.nick);
-
-                let actions = if !client.is_checker() && has_nick_clash {
-                    if client.protocol_number < 38 {
-                        vec![ByeClient("Nickname is already in use".to_string())]
-                    } else {
-                        server.clients[client_id].nick.clear();
-                        vec![Notice("NickAlreadyInUse".to_string()).send_self().action()]
-                    }
-                } else {
-                    vec![JoinLobby]
-                };
-                server.react(client_id, actions);
-            }
-        },
-        JoinLobby => {
-            server.clients[client_id].room_id = Some(server.lobby_id);
-
-            let mut lobby_nicks = Vec::new();
-            for (_, c) in server.clients.iter() {
-                if c.room_id.is_some() {
-                    lobby_nicks.push(c.nick.clone());
-                }
-            }
-            let joined_msg = LobbyJoined(lobby_nicks);
-
-            let everyone_msg = LobbyJoined(vec![server.clients[client_id].nick.clone()]);
-            let flags_msg = ClientFlags(
-                "+i".to_string(),
-                server.clients.iter()
-                    .filter(|(_, c)| c.room_id.is_some())
-                    .map(|(_, c)| c.nick.clone())
-                    .collect());
-            let server_msg = ServerMessage("\u{1f994} is watching".to_string());
-            let rooms_msg = Rooms(server.rooms.iter()
-                .filter(|(id, _)| *id != server.lobby_id)
-                .flat_map(|(_, r)|
-                    r.info(r.master_id.map(|id| &server.clients[id])))
-                .collect());
-            server.react(client_id, vec![
-                everyone_msg.send_all().but_self().action(),
-                joined_msg.send_self().action(),
-                flags_msg.send_self().action(),
-                server_msg.send_self().action(),
-                rooms_msg.send_self().action(),
-                ]);
-        },
-        AddRoom(name, password) => {
-            let room_id = server.add_room();;
-
-            let r = &mut server.rooms[room_id];
-            let c = &mut server.clients[client_id];
-            r.master_id = Some(c.id);
-            r.name = name;
-            r.password = password;
-            r.protocol_number = c.protocol_number;
-
-            let actions = vec![
-                RoomAdd(r.info(Some(&c))).send_all()
-                    .with_protocol(r.protocol_number).action(),
-                MoveToRoom(room_id)];
-
-            server.react(client_id, actions);
-        },
-        RemoveRoom(room_id) => {
-            let r = &mut server.rooms[room_id];
-            let actions = vec![RoomRemove(r.name.clone()).send_all()
-                .with_protocol(r.protocol_number).action()];
-            server.rooms.remove(room_id);
-            server.react(client_id, actions);
-        }
-        MoveToRoom(room_id) => {
-            let r = &mut server.rooms[room_id];
-            let c = &mut server.clients[client_id];
-            r.players_number += 1;
-            c.room_id = Some(room_id);
-
-            let is_master = r.master_id == Some(c.id);
-            c.set_is_master(is_master);
-            c.set_is_ready(is_master);
-            c.set_is_joined_mid_game(false);
-
-            if is_master {
-                r.ready_players_number += 1;
-            }
-
-            let mut v = vec![
-                RoomJoined(vec![c.nick.clone()]).send_all().in_room(room_id).action(),
-                ClientFlags("+i".to_string(), vec![c.nick.clone()]).send_all().action(),
-                SendRoomUpdate(None)];
-
-            if !r.greeting.is_empty() {
-                v.push(ChatMsg {nick: "[greeting]".to_string(), msg: r.greeting.clone()}
-                    .send_self().action());
-            }
-
-            if !c.is_master() {
-                let team_names: Vec<_>;
-                if let Some(ref mut info) = r.game_info {
-                    c.set_is_in_game(true);
-                    c.set_is_joined_mid_game(true);
-
-                    {
-                        let teams = info.client_teams(c.id);
-                        c.teams_in_game = teams.clone().count() as u8;
-                        c.clan = teams.clone().next().map(|t| t.color);
-                        team_names = 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;
-                        r.teams = info.teams_at_start.iter()
-                            .filter(|(_, t)| !team_names.contains(&t.name))
-                            .cloned().collect();
-                    }
-                } else {
-                    team_names = Vec::new();
-                }
-
-                v.push(SendRoomData{ to: client_id, teams: true, config: true, flags: true});
-
-                if let Some(ref info) = r.game_info {
-                    v.push(RunGame.send_self().action());
-                    v.push(ClientFlags("+g".to_string(), vec![c.nick.clone()])
-                        .send_all().in_room(r.id).action());
-                    v.push(ForwardEngineMessage(
-                        vec![to_engine_msg("e$spectate 1".bytes())])
-                        .send_self().action());
-                    v.push(ForwardEngineMessage(info.msg_log.clone())
-                        .send_self().action());
-
-                    for name in &team_names {
-                        v.push(ForwardEngineMessage(
-                            vec![to_engine_msg(once(b'G').chain(name.bytes()))])
-                            .send_all().in_room(r.id).action());
-                    }
-                    if info.is_paused {
-                        v.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
-                            .send_all().in_room(r.id).action())
-                    }
-                }
-            }
-            server.react(client_id, v);
-        }
-        SendRoomData {to, teams, config, flags} => {
-            let mut actions = Vec::new();
-            let room_id = server.clients[client_id].room_id;
-            if let Some(r) = room_id.and_then(|id| server.rooms.get(id)) {
-                if config {
-                    actions.push(ConfigEntry("FULLMAPCONFIG".to_string(), r.map_config())
-                        .send(to).action());
-                    for cfg in r.game_config() {
-                        actions.push(cfg.to_server_msg().send(to).action());
-                    }
-                }
-                if teams {
-                    let current_teams = match r.game_info {
-                        Some(ref info) => &info.teams_at_start,
-                        None => &r.teams
-                    };
-                    for (owner_id, team) in current_teams.iter() {
-                        actions.push(TeamAdd(HWRoom::team_info(&server.clients[*owner_id], &team))
-                            .send(to).action());
-                        actions.push(TeamColor(team.name.clone(), team.color)
-                            .send(to).action());
-                        actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
-                            .send(to).action());
-                    }
-                }
-                if flags {
-                    if let Some(id) = r.master_id {
-                        actions.push(ClientFlags("+h".to_string(), vec![server.clients[id].nick.clone()])
-                            .send(to).action());
-                    }
-                    let nicks: Vec<_> = server.clients.iter()
-                        .filter(|(_, c)| c.room_id == Some(r.id) && c.is_ready())
-                        .map(|(_, c)| c.nick.clone()).collect();
-                    if !nicks.is_empty() {
-                        actions.push(ClientFlags("+r".to_string(), nicks)
-                            .send(to).action());
-                    }
-                }
-            }
-            server.react(client_id, actions);
-        }
-        AddVote{vote, is_forced} => {
-            let mut actions = Vec::new();
-            if let Some(r) = server.room(client_id) {
-                let mut result = None;
-                if let Some(ref mut voting) = r.voting {
-                    if is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) {
-                        actions.push(server_chat("Your vote has been counted.".to_string())
-                            .send_self().action());
-                        voting.votes.push((client_id, vote));
-                        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 is_forced && vote || pro >= success_quota {
-                            result = Some(true);
-                        } else if is_forced && !vote || contra > voting.voters.len() - success_quota {
-                            result = Some(false);
-                        }
-                    } else {
-                        actions.push(server_chat("You already have voted.".to_string())
-                            .send_self().action());
-                    }
-                } else {
-                    actions.push(server_chat("There's no voting going on.".to_string())
-                        .send_self().action());
-                }
-
-                if let Some(res) = result {
-                    actions.push(server_chat("Voting closed.".to_string())
-                        .send_all().in_room(r.id).action());
-                    let voting = replace(&mut r.voting, None).unwrap();
-                    if res {
-                        actions.push(ApplyVoting(voting.kind, r.id));
-                    }
-                }
-            }
-
-            server.react(client_id, actions);
-        }
-        ApplyVoting(kind, room_id) => {
-            let mut actions = Vec::new();
-            let mut id = client_id;
-            match kind {
-                VoteType::Kick(nick) => {
-                    if let Some(c) = server.find_client(&nick) {
-                        if c.room_id == Some(room_id) {
-                            id = c.id;
-                            actions.push(Kicked.send_self().action());
-                            actions.push(MoveToLobby("kicked".to_string()));
-                        }
-                    }
-                },
-                VoteType::Map(None) => (),
-                VoteType::Map(Some(name)) => {
-                    if let Some(location) = server.rooms[room_id].load_config(&name) {
-                        actions.push(server_chat(location.to_string())
-                            .send_all().in_room(room_id).action());
-                        actions.push(SendRoomUpdate(None));
-                        for (_, c) in server.clients.iter() {
-                            if c.room_id == Some(room_id) {
-                               actions.push(SendRoomData{
-                                   to: c.id, teams: false,
-                                   config: true, flags: false})
-                            }
-                        }
-                    }
-                },
-                VoteType::Pause => {
-                    if let Some(ref mut info) = server.rooms[room_id].game_info {
-                        info.is_paused = !info.is_paused;
-                        actions.push(server_chat("Pause toggled.".to_string())
-                            .send_all().in_room(room_id).action());
-                        actions.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
-                            .send_all().in_room(room_id).action());
-                    }
-                },
-                VoteType::NewSeed => {
-                    let seed = thread_rng().gen_range(0, 1_000_000_000).to_string();
-                    let cfg = GameCfg::Seed(seed);
-                    actions.push(cfg.to_server_msg().send_all().in_room(room_id).action());
-                    server.rooms[room_id].set_config(cfg);
-                },
-                VoteType::HedgehogsPerTeam(number) => {
-                    let r = &mut server.rooms[room_id];
-                    let nicks = r.set_hedgehogs_number(number);
-                    actions.extend(nicks.into_iter().map(|n|
-                        HedgehogsNumber(n, number).send_all().in_room(room_id).action()
-                    ));
-                },
-            }
-            server.react(id, actions);
-        }
-        MoveToLobby(msg) => {
-            let mut actions = Vec::new();
-            let lobby_id = server.lobby_id;
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                r.players_number -= 1;
-                if c.is_ready() && r.ready_players_number > 0 {
-                    r.ready_players_number -= 1;
-                }
-                if c.is_master() && (r.players_number > 0 || r.is_fixed()) {
-                    actions.push(ChangeMaster(r.id, None));
-                }
-                actions.push(ClientFlags("-i".to_string(), vec![c.nick.clone()])
-                    .send_all().action());
-            }
-            server.react(client_id, actions);
-            actions = Vec::new();
-
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                c.room_id = Some(lobby_id);
-                if r.players_number == 0 && !r.is_fixed() {
-                    actions.push(RemoveRoom(r.id));
-                } else {
-                    actions.push(RemoveClientTeams);
-                    actions.push(RoomLeft(c.nick.clone(), msg)
-                        .send_all().in_room(r.id).but_self().action());
-                    actions.push(SendRoomUpdate(Some(r.name.clone())));
-                }
-            }
-            server.react(client_id, actions)
-        }
-        ChangeMaster(room_id, new_id) => {
-            let mut actions = Vec::new();
-            let room_client_ids = server.room_clients(room_id);
-            let new_id = if server.room(client_id).map(|r| r.is_fixed()).unwrap_or(false) {
-                new_id
-            } else {
-                new_id.or_else(||
-                    room_client_ids.iter().find(|id| **id != client_id).cloned())
-            };
-            let new_nick = new_id.map(|id| server.clients[id].nick.clone());
-
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                match r.master_id {
-                    Some(id) if id == c.id => {
-                        c.set_is_master(false);
-                        r.master_id = None;
-                        actions.push(ClientFlags("-h".to_string(), vec![c.nick.clone()])
-                            .send_all().in_room(r.id).action());
-                    }
-                    Some(_) => unreachable!(),
-                    None => {}
-                }
-                r.master_id = new_id;
-                if !r.is_fixed() && c.protocol_number < 42 {
-                    r.name.replace_range(.., new_nick.as_ref().map_or("[]", String::as_str));
-                }
-                r.set_join_restriction(false);
-                r.set_team_add_restriction(false);
-                let is_fixed = r.is_fixed();
-                r.set_unregistered_players_restriction(is_fixed);
-                if let Some(nick) = new_nick {
-                    actions.push(ClientFlags("+h".to_string(), vec![nick])
-                        .send_all().in_room(r.id).action());
-                }
-            }
-            if let Some(id) = new_id {
-                server.clients[id].set_is_master(true)
-            }
-            server.react(client_id, actions);
-        }
-        RemoveTeam(name) => {
-            let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                r.remove_team(&name);
-                if let Some(ref mut info) = r.game_info {
-                    info.left_teams.push(name.clone());
-                }
-                actions.push(TeamRemove(name.clone()).send_all().in_room(r.id).action());
-                actions.push(SendRoomUpdate(None));
-                if r.game_info.is_some() && c.is_in_game() {
-                    actions.push(SendTeamRemovalMessage(name));
-                }
-            }
-            server.react(client_id, actions);
-        },
-        RemoveClientTeams => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                let actions = r.client_teams(c.id).map(|t| RemoveTeam(t.name.clone())).collect();
-                server.react(client_id, actions);
-            }
-        }
-        SendRoomUpdate(old_name) => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                let name = old_name.unwrap_or_else(|| r.name.clone());
-                let actions = vec![RoomUpdated(name, r.info(Some(&c)))
-                    .send_all().with_protocol(r.protocol_number).action()];
-                server.react(client_id, actions);
-            }
-        },
-        StartRoomGame(room_id) => {
-            let actions = {
-                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() {
-                    vec![Warn("The game can't be started with less than two clans!".to_string())]
-                } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number {
-                    vec![Warn("Not all players are ready".to_string())]
-                } else if room.game_info.is_some() {
-                    vec![Warn("The game is already in progress".to_string())]
-                } else {
-                    room.start_round();
-                    for id in room_clients {
-                        let c = &mut server.clients[id];
-                        c.set_is_in_game(false);
-                        c.team_indices = room.client_team_indices(c.id);
-                    }
-                    vec![RunGame.send_all().in_room(room.id).action(),
-                         SendRoomUpdate(None),
-                         ClientFlags("+g".to_string(), room_nicks)
-                             .send_all().in_room(room.id).action()]
-                }
-            };
-            server.react(client_id, actions);
-        }
-        SendTeamRemovalMessage(team_name) => {
-            let mut actions = Vec::new();
-            if let Some(r) = server.room(client_id) {
-                if let Some(ref mut info) = r.game_info {
-                    let msg = once(b'F').chain(team_name.bytes());
-                    actions.push(ForwardEngineMessage(vec![to_engine_msg(msg)]).
-                        send_all().in_room(r.id).but_self().action());
-                    info.teams_in_game -= 1;
-                    if info.teams_in_game == 0 {
-                        actions.push(FinishRoomGame(r.id));
-                    }
-                    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());
-                    actions.push(ForwardEngineMessage(vec![remove_msg])
-                        .send_all().in_room(r.id).but_self().action());
-                }
-            }
-            server.react(client_id, actions);
-        }
-        FinishRoomGame(room_id) => {
-            let mut actions = Vec::new();
-
-            let r = &mut server.rooms[room_id];
-            r.ready_players_number = 1;
-            actions.push(SendRoomUpdate(None));
-            actions.push(RoundFinished.send_all().in_room(r.id).action());
-
-            if let Some(info) = replace(&mut r.game_info, None) {
-                for (_, c) in server.clients.iter() {
-                    if c.room_id == Some(room_id) && c.is_joined_mid_game() {
-                        actions.push(SendRoomData{
-                            to: c.id, teams: false,
-                            config: true, flags: false});
-                        for name in &info.left_teams {
-                            actions.push(TeamRemove(name.clone())
-                                .send(c.id).action());
-                        }
-                    }
-                }
-            }
-
-            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 r.protocol_number < 38 {
-                    LegacyReady(false, nicks)
-                } else {
-                    ClientFlags("-r".to_string(), nicks)
-                };
-                actions.push(msg.send_all().in_room(room_id).action());
-            }
-            server.react(client_id, actions);
-        }
-        Warn(msg) => {
-            run_action(server, client_id, Warning(msg).send_self().action());
-        }
-        ProtocolError(msg) => {
-            run_action(server, client_id, Error(msg).send_self().action())
-        }
-    }
-}
--- a/gameServer2/src/server/client.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-use super::coretypes::ClientId;
-use bitflags::*;
-
-bitflags!{
-    pub struct ClientFlags: u8 {
-        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 NONE = 0b0000_0000;
-        const DEFAULT = Self::NONE.bits;
-    }
-}
-
-pub struct HWClient {
-    pub id: ClientId,
-    pub room_id: Option<usize>,
-    pub nick: String,
-    pub web_password: String,
-    pub server_salt: String,
-    pub protocol_number: u16,
-    pub flags: ClientFlags,
-    pub teams_in_game: u8,
-    pub team_indices: Vec<u8>,
-    pub clan: Option<u8>
-}
-
-impl HWClient {
-    pub fn new(id: ClientId, salt: String) -> HWClient {
-        HWClient {
-            id,
-            room_id: None,
-            nick: String::new(),
-            web_password: String::new(),
-            server_salt: salt,
-            protocol_number: 0,
-            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 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) }
-}
\ No newline at end of file
--- a/gameServer2/src/server/core.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-use slab;
-use crate::utils;
-use super::{
-    io::HWServerIO,
-    client::HWClient, room::HWRoom, actions, handlers,
-    coretypes::{ClientId, RoomId},
-    actions::{Destination, PendingMessage}
-};
-use crate::protocol::messages::*;
-use rand::{RngCore, thread_rng};
-use base64::{encode};
-use log::*;
-
-type Slab<T> = slab::Slab<T>;
-
-pub struct HWServer {
-    pub clients: Slab<HWClient>,
-    pub rooms: Slab<HWRoom>,
-    pub lobby_id: RoomId,
-    pub output: Vec<(Vec<ClientId>, HWServerMessage)>,
-    pub removed_clients: Vec<ClientId>,
-    pub io: Box<dyn HWServerIO>
-}
-
-impl HWServer {
-    pub fn new(clients_limit: usize, rooms_limit: usize, io: Box<dyn HWServerIO>) -> HWServer {
-        let rooms = Slab::with_capacity(rooms_limit);
-        let clients = Slab::with_capacity(clients_limit);
-        let mut server = HWServer {
-            clients, rooms,
-            lobby_id: 0,
-            output: vec![],
-            removed_clients: vec![],
-            io
-        };
-        server.lobby_id = server.add_room();
-        server
-    }
-
-    pub fn add_client(&mut self) -> ClientId {
-        let key: ClientId;
-        {
-            let entry = self.clients.vacant_entry();
-            key = entry.key();
-            let mut salt = [0u8; 18];
-            thread_rng().fill_bytes(&mut salt);
-
-            let client = HWClient::new(entry.key(), encode(&salt));
-            entry.insert(client);
-        }
-        self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
-        key
-    }
-
-    pub fn client_lost(&mut self, client_id: ClientId) {
-        actions::run_action(self, client_id,
-                            actions::Action::ByeClient("Connection reset".to_string()));
-    }
-
-    pub fn add_room(&mut self) -> RoomId {
-        let entry = self.rooms.vacant_entry();
-        let key = entry.key();
-        let room = HWRoom::new(entry.key());
-        entry.insert(room);
-        key
-    }
-
-    pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) {
-        debug!("Handling message {:?} for client {}", msg, client_id);
-        if self.clients.contains(client_id) {
-            handlers::handle(self, client_id, msg);
-        }
-    }
-
-    fn get_recipients(&self, client_id: ClientId, destination: &Destination) -> Vec<ClientId> {
-        let mut ids = match *destination {
-            Destination::ToSelf => vec![client_id],
-            Destination::ToId(id) => vec![id],
-            Destination::ToAll {room_id: Some(id), ..} =>
-                self.room_clients(id),
-            Destination::ToAll {protocol: Some(proto), ..} =>
-                self.protocol_clients(proto),
-            Destination::ToAll {..} =>
-                self.clients.iter().map(|(id, _)| id).collect::<Vec<_>>()
-        };
-        if let Destination::ToAll {skip_self: true, ..} = destination {
-            if let Some(index) = ids.iter().position(|id| *id == client_id) {
-                ids.remove(index);
-            }
-        }
-        ids
-    }
-
-    pub fn send(&mut self, client_id: ClientId, destination: &Destination, message: HWServerMessage) {
-        let ids = self.get_recipients(client_id, &destination);
-        self.output.push((ids, message));
-    }
-
-    pub fn react(&mut self, client_id: ClientId, actions: Vec<actions::Action>) {
-        for action in actions {
-            actions::run_action(self, client_id, action);
-        }
-    }
-
-    pub fn lobby(&self) -> &HWRoom { &self.rooms[self.lobby_id] }
-
-    pub fn has_room(&self, name: &str) -> bool {
-        self.rooms.iter().any(|(_, r)| r.name == name)
-    }
-
-    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 select_clients<F>(&self, f: F) -> Vec<ClientId>
-        where F: Fn(&(usize, &HWClient)) -> bool {
-        self.clients.iter().filter(f)
-            .map(|(_, c)| c.id).collect()
-    }
-
-    pub fn room_clients(&self, room_id: RoomId) -> Vec<ClientId> {
-        self.select_clients(|(_, c)| c.room_id == Some(room_id))
-    }
-
-    pub fn protocol_clients(&self, protocol: u16) -> Vec<ClientId> {
-        self.select_clients(|(_, c)| c.protocol_number == protocol)
-    }
-
-    pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec<ClientId> {
-        let room_id = self.clients[self_id].room_id;
-        self.select_clients(|(id, c)| *id != self_id && c.room_id == room_id )
-    }
-
-    pub fn client_and_room(&mut self, client_id: ClientId) -> (&mut HWClient, Option<&mut HWRoom>) {
-        let c = &mut self.clients[client_id];
-        if let Some(room_id) = c.room_id {
-            (c, Some(&mut self.rooms[room_id]))
-        } else {
-            (c, None)
-        }
-    }
-
-    pub fn room(&mut self, client_id: ClientId) -> Option<&mut HWRoom> {
-        self.client_and_room(client_id).1
-    }
-}
--- a/gameServer2/src/server/coretypes.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-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(u32),
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum GameCfg {
-    FeatureSize(u32),
-    MapType(String),
-    MapGenerator(u32),
-    MazeSize(u32),
-    Seed(String),
-    Template(u32),
-
-    Ammo(String, Option<String>),
-    Scheme(String, Vec<String>),
-    Script(String),
-    Theme(String),
-    DrawnMap(String)
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct TeamInfo {
-    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(PartialEq, Eq, Clone, Debug)]
-pub enum VoteType {
-    Kick(String),
-    Map(Option<String>),
-    Pause,
-    NewSeed,
-    HedgehogsPerTeam(u8)
-}
-
-#[derive(Clone, Debug)]
-pub struct Voting {
-    pub ttl: u32,
-    pub voters: Vec<ClientId>,
-    pub votes: Vec<(ClientId, bool)>,
-    pub kind: VoteType
-}
-
-impl Voting {
-    pub fn new(kind: VoteType, voters: Vec<ClientId>) -> Voting {
-        Voting {
-            kind, voters, ttl: 2,
-            votes: Vec::new()
-        }
-    }
-}
\ No newline at end of file
--- a/gameServer2/src/server/handlers.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-use mio;
-use std::{io, io::Write};
-
-use super::{
-    core::HWServer,
-    actions::{Action, Action::*},
-    coretypes::ClientId
-};
-use crate::{
-    protocol::messages::{
-        HWProtocolMessage,
-        HWServerMessage::*
-    }
-};
-use log::*;
-
-mod loggingin;
-mod lobby;
-mod inroom;
-mod common;
-mod checker;
-
-pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
-    match message {
-        HWProtocolMessage::Ping =>
-            server.react(client_id, vec![Pong.send_self().action()]),
-        HWProtocolMessage::Quit(Some(msg)) =>
-            server.react(client_id, vec![ByeClient("User quit: ".to_string() + &msg)]),
-        HWProtocolMessage::Quit(None) =>
-            server.react(client_id, vec![ByeClient("User quit".to_string())]),
-        HWProtocolMessage::Malformed => warn!("Malformed/unknown message"),
-        HWProtocolMessage::Empty => warn!("Empty message"),
-        _ => {
-            match server.clients[client_id].room_id {
-                None =>
-                    loggingin::handle(server, client_id, message),
-                Some(id) if id == server.lobby_id =>
-                    lobby::handle(server, client_id, message),
-                Some(id) =>
-                    inroom::handle(server, client_id, id, message)
-            }
-        },
-    }
-}
--- a/gameServer2/src/server/handlers/checker.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-use mio;
-use log::*;
-
-use crate::{
-    server::{
-        core::HWServer,
-        coretypes::ClientId,
-    },
-    protocol::messages::{
-        HWProtocolMessage
-    },
-};
-
-pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
-    match message {
-        _ => warn!("Unknown command"),
-    }
-}
--- a/gameServer2/src/server/handlers/common.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-use crate::{
-    server::{actions::Action, core::HWServer},
-    protocol::messages::{
-        HWProtocolMessage::{self, Rnd}, HWServerMessage::{self, ChatMsg},
-    }
-};
-use rand::{self, Rng, thread_rng};
-
-pub fn rnd_reply(options: &[String]) -> HWServerMessage {
-    let mut rng = thread_rng();
-    let reply = if options.is_empty() {
-        (*rng.choose(&["heads", "tails"]).unwrap()).to_owned()
-    } else {
-        rng.choose(&options).unwrap().clone()
-    };
-
-    ChatMsg {
-        nick: "[random]".to_owned(),
-        msg: reply.clone(),
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::protocol::messages::HWServerMessage::ChatMsg;
-    use crate::server::actions::{
-        Action::{self, Send}, PendingMessage,
-    };
-
-    fn reply2string(r: HWServerMessage) -> String {
-        match r {
-            ChatMsg { msg: p, .. } => String::from(p),
-            _ => panic!("expected a ChatMsg"),
-        }
-    }
-
-    fn run_handle_test(opts: Vec<String>) {
-        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;
-            }
-        }
-    }
-}
--- a/gameServer2/src/server/handlers/inroom.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,486 +0,0 @@
-use mio;
-
-use crate::{
-    server::{
-        coretypes::{
-            ClientId, RoomId, Voting, VoteType, GameCfg,
-            MAX_HEDGEHOGS_PER_TEAM
-        },
-        core::HWServer,
-        room::{HWRoom, RoomFlags},
-        actions::{Action, Action::*}
-    },
-    protocol::messages::{
-        HWProtocolMessage,
-        HWServerMessage::*,
-        server_chat
-    },
-    utils::is_name_illegal
-};
-use std::{
-    mem::swap
-};
-use base64::{encode, decode};
-use super::common::rnd_reply;
-use log::*;
-
-#[derive(Clone)]
-struct ByMsg<'a> {
-    messages: &'a[u8]
-}
-
-impl <'a> Iterator for ByMsg<'a> {
-    type Item = &'a[u8];
-
-    fn next(&mut self) -> Option<<Self as Iterator>::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, room_id: RoomId, message: HWProtocolMessage) {
-    use crate::protocol::messages::HWProtocolMessage::*;
-    match message {
-        Part(None) => server.react(client_id, vec![
-            MoveToLobby("part".to_string())]),
-        Part(Some(msg)) => server.react(client_id, vec![
-            MoveToLobby(format!("part: {}", msg))]),
-        Chat(msg) => {
-            let actions = {
-                let c = &mut server.clients[client_id];
-                let chat_msg = ChatMsg {nick: c.nick.clone(), msg};
-                vec![chat_msg.send_all().in_room(room_id).but_self().action()]
-            };
-            server.react(client_id, actions);
-        },
-        Fix => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                if c.is_admin() { r.set_is_fixed(true) }
-            }
-        }
-        Unfix => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                if c.is_admin() { r.set_is_fixed(false) }
-            }
-        }
-        Greeting(text) => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                if c.is_admin() || c.is_master() && !r.is_fixed() {
-                    r.greeting = text
-                }
-            }
-        }
-        RoomName(new_name) => {
-            let actions =
-                if is_name_illegal(&new_name) {
-                    vec![Warn("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())]
-                } else if server.rooms[room_id].is_fixed() {
-                    vec![Warn("Access denied.".to_string())]
-                } else if server.has_room(&new_name) {
-                    vec![Warn("A room with the same name already exists.".to_string())]
-                } else {
-                    let mut old_name = new_name.clone();
-                    swap(&mut server.rooms[room_id].name, &mut old_name);
-                    vec![SendRoomUpdate(Some(old_name))]
-                };
-            server.react(client_id, actions);
-        },
-        ToggleReady => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                let flags = if c.is_ready() {
-                    r.ready_players_number -= 1;
-                    "-r"
-                } else {
-                    r.ready_players_number += 1;
-                    "+r"
-                };
-
-                let msg = if c.protocol_number < 38 {
-                    LegacyReady(c.is_ready(), vec![c.nick.clone()])
-                } else {
-                    ClientFlags(flags.to_string(), vec![c.nick.clone()])
-                };
-
-                let mut v = vec![msg.send_all().in_room(r.id).action()];
-
-                if r.is_fixed() && r.ready_players_number == r.players_number {
-                    v.push(StartRoomGame(r.id))
-                }
-
-                c.set_is_ready(!c.is_ready());
-                server.react(client_id, v);
-            }
-        }
-        AddTeam(info) => {
-            let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                if r.teams.len() >= r.team_limit as usize {
-                    actions.push(Warn("Too many teams!".to_string()))
-                } else if r.addable_hedgehogs() == 0 {
-                    actions.push(Warn("Too many hedgehogs!".to_string()))
-                } else if r.find_team(|t| t.name == info.name) != None {
-                    actions.push(Warn("There's already a team with same name in the list.".to_string()))
-                } else if r.game_info.is_some() {
-                    actions.push(Warn("Joining not possible: Round is in progress.".to_string()))
-                } else if r.is_team_add_restricted() {
-                    actions.push(Warn("This room currently does not allow adding new teams.".to_string()));
-                } else {
-                    let team = r.add_team(c.id, *info, c.protocol_number < 42);
-                    c.teams_in_game += 1;
-                    c.clan = Some(team.color);
-                    actions.push(TeamAccepted(team.name.clone())
-                        .send_self().action());
-                    actions.push(TeamAdd(HWRoom::team_info(&c, team))
-                        .send_all().in_room(room_id).but_self().action());
-                    actions.push(TeamColor(team.name.clone(), team.color)
-                        .send_all().in_room(room_id).action());
-                    actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
-                        .send_all().in_room(room_id).action());
-                    actions.push(SendRoomUpdate(None));
-                }
-            }
-            server.react(client_id, actions);
-        },
-        RemoveTeam(name) => {
-            let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                match r.find_team_owner(&name) {
-                    None =>
-                        actions.push(Warn("Error: The team you tried to remove does not exist.".to_string())),
-                    Some((id, _)) if id != client_id =>
-                        actions.push(Warn("You can't remove a team you don't own.".to_string())),
-                    Some((_, name)) => {
-                        c.teams_in_game -= 1;
-                        c.clan = r.find_team_color(c.id);
-                        actions.push(Action::RemoveTeam(name.to_string()));
-                    }
-                }
-            };
-            server.react(client_id, actions);
-        },
-        SetHedgehogsNumber(team_name, number) => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                let addable_hedgehogs = r.addable_hedgehogs();
-                let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
-                    if !c.is_master() {
-                        vec![ProtocolError("You're not the room master!".to_string())]
-                    } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM
-                           || number > addable_hedgehogs + team.hedgehogs_number {
-                        vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
-                            .send_self().action()]
-                    } else {
-                        team.hedgehogs_number = number;
-                        vec![HedgehogsNumber(team.name.clone(), number)
-                            .send_all().in_room(room_id).but_self().action()]
-                    }
-                } else {
-                    vec![(Warn("No such team.".to_string()))]
-                };
-                server.react(client_id, actions);
-            }
-        },
-        SetTeamColor(team_name, color) => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                let mut owner_id = None;
-                let actions = if let Some((owner, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
-                    if !c.is_master() {
-                        vec![ProtocolError("You're not the room master!".to_string())]
-                    } else if false  {
-                        Vec::new()
-                    } else {
-                        owner_id = Some(owner);
-                        team.color = color;
-                        vec![TeamColor(team.name.clone(), color)
-                            .send_all().in_room(room_id).but_self().action()]
-                    }
-                } else {
-                    vec![(Warn("No such team.".to_string()))]
-                };
-
-                if let Some(id) = owner_id {
-                    server.clients[id].clan = Some(color);
-                }
-
-                server.react(client_id, actions);
-            };
-        },
-        Cfg(cfg) => {
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                let actions = if r.is_fixed() {
-                    vec![Warn("Access denied.".to_string())]
-                } else if !c.is_master() {
-                    vec![ProtocolError("You're not the room master!".to_string())]
-                } else {
-                    let cfg = match cfg {
-                        GameCfg::Scheme(name, mut values) => {
-                            if c.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
-                    };
-
-                    let v = vec![cfg.to_server_msg()
-                        .send_all().in_room(r.id).but_self().action()];
-                    r.set_config(cfg);
-                    v
-                };
-                server.react(client_id, actions);
-            }
-        }
-        Save(name, location) => {
-            let actions = vec![server_chat(format!("Room config saved as {}", name))
-                .send_all().in_room(room_id).action()];
-            server.rooms[room_id].save_config(name, location);
-            server.react(client_id, actions);
-        }
-        SaveRoom(filename) => {
-            if server.clients[client_id].is_admin() {
-                let actions = match server.rooms[room_id].get_saves() {
-                    Ok(text) => match server.io.write_file(&filename, &text) {
-                        Ok(_) => vec![server_chat("Room configs saved successfully.".to_string())
-                            .send_self().action()],
-                        Err(e) => {
-                            warn!("Error while writing the config file \"{}\": {}", filename, e);
-                            vec![Warn("Unable to save the room configs.".to_string())]
-                        }
-                    }
-                    Err(e) => {
-                        warn!("Error while serializing the room configs: {}", e);
-                        vec![Warn("Unable to serialize the room configs.".to_string())]
-                    }
-                };
-                server.react(client_id, actions);
-            }
-        }
-        LoadRoom(filename) => {
-            if server.clients[client_id].is_admin() {
-                let actions = match server.io.read_file(&filename) {
-                    Ok(text) => match server.rooms[room_id].set_saves(&text) {
-                        Ok(_) => vec![server_chat("Room configs loaded successfully.".to_string())
-                            .send_self().action()],
-                        Err(e) => {
-                            warn!("Error while deserializing the room configs: {}", e);
-                            vec![Warn("Unable to deserialize the room configs.".to_string())]
-                        }
-                    }
-                    Err(e) => {
-                        warn!("Error while reading the config file \"{}\": {}", filename, e);
-                        vec![Warn("Unable to load the room configs.".to_string())]
-                    }
-                };
-                server.react(client_id, actions);
-            }
-        }
-        Delete(name) => {
-            let actions = if !server.rooms[room_id].delete_config(&name) {
-                vec![Warn(format!("Save doesn't exist: {}", name))]
-            } else {
-                vec![server_chat(format!("Room config {} has been deleted", name))
-                    .send_all().in_room(room_id).action()]
-            };
-            server.react(client_id, actions);
-        }
-        CallVote(None) => {
-            server.react(client_id, vec![
-                server_chat("Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs <number>".to_string())
-                    .send_self().action()])
-        }
-        CallVote(Some(kind)) => {
-            let is_in_game = server.rooms[room_id].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 server.rooms[room_id].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));
-                    server.rooms[room_id].voting = Some(voting);
-                    server.react(client_id, vec![
-                        server_chat(msg).send_all().in_room(room_id).action(),
-                        AddVote{ vote: true, is_forced: false}]);
-                }
-                Some(msg) => {
-                    server.react(client_id, vec![
-                        server_chat(msg).send_self().action()])
-                }
-            }
-        }
-        Vote(vote) => {
-            server.react(client_id, vec![AddVote{ vote, is_forced: false }]);
-        }
-        ForceVote(vote) => {
-            let is_forced = server.clients[client_id].is_admin();
-            server.react(client_id, vec![AddVote{ vote, is_forced }]);
-        }
-        ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly  => {
-            if server.clients[client_id].is_master() {
-                server.rooms[room_id].flags.toggle(room_message_flag(&message));
-            }
-            server.react(client_id, vec![SendRoomUpdate(None)]);
-        }
-        StartGame => {
-            server.react(client_id, vec![StartRoomGame(room_id)]);
-        }
-        EngineMessage(em) => {
-            let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                if c.teams_in_game > 0 {
-                    let decoding = decode(&em[..]).unwrap();
-                    let messages = by_msg(&decoding);
-                    let valid = messages.filter(|m| is_msg_valid(m, &c.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::<Vec<_>>());
-                    if !em_response.is_empty() {
-                        actions.push(ForwardEngineMessage(vec![em_response])
-                            .send_all().in_room(r.id).but_self().action());
-                    }
-                    let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::<Vec<_>>());
-                    if let Some(ref mut info) = r.game_info {
-                        if !em_log.is_empty() {
-                            info.msg_log.push(em_log);
-                        }
-                        if let Some(msg) = sync_msg {
-                            info.sync_msg = msg;
-                        }
-                    }
-                }
-            }
-            server.react(client_id, actions)
-        }
-        RoundFinished => {
-            let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
-                if c.is_in_game() {
-                    c.set_is_in_game(false);
-                    actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]).
-                        send_all().in_room(r.id).action());
-                    if r.game_info.is_some() {
-                        for team in r.client_teams(c.id) {
-                            actions.push(SendTeamRemovalMessage(team.name.clone()));
-                        }
-                    }
-                }
-            }
-            server.react(client_id, actions)
-        },
-        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(" ")
-            };
-            server.react(client_id, vec![
-                chat_msg.send_all().in_room(room_id).action(),
-                result.send_all().in_room(room_id).action()])
-        },
-        _ => warn!("Unimplemented!")
-    }
-}
--- a/gameServer2/src/server/handlers/lobby.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-use mio;
-
-use crate::{
-    server::{
-        core::HWServer,
-        coretypes::ClientId,
-        actions::{Action, Action::*}
-    },
-    protocol::messages::{
-        HWProtocolMessage,
-        HWServerMessage::*
-    },
-    utils::is_name_illegal
-};
-use super::common::rnd_reply;
-use log::*;
-
-pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
-    use crate::protocol::messages::HWProtocolMessage::*;
-    match message {
-        CreateRoom(name, password) => {
-            let actions =
-                if is_name_illegal(&name) {
-                    vec![Warn("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())]
-                } else if server.has_room(&name) {
-                    vec![Warn("A room with the same name already exists.".to_string())]
-                } else {
-                    let flags_msg = ClientFlags(
-                        "+hr".to_string(),
-                        vec![server.clients[client_id].nick.clone()]);
-                    vec![AddRoom(name, password),
-                         flags_msg.send_self().action()]
-                };
-            server.react(client_id, actions);
-        },
-        Chat(msg) => {
-            let actions = vec![ChatMsg {nick: server.clients[client_id].nick.clone(), msg}
-                .send_all().in_room(server.lobby_id).but_self().action()];
-            server.react(client_id, actions);
-        },
-        JoinRoom(name, _password) => {
-            let room = server.rooms.iter().find(|(_, r)| r.name == name);
-            let room_id = room.map(|(_, r)| r.id);
-            let nicks = server.clients.iter()
-                .filter(|(_, c)| c.room_id == room_id)
-                .map(|(_, c)| c.nick.clone())
-                .collect();
-            let c = &mut server.clients[client_id];
-
-            let actions = if let Some((_, r)) = room {
-                if c.protocol_number != r.protocol_number {
-                    vec![Warn("Room version incompatible to your Hedgewars version!".to_string())]
-                } else if r.is_join_restricted() {
-                    vec![Warn("Access denied. This room currently doesn't allow joining.".to_string())]
-                } else if r.players_number == u8::max_value() {
-                    vec![Warn("This room is already full".to_string())]
-                } else {
-                    vec![MoveToRoom(r.id),
-                         RoomJoined(nicks).send_self().action()]
-                }
-            } else {
-                vec![Warn("No such room.".to_string())]
-            };
-            server.react(client_id, actions);
-        },
-        Rnd(v) => {
-            server.react(client_id, vec![rnd_reply(&v).send_self().action()]);
-        },
-        List => warn!("Deprecated LIST message received"),
-        _ => warn!("Incorrect command in lobby state"),
-    }
-}
--- a/gameServer2/src/server/handlers/loggingin.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-use mio;
-
-use crate::{
-    server::{
-        client::HWClient,
-        core::HWServer,
-        coretypes::ClientId,
-        actions::{Action, Action::*}
-    },
-    protocol::messages::{
-        HWProtocolMessage, HWServerMessage::*
-    },
-    utils::is_name_illegal
-};
-#[cfg(feature = "official-server")]
-use openssl::sha::sha1;
-use std::fmt::{Formatter, LowerHex};
-use log::*;
-
-#[derive(PartialEq)]
-struct Sha1Digest([u8; 20]);
-
-impl LowerHex for Sha1Digest {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
-        for byte in &self.0 {
-            write!(f, "{:02x}", byte)?;
-        }
-        Ok(())
-    }
-}
-
-#[cfg(feature = "official-server")]
-fn get_hash(client: &HWClient, salt1: &str, salt2: &str) -> Sha1Digest {
-    let s = format!("{}{}{}{}{}", salt1, salt2,
-                    client.web_password, client.protocol_number, "!hedgewars");
-    Sha1Digest(sha1(s.as_bytes()))
-}
-
-pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
-    match message {
-        HWProtocolMessage::Nick(nick) => {
-            let client = &mut server.clients[client_id];
-            debug!("{} {}", nick, is_name_illegal(&nick));
-            let actions = if client.room_id != None {
-                unreachable!()
-            }
-            else if !client.nick.is_empty() {
-                vec![ProtocolError("Nickname already provided.".to_string())]
-            }
-            else if is_name_illegal(&nick) {
-                vec![ByeClient("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())]
-            }
-            else {
-                client.nick = nick.clone();
-                vec![Nick(nick).send_self().action(),
-                     CheckRegistered]
-            };
-
-            server.react(client_id, actions);
-        }
-        HWProtocolMessage::Proto(proto) => {
-            let client = &mut server.clients[client_id];
-            let actions = if client.protocol_number != 0 {
-                vec![ProtocolError("Protocol already known.".to_string())]
-            }
-            else if proto == 0 {
-                vec![ProtocolError("Bad number.".to_string())]
-            }
-            else {
-                client.protocol_number = proto;
-                vec![Proto(proto).send_self().action(),
-                     CheckRegistered]
-            };
-            server.react(client_id, actions);
-        }
-        #[cfg(feature = "official-server")]
-        HWProtocolMessage::Password(hash, salt) => {
-            let c = &server.clients[client_id];
-
-            let client_hash = get_hash(c, &salt, &c.server_salt);
-            let server_hash = get_hash(c, &c.server_salt, &salt);
-            let actions = if client_hash == server_hash {
-                vec![ServerAuth(format!("{:x}", server_hash)).send_self().action(),
-                     JoinLobby]
-            } else {
-                vec![ByeClient("Authentication failed".to_string())]
-            };
-            server.react(client_id, actions);
-        }
-        #[cfg(feature = "official-server")]
-        HWProtocolMessage::Checker(protocol, nick, password) => {
-            let c = &mut server.clients[client_id];
-            c.nick = nick;
-            c.web_password = password;
-            c.set_is_checker(true);
-        }
-        _ => warn!("Incorrect command in logging-in state"),
-    }
-}
--- a/gameServer2/src/server/io.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-use std::{
-    fs::{File, OpenOptions},
-    io::{Read, Write, Result, Error, ErrorKind}
-};
-
-pub trait HWServerIO {
-    fn write_file(&mut self, name: &str, content: &str) -> Result<()>;
-    fn read_file(&mut self, name: &str) -> Result<String>;
-}
-
-pub struct EmptyServerIO {}
-
-impl EmptyServerIO {
-    pub fn new() -> Self {
-        Self {}
-    }
-}
-
-impl HWServerIO for EmptyServerIO {
-    fn write_file(&mut self, _name: &str, _content: &str) -> Result<()> {
-        Ok(())
-    }
-
-    fn read_file(&mut self, _name: &str) -> Result<String> {
-        Ok("".to_string())
-    }
-}
-
-pub struct FileServerIO {}
-
-impl FileServerIO {
-    pub fn new() -> Self {
-        Self {}
-    }
-}
-
-impl HWServerIO for FileServerIO {
-    fn write_file(&mut self, name: &str, content: &str) -> Result<()> {
-        let mut writer = OpenOptions::new().create(true).write(true).open(name)?;
-        writer.write_all(content.as_bytes())
-    }
-
-    fn read_file(&mut self, name: &str) -> Result<String> {
-        let mut reader = File::open(name)?;
-        let mut result = String::new();
-        reader.read_to_string(&mut result)?;
-        Ok(result)
-    }
-}
\ No newline at end of file
--- a/gameServer2/src/server/network.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,444 +0,0 @@
-extern crate slab;
-
-use std::{
-    io, io::{Error, ErrorKind, Read, Write},
-    net::{SocketAddr, IpAddr, Ipv4Addr},
-    collections::HashSet,
-    mem::{swap, replace}
-};
-
-use mio::{
-    net::{TcpStream, TcpListener},
-    Poll, PollOpt, Ready, Token
-};
-use netbuf;
-use slab::Slab;
-use log::*;
-
-use crate::{
-    utils,
-    protocol::{ProtocolDecoder, messages::*}
-};
-use super::{
-    io::FileServerIO,
-    core::{HWServer},
-    coretypes::ClientId
-};
-#[cfg(feature = "tls-connections")]
-use openssl::{
-    ssl::{
-        SslMethod, SslContext, Ssl, SslContextBuilder,
-        SslVerifyMode, SslFiletype, SslOptions,
-        SslStreamBuilder, HandshakeError, MidHandshakeSslStream, SslStream
-    },
-    error::ErrorStack
-};
-
-const MAX_BYTES_PER_READ: usize = 2048;
-
-#[derive(Hash, Eq, PartialEq, Copy, Clone)]
-pub enum NetworkClientState {
-    Idle,
-    NeedsWrite,
-    NeedsRead,
-    Closed,
-}
-
-type NetworkResult<T> = io::Result<(T, NetworkClientState)>;
-
-#[cfg(not(feature = "tls-connections"))]
-pub enum ClientSocket {
-    Plain(TcpStream)
-}
-
-#[cfg(feature = "tls-connections")]
-pub enum ClientSocket {
-    SslHandshake(Option<MidHandshakeSslStream<TcpStream>>),
-    SslStream(SslStream<TcpStream>)
-}
-
-impl ClientSocket {
-    fn inner(&self) -> &TcpStream {
-        #[cfg(not(feature = "tls-connections"))]
-        match self {
-            ClientSocket::Plain(stream) => stream,
-        }
-
-        #[cfg(feature = "tls-connections")]
-        match self {
-            ClientSocket::SslHandshake(Some(builder)) => builder.get_ref(),
-            ClientSocket::SslHandshake(None) => unreachable!(),
-            ClientSocket::SslStream(ssl_stream) => ssl_stream.get_ref()
-        }
-    }
-}
-
-pub struct NetworkClient {
-    id: ClientId,
-    socket: ClientSocket,
-    peer_addr: SocketAddr,
-    decoder: ProtocolDecoder,
-    buf_out: netbuf::Buf
-}
-
-impl NetworkClient {
-    pub fn new(id: ClientId, socket: ClientSocket, peer_addr: SocketAddr) -> NetworkClient {
-        NetworkClient {
-            id, socket, peer_addr,
-            decoder: ProtocolDecoder::new(),
-            buf_out: netbuf::Buf::new()
-        }
-    }
-
-    #[cfg(feature = "tls-connections")]
-    fn handshake_impl(&mut self, handshake: MidHandshakeSslStream<TcpStream>) -> io::Result<NetworkClientState> {
-        match handshake.handshake() {
-            Ok(stream) => {
-                self.socket = ClientSocket::SslStream(stream);
-                debug!("TLS handshake with {} ({}) completed", self.id, self.peer_addr);
-                Ok(NetworkClientState::Idle)
-            }
-            Err(HandshakeError::WouldBlock(new_handshake)) => {
-                self.socket = ClientSocket::SslHandshake(Some(new_handshake));
-                Ok(NetworkClientState::Idle)
-            }
-            Err(HandshakeError::Failure(new_handshake)) => {
-                self.socket = ClientSocket::SslHandshake(Some(new_handshake));
-                debug!("TLS handshake with {} ({}) failed", self.id, self.peer_addr);
-                Err(Error::new(ErrorKind::Other, "Connection failure"))
-            }
-            Err(HandshakeError::SetupFailure(_)) => unreachable!()
-        }
-    }
-
-    fn read_impl<R: Read>(decoder: &mut ProtocolDecoder, source: &mut R,
-                          id: ClientId, addr: &SocketAddr) -> NetworkResult<Vec<HWProtocolMessage>> {
-        let mut bytes_read = 0;
-        let result = loop {
-            match decoder.read_from(source) {
-                Ok(bytes) => {
-                    debug!("Client {}: read {} bytes", id, bytes);
-                    bytes_read += bytes;
-                    if bytes == 0 {
-                        let result = if bytes_read == 0 {
-                            info!("EOF for client {} ({})", id, addr);
-                            (Vec::new(), NetworkClientState::Closed)
-                        } else {
-                            (decoder.extract_messages(), NetworkClientState::NeedsRead)
-                        };
-                        break Ok(result);
-                    }
-                    else if bytes_read >= MAX_BYTES_PER_READ {
-                        break Ok((decoder.extract_messages(), NetworkClientState::NeedsRead))
-                    }
-                }
-                Err(ref error) if error.kind() == ErrorKind::WouldBlock => {
-                    let messages =  if bytes_read == 0 {
-                        Vec::new()
-                    } else {
-                        decoder.extract_messages()
-                    };
-                    break Ok((messages, NetworkClientState::Idle));
-                }
-                Err(error) =>
-                    break Err(error)
-            }
-        };
-        decoder.sweep();
-        result
-    }
-
-    pub fn read(&mut self) -> NetworkResult<Vec<HWProtocolMessage>> {
-        #[cfg(not(feature = "tls-connections"))]
-        match self.socket {
-            ClientSocket::Plain(ref mut stream) =>
-                NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr),
-        }
-
-        #[cfg(feature = "tls-connections")]
-        match self.socket {
-            ClientSocket::SslHandshake(ref mut handshake_opt) => {
-                let handshake = std::mem::replace(handshake_opt, None).unwrap();
-                Ok((Vec::new(), self.handshake_impl(handshake)?))
-            },
-            ClientSocket::SslStream(ref mut stream) =>
-                NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr)
-        }
-    }
-
-    fn write_impl<W: Write>(buf_out: &mut netbuf::Buf, destination: &mut W) -> NetworkResult<()> {
-        let result = loop {
-            match buf_out.write_to(destination) {
-                Ok(bytes) if buf_out.is_empty() || bytes == 0 =>
-                    break Ok(((), NetworkClientState::Idle)),
-                Ok(_) => (),
-                Err(ref error) if error.kind() == ErrorKind::Interrupted
-                    || error.kind() == ErrorKind::WouldBlock => {
-                    break Ok(((), NetworkClientState::NeedsWrite));
-                },
-                Err(error) =>
-                    break Err(error)
-            }
-        };
-        result
-    }
-
-    pub fn write(&mut self) -> NetworkResult<()> {
-        let result = {
-            #[cfg(not(feature = "tls-connections"))]
-            match self.socket {
-                ClientSocket::Plain(ref mut stream) =>
-                    NetworkClient::write_impl(&mut self.buf_out, stream)
-            }
-
-            #[cfg(feature = "tls-connections")] {
-                match self.socket {
-                    ClientSocket::SslHandshake(ref mut handshake_opt) => {
-                        let handshake = std::mem::replace(handshake_opt, None).unwrap();
-                        Ok(((), self.handshake_impl(handshake)?))
-                    }
-                    ClientSocket::SslStream(ref mut stream) =>
-                        NetworkClient::write_impl(&mut self.buf_out, stream)
-                }
-            }
-        };
-
-        self.socket.inner().flush()?;
-        result
-    }
-
-    pub fn send_raw_msg(&mut self, msg: &[u8]) {
-        self.buf_out.write_all(msg).unwrap();
-    }
-
-    pub fn send_string(&mut self, msg: &str) {
-        self.send_raw_msg(&msg.as_bytes());
-    }
-
-    pub fn send_msg(&mut self, msg: &HWServerMessage) {
-        self.send_string(&msg.to_raw_protocol());
-    }
-}
-
-#[cfg(feature = "tls-connections")]
-struct ServerSsl {
-    context: SslContext
-}
-
-pub struct NetworkLayer {
-    listener: TcpListener,
-    server: HWServer,
-    clients: Slab<NetworkClient>,
-    pending: HashSet<(ClientId, NetworkClientState)>,
-    pending_cache: Vec<(ClientId, NetworkClientState)>,
-    #[cfg(feature = "tls-connections")]
-    ssl: ServerSsl
-}
-
-impl NetworkLayer {
-    pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer {
-        let server = HWServer::new(clients_limit, rooms_limit, Box::new(FileServerIO::new()));
-        let clients = Slab::with_capacity(clients_limit);
-        let pending = HashSet::with_capacity(2 * clients_limit);
-        let pending_cache = Vec::with_capacity(2 * clients_limit);
-
-        NetworkLayer {
-            listener, server, clients, pending, pending_cache,
-            #[cfg(feature = "tls-connections")]
-            ssl: NetworkLayer::create_ssl_context()
-        }
-    }
-
-    #[cfg(feature = "tls-connections")]
-    fn create_ssl_context() -> ServerSsl {
-        let mut builder = SslContextBuilder::new(SslMethod::tls()).unwrap();
-        builder.set_verify(SslVerifyMode::NONE);
-        builder.set_read_ahead(true);
-        builder.set_certificate_file("ssl/cert.pem", SslFiletype::PEM).unwrap();
-        builder.set_private_key_file("ssl/key.pem", SslFiletype::PEM).unwrap();
-        builder.set_options(SslOptions::NO_COMPRESSION);
-        builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap();
-        ServerSsl { context: builder.build() }
-    }
-
-    pub fn register_server(&self, poll: &Poll) -> io::Result<()> {
-        poll.register(&self.listener, utils::SERVER, Ready::readable(),
-                      PollOpt::edge())
-    }
-
-    fn deregister_client(&mut self, poll: &Poll, id: ClientId) {
-        let mut client_exists = false;
-        if let Some(ref client) = self.clients.get(id) {
-            poll.deregister(client.socket.inner())
-                .expect("could not deregister socket");
-            info!("client {} ({}) removed", client.id, client.peer_addr);
-            client_exists = true;
-        }
-        if client_exists {
-            self.clients.remove(id);
-        }
-    }
-
-    fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: ClientSocket, addr: SocketAddr) {
-        poll.register(client_socket.inner(), Token(id),
-                      Ready::readable() | Ready::writable(),
-                      PollOpt::edge())
-            .expect("could not register socket with event loop");
-
-        let entry = self.clients.vacant_entry();
-        let client = NetworkClient::new(id, client_socket, addr);
-        info!("client {} ({}) added", client.id, client.peer_addr);
-        entry.insert(client);
-    }
-
-    fn flush_server_messages(&mut self) {
-        debug!("{} pending server messages", self.server.output.len());
-        for (clients, message) in self.server.output.drain(..) {
-            debug!("Message {:?} to {:?}", message, clients);
-            let msg_string = message.to_raw_protocol();
-            for client_id in clients {
-                if let Some(client) = self.clients.get_mut(client_id) {
-                    client.send_string(&msg_string);
-                    self.pending.insert((client_id, NetworkClientState::NeedsWrite));
-                }
-            }
-        }
-    }
-
-    fn create_client_socket(&self, socket: TcpStream) -> io::Result<ClientSocket> {
-        #[cfg(not(feature = "tls-connections"))] {
-            Ok(ClientSocket::Plain(socket))
-        }
-
-        #[cfg(feature = "tls-connections")] {
-            let ssl = Ssl::new(&self.ssl.context).unwrap();
-            let mut builder = SslStreamBuilder::new(ssl, socket);
-            builder.set_accept_state();
-            match builder.handshake() {
-                Ok(stream) =>
-                    Ok(ClientSocket::SslStream(stream)),
-                Err(HandshakeError::WouldBlock(stream)) =>
-                    Ok(ClientSocket::SslHandshake(Some(stream))),
-                Err(e) => {
-                    debug!("OpenSSL handshake failed: {}", e);
-                    Err(Error::new(ErrorKind::Other, "Connection failure"))
-                }
-            }
-        }
-    }
-
-    pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> {
-        let (client_socket, addr) = self.listener.accept()?;
-        info!("Connected: {}", addr);
-
-        let client_id = self.server.add_client();
-        self.register_client(poll, client_id, self.create_client_socket(client_socket)?, addr);
-        self.flush_server_messages();
-
-        Ok(())
-    }
-
-    fn operation_failed(&mut self, poll: &Poll, client_id: ClientId, error: &Error, msg: &str) -> io::Result<()> {
-        let addr = if let Some(ref mut client) = self.clients.get_mut(client_id) {
-            client.peer_addr
-        } else {
-            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
-        };
-        debug!("{}({}): {}", msg, addr, error);
-        self.client_error(poll, client_id)
-    }
-
-    pub fn client_readable(&mut self, poll: &Poll,
-                           client_id: ClientId) -> io::Result<()> {
-        let messages =
-            if let Some(ref mut client) = self.clients.get_mut(client_id) {
-                client.read()
-            } else {
-                warn!("invalid readable client: {}", client_id);
-                Ok((Vec::new(), NetworkClientState::Idle))
-            };
-
-        match messages {
-            Ok((messages, state)) => {
-                for message in messages {
-                    self.server.handle_msg(client_id, message);
-                }
-                match state {
-                    NetworkClientState::NeedsRead => {
-                        self.pending.insert((client_id, state));
-                    },
-                    NetworkClientState::Closed =>
-                        self.client_error(&poll, client_id)?,
-                    _ => {}
-                };
-            }
-            Err(e) => self.operation_failed(
-                poll, client_id, &e,
-                "Error while reading from client socket")?
-        }
-
-        self.flush_server_messages();
-
-        if !self.server.removed_clients.is_empty() {
-            let ids: Vec<_> = self.server.removed_clients.drain(..).collect();
-            for client_id in ids {
-                self.deregister_client(poll, client_id);
-            }
-        }
-
-        Ok(())
-    }
-
-    pub fn client_writable(&mut self, poll: &Poll,
-                           client_id: ClientId) -> io::Result<()> {
-        let result =
-            if let Some(ref mut client) = self.clients.get_mut(client_id) {
-                client.write()
-            } else {
-                warn!("invalid writable client: {}", client_id);
-                Ok(((), NetworkClientState::Idle))
-            };
-
-        match result {
-            Ok(((), state)) if state == NetworkClientState::NeedsWrite => {
-                self.pending.insert((client_id, state));
-            },
-            Ok(_) => {}
-            Err(e) => self.operation_failed(
-                poll, client_id, &e,
-                "Error while writing to client socket")?
-        }
-
-        Ok(())
-    }
-
-    pub fn client_error(&mut self, poll: &Poll,
-                        client_id: ClientId) -> io::Result<()> {
-        self.deregister_client(poll, client_id);
-        self.server.client_lost(client_id);
-
-        Ok(())
-    }
-
-    pub fn has_pending_operations(&self) -> bool {
-        !self.pending.is_empty()
-    }
-
-    pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> {
-        if self.has_pending_operations() {
-            let mut cache = replace(&mut self.pending_cache, Vec::new());
-            cache.extend(self.pending.drain());
-            for (id, state) in cache.drain(..) {
-                match state {
-                    NetworkClientState::NeedsRead =>
-                        self.client_readable(poll, id)?,
-                    NetworkClientState::NeedsWrite =>
-                        self.client_writable(poll, id)?,
-                    _ => {}
-                }
-            }
-            swap(&mut cache, &mut self.pending_cache);
-        }
-        Ok(())
-    }
-}
--- a/gameServer2/src/server/room.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,391 +0,0 @@
-use std::{
-    iter, collections::HashMap
-};
-use crate::server::{
-    coretypes::{
-        ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting,
-        MAX_HEDGEHOGS_PER_TEAM
-    },
-    client::{HWClient}
-};
-use bitflags::*;
-use serde::{Serialize, Deserialize};
-use serde_derive::{Serialize, Deserialize};
-use serde_yaml;
-
-const MAX_TEAMS_IN_ROOM: u8 = 8;
-const MAX_HEDGEHOGS_IN_ROOM: u8 =
-    MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM;
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Ammo {
-    name: String,
-    settings: Option<String>
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Scheme {
-    name: String,
-    settings: Vec<String>
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct RoomConfig {
-    feature_size: u32,
-    map_type: String,
-    map_generator: u32,
-    maze_size: u32,
-    seed: String,
-    template: u32,
-
-    ammo: Ammo,
-    scheme: Scheme,
-    script: String,
-    theme: String,
-    drawn_map: Option<String>
-}
-
-impl RoomConfig {
-    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
-        }
-    }
-}
-
-fn client_teams_impl(teams: &[(ClientId, TeamInfo)], client_id: ClientId)
-    -> impl Iterator<Item = &TeamInfo> + Clone
-{
-    teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
-}
-
-fn map_config_from(c: &RoomConfig) -> Vec<String> {
-    vec![c.feature_size.to_string(), c.map_type.to_string(),
-         c.map_generator.to_string(), c.maze_size.to_string(),
-         c.seed.to_string(), c.template.to_string()]
-}
-
-fn game_config_from(c: &RoomConfig) -> Vec<GameCfg> {
-    use crate::server::coretypes::GameCfg::*;
-    let mut v = vec![
-        Ammo(c.ammo.name.to_string(), c.ammo.settings.clone()),
-        Scheme(c.scheme.name.to_string(), c.scheme.settings.clone()),
-        Script(c.script.to_string()),
-        Theme(c.theme.to_string())];
-    if let Some(ref m) = c.drawn_map {
-        v.push(DrawnMap(m.to_string()))
-    }
-    v
-}
-
-pub struct GameInfo {
-    pub teams_in_game: u8,
-    pub teams_at_start: Vec<(ClientId, TeamInfo)>,
-    pub left_teams: Vec<String>,
-    pub msg_log: Vec<String>,
-    pub sync_msg: Option<String>,
-    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<Item = &TeamInfo> + 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<ClientId>,
-    pub name: String,
-    pub password: Option<String>,
-    pub greeting: String,
-    pub protocol_number: u16,
-    pub flags: RoomFlags,
-
-    pub players_number: u8,
-    pub default_hedgehog_number: u8,
-    pub team_limit: u8,
-    pub ready_players_number: u8,
-    pub teams: Vec<(ClientId, TeamInfo)>,
-    config: RoomConfig,
-    pub voting: Option<Voting>,
-    pub saves: HashMap<String, RoomSave>,
-    pub game_info: Option<GameInfo>
-}
-
-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,
-            team_limit: 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<String> {
-        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<F>(&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<F>(&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<Item = &TeamInfo> {
-        client_teams_impl(&self.teams, client_id)
-    }
-
-    pub fn client_team_indices(&self, client_id: ClientId) -> Vec<u8> {
-        self.teams.iter().enumerate()
-            .filter(move |(_, (id, _))| *id == client_id)
-            .map(|(i, _)| i as u8).collect()
-    }
-
-    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<u8> {
-        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) {
-        let c = &mut self.config;
-        match cfg {
-            FeatureSize(s) => c.feature_size = s,
-            MapType(t) => c.map_type = t,
-            MapGenerator(g) => c.map_generator = g,
-            MazeSize(s) => c.maze_size = s,
-            Seed(s) => c.seed = s,
-            Template(t) => c.template = t,
-
-            Ammo(n, s) => c.ammo = Ammo {name: n, settings: s},
-            Scheme(n, s) => c.scheme = Scheme {name: n, settings: s},
-            Script(s) => c.script = s,
-            Theme(t) => c.theme = t,
-            DrawnMap(m) => c.drawn_map = Some(m)
-        };
-    }
-
-    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<String> {
-        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 map_config(&self) -> Vec<String> {
-        match self.game_info {
-            Some(ref info) => map_config_from(&info.config),
-            None => map_config_from(&self.config)
-        }
-    }
-
-    pub fn game_config(&self) -> Vec<GameCfg> {
-        match self.game_info {
-            Some(ref info) => game_config_from(&info.config),
-            None => game_config_from(&self.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<String, serde_yaml::Error> {
-        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<String, RoomSave>)>(text).map(|(greeting, saves)| {
-            self.greeting = greeting;
-            self.saves = saves;
-        })
-    }
-
-    pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec<String> {
-        let mut info = vec![
-            team.name.clone(),
-            team.grave.clone(),
-            team.fort.clone(),
-            team.voice_pack.clone(),
-            team.flag.clone(),
-            owner.nick.clone(),
-            team.difficulty.to_string()];
-        let hogs = team.hedgehogs.iter().flat_map(|h|
-            iter::once(h.name.clone()).chain(iter::once(h.hat.clone())));
-        info.extend(hogs);
-        info
-    }
-}
\ No newline at end of file
--- a/gameServer2/src/utils.rs	Mon Dec 10 21:23:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-use std::iter::Iterator;
-use mio;
-use base64::{encode};
-
-pub const PROTOCOL_VERSION : u32 = 3;
-pub const SERVER: mio::Token = mio::Token(1_000_000_000);
-
-pub fn is_name_illegal(name: &str ) -> bool{
-    name.len() > 40 ||
-        name.trim().is_empty() ||
-        name.chars().any(|c|
-            "$()*+?[]^{|}\x7F".contains(c) ||
-                '\x00' <= c && c <= '\x1F')
-}
-
-pub fn to_engine_msg<T>(msg: T) -> String
-    where T: Iterator<Item = u8> + Clone
-{
-    let mut tmp = Vec::new();
-    tmp.push(msg.clone().count() as u8);
-    tmp.extend(msg);
-    encode(&tmp)
-}
-
-pub fn protocol_version_string(protocol_number: u16) -> &'static str {
-    match protocol_number {
-        17 => "0.9.7-dev",
-        19 => "0.9.7",
-        20 => "0.9.8-dev",
-        21 => "0.9.8",
-        22 => "0.9.9-dev",
-        23 => "0.9.9",
-        24 => "0.9.10-dev",
-        25 => "0.9.10",
-        26 => "0.9.11-dev",
-        27 => "0.9.11",
-        28 => "0.9.12-dev",
-        29 => "0.9.12",
-        30 => "0.9.13-dev",
-        31 => "0.9.13",
-        32 => "0.9.14-dev",
-        33 => "0.9.14",
-        34 => "0.9.15-dev",
-        35 => "0.9.14.1",
-        37 => "0.9.15",
-        38 => "0.9.16-dev",
-        39 => "0.9.16",
-        40 => "0.9.17-dev",
-        41 => "0.9.17",
-        42 => "0.9.18-dev",
-        43 => "0.9.18",
-        44 => "0.9.19-dev",
-        45 => "0.9.19",
-        46 => "0.9.20-dev",
-        47 => "0.9.20",
-        48 => "0.9.21-dev",
-        49 => "0.9.21",
-        50 => "0.9.22-dev",
-        51 => "0.9.22",
-        52 => "0.9.23-dev",
-        53 => "0.9.23",
-        54 => "0.9.24-dev",
-        55 => "0.9.24",
-        56 => "0.9.25-dev",
-        _ => "Unknown"
-    }
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/Cargo.toml	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,28 @@
+[package]
+edition = "2018"
+name = "hedgewars-server"
+version = "0.0.1"
+authors = [ "Andrey Korotaev <a.korotaev@hedgewars.org>" ]
+
+[features]
+official-server = ["openssl"]
+tls-connections = ["openssl"]
+default = []
+
+[dependencies]
+rand = "0.5"
+mio = "0.6"
+slab = "0.4"
+netbuf = "0.4"
+nom = "4.1"
+env_logger = "0.6"
+log = "0.4"
+base64 = "0.10"
+bitflags = "1.0"
+serde = "1.0"
+serde_yaml = "0.8"
+serde_derive = "1.0"
+openssl = { version = "0.10", optional = true }
+
+[dev-dependencies]
+proptest = "0.8"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/main.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,62 @@
+#![allow(unused_imports)]
+#![deny(bare_trait_objects)]
+
+//use std::io::*;
+//use rand::Rng;
+//use std::cmp::Ordering;
+use mio::net::*;
+use mio::*;
+use log::*;
+
+mod utils;
+mod server;
+mod protocol;
+
+use crate::server::network::NetworkLayer;
+use std::time::Duration;
+
+fn main() {
+    env_logger::init();
+
+    info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION);
+
+    let address = "0.0.0.0:46631".parse().unwrap();
+    let listener = TcpListener::bind(&address).unwrap();
+
+    let poll = Poll::new().unwrap();
+    let mut hw_network = NetworkLayer::new(listener, 1024, 512);
+    hw_network.register_server(&poll).unwrap();
+
+    let mut events = Events::with_capacity(1024);
+
+    loop {
+        let timeout = if hw_network.has_pending_operations() {
+            Some(Duration::from_millis(1))
+        } else {
+            None
+        };
+        poll.poll(&mut events, timeout).unwrap();
+
+        for event in events.iter() {
+            if event.readiness() & Ready::readable() == Ready::readable() {
+                match event.token() {
+                    utils::SERVER => hw_network.accept_client(&poll).unwrap(),
+                    Token(tok) => hw_network.client_readable(&poll, tok).unwrap(),
+                }
+            }
+            if event.readiness() & Ready::writable() == Ready::writable() {
+                match event.token() {
+                    utils::SERVER => unreachable!(),
+                    Token(tok) => hw_network.client_writable(&poll, tok).unwrap(),
+                }
+            }
+//            if event.kind().is_hup() || event.kind().is_error() {
+//                match event.token() {
+//                    utils::SERVER => unreachable!(),
+//                    Token(tok) => server.client_error(&poll, tok).unwrap(),
+//                }
+//            }
+        }
+        hw_network.on_idle(&poll).unwrap();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/protocol.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,47 @@
+use netbuf;
+use std::{
+    io::{Read, Result}
+};
+use nom::{
+    IResult, Err
+};
+
+pub mod messages;
+#[cfg(test)]
+pub mod test;
+mod parser;
+
+pub struct ProtocolDecoder {
+    buf: netbuf::Buf,
+    consumed: usize,
+}
+
+impl ProtocolDecoder {
+    pub fn new() -> ProtocolDecoder {
+        ProtocolDecoder {
+            buf: netbuf::Buf::new(),
+            consumed: 0,
+        }
+    }
+
+    pub fn read_from<R: Read>(&mut self, stream: &mut R) -> Result<usize> {
+        self.buf.read_from(stream)
+    }
+
+    pub fn extract_messages(&mut self) -> Vec<messages::HWProtocolMessage> {
+        let parse_result = parser::extract_messages(&self.buf[..]);
+        match parse_result {
+            Ok((tail, msgs)) => {
+                self.consumed = self.buf.len() - self.consumed - tail.len();
+                msgs
+            },
+            Err(Err::Incomplete(_)) => unreachable!(),
+            Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(),
+        }
+    }
+
+    pub fn sweep(&mut self) {
+        self.buf.consume(self.consumed);
+        self.consumed = 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/protocol/messages.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,316 @@
+use crate::server::coretypes::{
+    ServerVar, GameCfg, TeamInfo,
+    HedgehogInfo, VoteType
+};
+use std::{ops, convert::From, iter::once};
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum HWProtocolMessage {
+    // core
+    Ping,
+    Pong,
+    Quit(Option<String>),
+    //Cmd(String, Vec<String>),
+    Global(String),
+    Watch(String),
+    ToggleServerRegisteredOnly,
+    SuperPower,
+    Info(String),
+    // not entered state
+    Nick(String),
+    Proto(u16),
+    Password(String, String),
+    Checker(u16, String, String),
+    // lobby
+    List,
+    Chat(String),
+    CreateRoom(String, Option<String>),
+    JoinRoom(String, Option<String>),
+    Follow(String),
+    Rnd(Vec<String>),
+    Kick(String),
+    Ban(String, String, u32),
+    BanIP(String, String, u32),
+    BanNick(String, String, u32),
+    BanList,
+    Unban(String),
+    SetServerVar(ServerVar),
+    GetServerVar,
+    RestartServer,
+    Stats,
+    // in room
+    Part(Option<String>),
+    Cfg(GameCfg),
+    AddTeam(Box<TeamInfo>),
+    RemoveTeam(String),
+    SetHedgehogsNumber(String, u8),
+    SetTeamColor(String, u8),
+    ToggleReady,
+    StartGame,
+    EngineMessage(String),
+    RoundFinished,
+    ToggleRestrictJoin,
+    ToggleRestrictTeams,
+    ToggleRegisteredOnly,
+    RoomName(String),
+    Delegate(String),
+    TeamChat(String),
+    MaxTeams(u8),
+    Fix,
+    Unfix,
+    Greeting(String),
+    CallVote(Option<VoteType>),
+    Vote(bool),
+    ForceVote(bool),
+    Save(String, String),
+    Delete(String),
+    SaveRoom(String),
+    LoadRoom(String),
+    Malformed,
+    Empty,
+}
+
+#[derive(Debug)]
+pub enum HWServerMessage {
+    Ping,
+    Pong,
+    Bye(String),
+    Nick(String),
+    Proto(u16),
+    ServerAuth(String),
+    LobbyLeft(String, String),
+    LobbyJoined(Vec<String>),
+    ChatMsg {nick: String, msg: String},
+    ClientFlags(String, Vec<String>),
+    Rooms(Vec<String>),
+    RoomAdd(Vec<String>),
+    RoomJoined(Vec<String>),
+    RoomLeft(String, String),
+    RoomRemove(String),
+    RoomUpdated(String, Vec<String>),
+    TeamAdd(Vec<String>),
+    TeamRemove(String),
+    TeamAccepted(String),
+    TeamColor(String, u8),
+    HedgehogsNumber(String, u8),
+    ConfigEntry(String, Vec<String>),
+    Kicked,
+    RunGame,
+    ForwardEngineMessage(Vec<String>),
+    RoundFinished,
+
+    ServerMessage(String),
+    Notice(String),
+    Warning(String),
+    Error(String),
+    Connected(u32),
+    Unreachable,
+
+    //Deprecated messages
+    LegacyReady(bool, Vec<String>)
+}
+
+pub fn server_chat(msg: String) -> HWServerMessage  {
+    HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg }
+}
+
+impl GameCfg {
+    pub fn to_protocol(&self) -> (String, Vec<String>) {
+        use crate::server::coretypes::GameCfg::*;
+        match self {
+            FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]),
+            MapType(t) => ("MAP".to_string(), vec![t.to_string()]),
+            MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]),
+            MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]),
+            Seed(s) => ("SEED".to_string(), vec![s.to_string()]),
+            Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]),
+
+            Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]),
+            Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]),
+            Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]),
+            Scheme(n, s) => ("SCHEME".to_string(), {
+                let mut v = vec![n.to_string()];
+                v.extend(s.clone().into_iter());
+                v
+            }),
+            Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]),
+            Theme(t) => ("THEME".to_string(), vec![t.to_string()]),
+            DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()])
+        }
+    }
+
+    pub fn to_server_msg(&self) -> HWServerMessage {
+        use self::HWServerMessage::ConfigEntry;
+        let (name, args) = self.to_protocol();
+        HWServerMessage::ConfigEntry(name, args)
+    }
+}
+
+macro_rules! const_braces {
+    ($e: expr) => { "{}\n" }
+}
+
+macro_rules! msg {
+    [$($part: expr),*] => {
+        format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
+    };
+}
+
+#[cfg(test)]
+macro_rules! several {
+    [$part: expr] => { once($part) };
+    [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) };
+}
+
+impl HWProtocolMessage {
+    /** Converts the message to a raw `String`, which can be sent over the network.
+     *
+     * This is the inverse of the `message` parser.
+     */
+    #[cfg(test)]
+    pub(crate) fn to_raw_protocol(&self) -> String {
+        use self::HWProtocolMessage::*;
+        match self {
+            Ping => msg!["PING"],
+            Pong => msg!["PONG"],
+            Quit(None) => msg!["QUIT"],
+            Quit(Some(msg)) => msg!["QUIT", msg],
+            Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)],
+            Watch(name) => msg!["CMD", format!("WATCH {}", name)],
+            ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
+            SuperPower =>  msg!["CMD", "SUPER_POWER"],
+            Info(info) => msg!["CMD", format!("INFO {}", info)],
+            Nick(nick) => msg!("NICK", nick),
+            Proto(version) => msg!["PROTO", version],
+            Password(p, s) => msg!["PASSWORD", p, s],
+            Checker(i, n, p) => msg!["CHECKER", i, n, p],
+            List => msg!["LIST"],
+            Chat(msg) => msg!["CHAT", msg],
+            CreateRoom(name, None) => msg!["CREATE_ROOM", name],
+            CreateRoom(name, Some(password)) =>
+                msg!["CREATE_ROOM", name, password],
+            JoinRoom(name, None) => msg!["JOIN_ROOM", name],
+            JoinRoom(name, Some(password)) =>
+                msg!["JOIN_ROOM", name, password],
+            Follow(name) => msg!["FOLLOW", name],
+            Rnd(args) => if args.is_empty() {
+                msg!["CMD", "RND"]
+            } else {
+                msg!["CMD", format!("RND {}", args.join(" "))]
+            },
+            Kick(name) => msg!["KICK", name],
+            Ban(name, reason, time) => msg!["BAN", name, reason, time],
+            BanIP(ip, reason, time) => msg!["BAN_IP", ip, reason, time],
+            BanNick(nick, reason, time) =>
+                msg!("BAN_NICK", nick, reason, time),
+            BanList => msg!["BANLIST"],
+            Unban(name) => msg!["UNBAN", name],
+            //SetServerVar(ServerVar), ???
+            GetServerVar => msg!["GET_SERVER_VAR"],
+            RestartServer => msg!["CMD", "RESTART_SERVER YES"],
+            Stats => msg!["CMD", "STATS"],
+            Part(None) => msg!["PART"],
+            Part(Some(msg)) => msg!["PART", msg],
+            Cfg(config) => {
+                let (name, args) = config.to_protocol();
+                msg!["CFG", name, args.join("\n")]
+            },
+            AddTeam(info) =>
+                msg!["ADD_TEAM", info.name, info.color, info.grave, info.fort,
+                     info.voice_pack, info.flag, info.difficulty,
+                     info.hedgehogs.iter()
+                        .flat_map(|h| several![&h.name[..], &h.hat[..]])
+                        .collect::<Vec<_>>().join("\n")],
+            RemoveTeam(name) => msg!["REMOVE_TEAM", name],
+            SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number],
+            SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color],
+            ToggleReady => msg!["TOGGLE_READY"],
+            StartGame => msg!["START_GAME"],
+            EngineMessage(msg) => msg!["EM", msg],
+            RoundFinished => msg!["ROUNDFINISHED"],
+            ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"],
+            ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"],
+            ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"],
+            RoomName(name) => msg!["ROOM_NAME", name],
+            Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)],
+            TeamChat(msg) => msg!["TEAMCHAT", msg],
+            MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)] ,
+            Fix => msg!["CMD", "FIX"],
+            Unfix => msg!["CMD", "UNFIX"],
+            Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)],
+            //CallVote(Option<(String, Option<String>)>) =>, ??
+            Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg {"YES"} else {"NO"})],
+            ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg {"YES"} else {"NO"})],
+            Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)],
+            Delete(name) => msg!["CMD", format!("DELETE {}", name)],
+            SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)],
+            LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)],
+            Malformed => msg!["A", "QUICK", "BROWN", "HOG", "JUMPS", "OVER", "THE", "LAZY", "DOG"],
+            Empty => msg![""],
+            _ => panic!("Protocol message not yet implemented")
+        }
+    }
+}
+
+fn construct_message(header: &[&str], msg: &[String]) -> String {
+    let mut v: Vec<_> = header.iter().cloned().collect();
+    v.extend(msg.iter().map(|s| &s[..]));
+    v.push("\n");
+    v.join("\n")
+}
+
+impl HWServerMessage {
+    pub fn to_raw_protocol(&self) -> String {
+        use self::HWServerMessage::*;
+        match self {
+            Ping => msg!["PING"],
+            Pong => msg!["PONG"],
+            Connected(protocol_version) => msg![
+                "CONNECTED",
+                "Hedgewars server https://www.hedgewars.org/",
+                protocol_version],
+            Bye(msg) => msg!["BYE", msg],
+            Nick(nick) => msg!["NICK", nick],
+            Proto(proto) => msg!["PROTO", proto],
+            ServerAuth(hash) => msg!["SERVER_AUTH", hash],
+            LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg],
+            LobbyJoined(nicks) =>
+                construct_message(&["LOBBY:JOINED"], &nicks),
+            ClientFlags(flags, nicks) =>
+                construct_message(&["CLIENT_FLAGS", flags], &nicks),
+            Rooms(info) =>
+                construct_message(&["ROOMS"], &info),
+            RoomAdd(info) =>
+                construct_message(&["ROOM", "ADD"], &info),
+            RoomJoined(nicks) =>
+                construct_message(&["JOINED"], &nicks),
+            RoomLeft(nick, msg) => msg!["LEFT", nick, msg],
+            RoomRemove(name) => msg!["ROOM", "DEL", name],
+            RoomUpdated(name, info) =>
+                construct_message(&["ROOM", "UPD", name], &info),
+            TeamAdd(info) =>
+                construct_message(&["ADD_TEAM"], &info),
+            TeamRemove(name) => msg!["REMOVE_TEAM", name],
+            TeamAccepted(name) => msg!["TEAM_ACCEPTED", name],
+            TeamColor(name, color) => msg!["TEAM_COLOR", name, color],
+            HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
+            ConfigEntry(name, values) =>
+                construct_message(&["CFG", name], &values),
+            Kicked => msg!["KICKED"],
+            RunGame => msg!["RUN_GAME"],
+            ForwardEngineMessage(em) =>
+                construct_message(&["EM"], &em),
+            RoundFinished => msg!["ROUND_FINISHED"],
+            ChatMsg {nick, msg} => msg!["CHAT", nick, msg],
+            ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
+            Notice(msg) => msg!["NOTICE", msg],
+            Warning(msg) => msg!["WARNING", msg],
+            Error(msg) => msg!["ERROR", msg],
+
+            LegacyReady(is_ready, nicks) =>
+                construct_message(&[if *is_ready {"READY"} else {"NOT_READY"}], &nicks),
+
+            _ => msg!["ERROR", "UNIMPLEMENTED"],
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/protocol/parser.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,284 @@
+/** The parsers for the chat and multiplayer protocol. The main parser is `message`.
+ * # Protocol
+ * All messages consist of `\n`-separated strings. The end of a message is
+ * indicated by a double newline - `\n\n`.
+ *
+ * For example, a nullary command like PING will be actually sent as `PING\n\n`.
+ * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
+ */
+
+use nom::*;
+
+use std::{
+    str, str::FromStr,
+    ops::Range
+};
+use super::{
+    messages::{HWProtocolMessage, HWProtocolMessage::*}
+};
+#[cfg(test)]
+use {
+    super::test::gen_proto_msg,
+    proptest::{proptest, proptest_helper}
+};
+use crate::server::coretypes::{
+    HedgehogInfo, TeamInfo, GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM
+};
+
+named!(end_of_message, tag!("\n\n"));
+named!(str_line<&[u8],   &str>, map_res!(not_line_ending, str::from_utf8));
+named!(  a_line<&[u8], String>, map!(str_line, String::from));
+named!(cmd_arg<&[u8], String>,
+    map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from));
+named!( u8_line<&[u8],     u8>, map_res!(str_line, FromStr::from_str));
+named!(u16_line<&[u8],    u16>, map_res!(str_line, FromStr::from_str));
+named!(u32_line<&[u8],    u32>, map_res!(str_line, FromStr::from_str));
+named!(yes_no_line<&[u8], bool>, alt!(
+      do_parse!(tag_no_case!("YES") >> (true))
+    | do_parse!(tag_no_case!("NO") >> (false))));
+named!(opt_param<&[u8], Option<String> >, alt!(
+      do_parse!(peek!(tag!("\n\n")) >> (None))
+    | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string())))));
+named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" ")));
+named!(opt_space_param<&[u8], Option<String> >, alt!(
+      do_parse!(peek!(tag!("\n\n")) >> (None))
+    | do_parse!(spaces >> s: str_line >> (Some(s.to_string())))));
+named!(hog_line<&[u8], HedgehogInfo>,
+    do_parse!(name: str_line >> eol >> hat: str_line >>
+        (HedgehogInfo{name: name.to_string(), hat: hat.to_string()})));
+named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>,
+    do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >>
+              h3: hog_line >> eol >> h4: hog_line >> eol >>
+              h5: hog_line >> eol >> h6: hog_line >> eol >>
+              h7: hog_line >> eol >> h8: hog_line >>
+              ([h1, h2, h3, h4, h5, h6, h7, h8])));
+named!(voting<&[u8], VoteType>, alt!(
+      do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >>
+        (VoteType::Kick(n)))
+    | do_parse!(tag_no_case!("MAP") >>
+        n: opt!(preceded!(spaces, a_line)) >>
+        (VoteType::Map(n)))
+    | do_parse!(tag_no_case!("PAUSE") >>
+        (VoteType::Pause))
+    | do_parse!(tag_no_case!("NEWSEED") >>
+        (VoteType::NewSeed))
+    | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >>
+        (VoteType::HedgehogsPerTeam(n)))));
+
+/** Recognizes messages which do not take any parameters */
+named!(basic_message<&[u8], HWProtocolMessage>, alt!(
+      do_parse!(tag!("PING") >> (Ping))
+    | do_parse!(tag!("PONG") >> (Pong))
+    | do_parse!(tag!("LIST") >> (List))
+    | do_parse!(tag!("BANLIST")        >> (BanList))
+    | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
+    | do_parse!(tag!("TOGGLE_READY")   >> (ToggleReady))
+    | do_parse!(tag!("START_GAME")     >> (StartGame))
+    | do_parse!(tag!("ROUNDFINISHED")  >> _m: opt_param >> (RoundFinished))
+    | do_parse!(tag!("TOGGLE_RESTRICT_JOINS")  >> (ToggleRestrictJoin))
+    | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS")  >> (ToggleRestrictTeams))
+    | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
+));
+
+/** Recognizes messages which take exactly one parameter */
+named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
+      do_parse!(tag!("NICK")    >> eol >> n: a_line >> (Nick(n)))
+    | do_parse!(tag!("INFO")    >> eol >> n: a_line >> (Info(n)))
+    | do_parse!(tag!("CHAT")    >> eol >> m: a_line >> (Chat(m)))
+    | do_parse!(tag!("PART")    >> msg: opt_param   >> (Part(msg)))
+    | do_parse!(tag!("FOLLOW")  >> eol >> n: a_line >> (Follow(n)))
+    | do_parse!(tag!("KICK")    >> eol >> n: a_line >> (Kick(n)))
+    | do_parse!(tag!("UNBAN")   >> eol >> n: a_line >> (Unban(n)))
+    | do_parse!(tag!("EM")      >> eol >> m: a_line >> (EngineMessage(m)))
+    | do_parse!(tag!("TEAMCHAT")    >> eol >> m: a_line >> (TeamChat(m)))
+    | do_parse!(tag!("ROOM_NAME")   >> eol >> n: a_line >> (RoomName(n)))
+    | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
+
+    | do_parse!(tag!("PROTO")   >> eol >> d: u16_line >> (Proto(d)))
+
+    | do_parse!(tag!("QUIT")   >> msg: opt_param >> (Quit(msg)))
+));
+
+/** Recognizes messages preceded with CMD */
+named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
+      do_parse!(tag_no_case!("STATS") >> (Stats))
+    | do_parse!(tag_no_case!("FIX")   >> (Fix))
+    | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
+    | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer))
+    | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
+    | do_parse!(tag_no_case!("SUPER_POWER")     >> (SuperPower))
+    | do_parse!(tag_no_case!("PART")     >> m: opt_space_param >> (Part(m)))
+    | do_parse!(tag_no_case!("QUIT")     >> m: opt_space_param >> (Quit(m)))
+    | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line  >> (Delegate(n)))
+    | do_parse!(tag_no_case!("SAVE")     >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l)))
+    | do_parse!(tag_no_case!("DELETE")   >> spaces >> n: a_line  >> (Delete(n)))
+    | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line  >> (SaveRoom(r)))
+    | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line  >> (LoadRoom(r)))
+    | do_parse!(tag_no_case!("GLOBAL")   >> spaces >> m: a_line  >> (Global(m)))
+    | do_parse!(tag_no_case!("WATCH")    >> spaces >> i: a_line  >> (Watch(i)))
+    | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line  >> (Greeting(m)))
+    | do_parse!(tag_no_case!("VOTE")     >> spaces >> m: yes_no_line >> (Vote(m)))
+    | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: yes_no_line >> (ForceVote(m)))
+    | do_parse!(tag_no_case!("INFO")     >> spaces >> n: a_line  >> (Info(n)))
+    | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n)))
+    | do_parse!(tag_no_case!("CALLVOTE") >>
+        v: opt!(preceded!(spaces, voting)) >> (CallVote(v)))
+    | do_parse!(
+        tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >>
+        v: str_line >>
+        (Rnd(v.split_whitespace().map(String::from).collect())))
+)));
+
+named!(complex_message<&[u8], HWProtocolMessage>, alt!(
+      do_parse!(tag!("PASSWORD")  >> eol >>
+                    p: a_line     >> eol >>
+                    s: a_line     >>
+                    (Password(p, s)))
+    | do_parse!(tag!("CHECKER")   >> eol >>
+                    i: u16_line   >> eol >>
+                    n: a_line     >> eol >>
+                    p: a_line     >>
+                    (Checker(i, n, p)))
+    | do_parse!(tag!("CREATE_ROOM") >> eol >>
+                    n: a_line       >>
+                    p: opt_param    >>
+                    (CreateRoom(n, p)))
+    | do_parse!(tag!("JOIN_ROOM")   >> eol >>
+                    n: a_line       >>
+                    p: opt_param    >>
+                    (JoinRoom(n, p)))
+    | do_parse!(tag!("ADD_TEAM")    >> eol >>
+                    name: a_line    >> eol >>
+                    color: u8_line  >> eol >>
+                    grave: a_line   >> eol >>
+                    fort: a_line    >> eol >>
+                    voice_pack: a_line >> eol >>
+                    flag: a_line    >> eol >>
+                    difficulty: u8_line >> eol >>
+                    hedgehogs: _8_hogs >>
+                    (AddTeam(Box::new(TeamInfo{
+                        name, color, grave, fort,
+                        voice_pack, flag, difficulty,
+                        hedgehogs, hedgehogs_number: 0
+                     }))))
+    | do_parse!(tag!("HH_NUM")    >> eol >>
+                    n: a_line     >> eol >>
+                    c: u8_line    >>
+                    (SetHedgehogsNumber(n, c)))
+    | do_parse!(tag!("TEAM_COLOR")    >> eol >>
+                    n: a_line     >> eol >>
+                    c: u8_line    >>
+                    (SetTeamColor(n, c)))
+    | do_parse!(tag!("BAN")    >> eol >>
+                    n: a_line     >> eol >>
+                    r: a_line     >> eol >>
+                    t: u32_line   >>
+                    (Ban(n, r, t)))
+    | do_parse!(tag!("BAN_IP")    >> eol >>
+                    n: a_line     >> eol >>
+                    r: a_line     >> eol >>
+                    t: u32_line   >>
+                    (BanIP(n, r, t)))
+    | do_parse!(tag!("BAN_NICK")    >> eol >>
+                    n: a_line     >> eol >>
+                    r: a_line     >> eol >>
+                    t: u32_line   >>
+                    (BanNick(n, r, t)))
+));
+
+named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!(
+      do_parse!(tag!("THEME")    >> eol >>
+                name: a_line     >>
+                (GameCfg::Theme(name)))
+    | do_parse!(tag!("SCRIPT")   >> eol >>
+                name: a_line     >>
+                (GameCfg::Script(name)))
+    | do_parse!(tag!("AMMO")     >> eol >>
+                name: a_line     >>
+                value: opt_param >>
+                (GameCfg::Ammo(name, value)))
+    | do_parse!(tag!("SCHEME")   >> eol >>
+                name: a_line     >>
+                values: opt!(preceded!(eol, separated_list!(eol, a_line))) >>
+                (GameCfg::Scheme(name, values.unwrap_or_default())))
+    | do_parse!(tag!("FEATURE_SIZE") >> eol >>
+                value: u32_line    >>
+                (GameCfg::FeatureSize(value)))
+    | do_parse!(tag!("MAP")      >> eol >>
+                value: a_line    >>
+                (GameCfg::MapType(value)))
+    | do_parse!(tag!("MAPGEN")   >> eol >>
+                value: u32_line  >>
+                (GameCfg::MapGenerator(value)))
+    | do_parse!(tag!("MAZE_SIZE") >> eol >>
+                value: u32_line   >>
+                (GameCfg::MazeSize(value)))
+    | do_parse!(tag!("SEED")     >> eol >>
+                value: a_line    >>
+                (GameCfg::Seed(value)))
+    | do_parse!(tag!("TEMPLATE") >> eol >>
+                value: u32_line  >>
+                (GameCfg::Template(value)))
+    | do_parse!(tag!("DRAWNMAP") >> eol >>
+                value: a_line    >>
+                (GameCfg::DrawnMap(value)))
+), Cfg)));
+
+named!(malformed_message<&[u8], HWProtocolMessage>,
+    do_parse!(separated_list!(eol, a_line) >> (Malformed)));
+
+named!(empty_message<&[u8], HWProtocolMessage>,
+    do_parse!(alt!(end_of_message | eol) >> (Empty)));
+
+named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
+    alt!(
+          basic_message
+        | one_param_message
+        | cmd_message
+        | complex_message
+        | cfg_message
+        ), end_of_message
+    )
+    | terminated!(malformed_message, end_of_message)
+    | empty_message
+    )
+);
+
+named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message)));
+
+#[cfg(test)]
+proptest! {
+    #[test]
+    fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
+        println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
+        assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
+    }
+}
+
+#[test]
+fn parse_test() {
+    assert_eq!(message(b"PING\n\n"),          Ok((&b""[..], Ping)));
+    assert_eq!(message(b"START_GAME\n\n"),    Ok((&b""[..], StartGame)));
+    assert_eq!(message(b"NICK\nit's me\n\n"), Ok((&b""[..], Nick("it's me".to_string()))));
+    assert_eq!(message(b"PROTO\n51\n\n"),     Ok((&b""[..], Proto(51))));
+    assert_eq!(message(b"QUIT\nbye-bye\n\n"), Ok((&b""[..], Quit(Some("bye-bye".to_string())))));
+    assert_eq!(message(b"QUIT\n\n"),          Ok((&b""[..], Quit(None))));
+    assert_eq!(message(b"CMD\nwatch demo\n\n"), Ok((&b""[..], Watch("demo".to_string()))));
+    assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77))));
+
+    assert_eq!(message(b"CMD\nPART\n\n"),      Ok((&b""[..], Part(None))));
+    assert_eq!(message(b"CMD\nPART _msg_\n\n"), Ok((&b""[..], Part(Some("_msg_".to_string())))));
+
+    assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![]))));
+    assert_eq!(
+        message(b"CMD\nRND A B\n\n"),
+        Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")])))
+    );
+
+    assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"),    Ok((&b""[..], vec![Malformed])));
+
+    assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), Ok((&b"P"[..], vec![Ping, Ping])));
+    assert_eq!(extract_messages(b"SING\n\nPING\n\n"),  Ok((&b""[..],  vec![Malformed, Ping])));
+    assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"),  Ok((&b""[..],  vec![Empty, Empty, Ping])));
+    assert_eq!(extract_messages(b"\n\n\nPING\n\n"),    Ok((&b""[..],  vec![Empty, Empty, Ping])));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/protocol/test.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,168 @@
+use proptest::{
+    test_runner::{TestRunner, Reason},
+    arbitrary::{any, any_with, Arbitrary, StrategyFor},
+    strategy::{Strategy, BoxedStrategy, Just, Map}
+};
+
+use crate::server::coretypes::{GameCfg, TeamInfo, HedgehogInfo};
+
+use super::messages::{
+    HWProtocolMessage, HWProtocolMessage::*
+};
+
+// Due to inability to define From between Options
+trait Into2<T>: Sized { fn into2(self) -> T; }
+impl <T> Into2<T> for T { fn into2(self) -> T { self } }
+impl Into2<Vec<String>> for Vec<Ascii> {
+    fn into2(self) -> Vec<String> {
+        self.into_iter().map(|x| x.0).collect()
+    }
+}
+impl Into2<String> for Ascii { fn into2(self) -> String { self.0 } }
+impl Into2<Option<String>> for Option<Ascii>{
+    fn into2(self) -> Option<String> { self.map(|x| {x.0}) }
+}
+
+macro_rules! proto_msg_case {
+    ($val: ident()) =>
+        (Just($val));
+    ($val: ident($arg: ty)) =>
+        (any::<$arg>().prop_map(|v| {$val(v.into2())}));
+    ($val: ident($arg1: ty, $arg2: ty)) =>
+        (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())}));
+    ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) =>
+        (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())}));
+}
+
+macro_rules! proto_msg_match {
+    ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => (
+        match $var {
+            $($num => (proto_msg_case!($constr $res)).boxed()),*,
+            _ => Just($default).boxed()
+        }
+    )
+}
+
+/// Wrapper type for generating non-empty strings
+#[derive(Debug)]
+struct Ascii(String);
+
+impl Arbitrary for Ascii {
+    type Parameters = <String as Arbitrary>::Parameters;
+
+    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+        "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
+    }
+
+    type Strategy = BoxedStrategy<Ascii>;
+}
+
+impl Arbitrary for GameCfg {
+    type Parameters = ();
+
+    fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
+        use crate::server::coretypes::GameCfg::*;
+        (0..10).no_shrink().prop_flat_map(|i| {
+            proto_msg_match!(i, def = FeatureSize(0),
+            0 => FeatureSize(u32),
+            1 => MapType(Ascii),
+            2 => MapGenerator(u32),
+            3 => MazeSize(u32),
+            4 => Seed(Ascii),
+            5 => Template(u32),
+            6 => Ammo(Ascii, Option<Ascii>),
+            7 => Scheme(Ascii, Vec<Ascii>),
+            8 => Script(Ascii),
+            9 => Theme(Ascii),
+            10 => DrawnMap(Ascii))
+        }).boxed()
+    }
+
+    type Strategy = BoxedStrategy<GameCfg>;
+}
+
+impl Arbitrary for TeamInfo {
+    type Parameters = ();
+
+    fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy {
+        ("[a-z]+", 0u8..127u8, "[a-z]+", "[a-z]+", "[a-z]+", "[a-z]+",  0u8..127u8)
+            .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| {
+                fn hog(n: u8) -> HedgehogInfo {
+                    HedgehogInfo { name: format!("hog{}", n), hat: format!("hat{}", n)}
+                }
+                let hedgehogs = [hog(1), hog(2), hog(3), hog(4), hog(5), hog(6), hog(7), hog(8)];
+                TeamInfo {
+                    name, color, grave, fort,
+                    voice_pack, flag,difficulty,
+                    hedgehogs, hedgehogs_number: 0
+                }
+            }).boxed()
+    }
+
+    type Strategy = BoxedStrategy<TeamInfo>;
+}
+
+pub fn gen_proto_msg() -> BoxedStrategy<HWProtocolMessage> where {
+    let res = (0..58).no_shrink().prop_flat_map(|i| {
+        proto_msg_match!(i, def = Malformed,
+        0 => Ping(),
+        1 => Pong(),
+        2 => Quit(Option<Ascii>),
+        //3 => Cmd
+        4 => Global(Ascii),
+        5 => Watch(Ascii),
+        6 => ToggleServerRegisteredOnly(),
+        7 => SuperPower(),
+        8 => Info(Ascii),
+        9 => Nick(Ascii),
+        10 => Proto(u16),
+        11 => Password(Ascii, Ascii),
+        12 => Checker(u16, Ascii, Ascii),
+        13 => List(),
+        14 => Chat(Ascii),
+        15 => CreateRoom(Ascii, Option<Ascii>),
+        16 => JoinRoom(Ascii, Option<Ascii>),
+        17 => Follow(Ascii),
+        18 => Rnd(Vec<Ascii>),
+        19 => Kick(Ascii),
+        20 => Ban(Ascii, Ascii, u32),
+        21 => BanIP(Ascii, Ascii, u32),
+        22 => BanNick(Ascii, Ascii, u32),
+        23 => BanList(),
+        24 => Unban(Ascii),
+        //25 => SetServerVar(ServerVar),
+        26 => GetServerVar(),
+        27 => RestartServer(),
+        28 => Stats(),
+        29 => Part(Option<Ascii>),
+        30 => Cfg(GameCfg),
+        31 => AddTeam(Box<TeamInfo>),
+        32 => RemoveTeam(Ascii),
+        33 => SetHedgehogsNumber(Ascii, u8),
+        34 => SetTeamColor(Ascii, u8),
+        35 => ToggleReady(),
+        36 => StartGame(),
+        37 => EngineMessage(Ascii),
+        38 => RoundFinished(),
+        39 => ToggleRestrictJoin(),
+        40 => ToggleRestrictTeams(),
+        41 => ToggleRegisteredOnly(),
+        42 => RoomName(Ascii),
+        43 => Delegate(Ascii),
+        44 => TeamChat(Ascii),
+        45 => MaxTeams(u8),
+        46 => Fix(),
+        47 => Unfix(),
+        48 => Greeting(Ascii),
+        //49 => CallVote(Option<(String, Option<String>)>),
+        50 => Vote(bool),
+        51 => ForceVote(bool),
+        52 => Save(Ascii, Ascii),
+        53 => Delete(Ascii),
+        54 => SaveRoom(Ascii),
+        55 => LoadRoom(Ascii),
+        56 => Malformed(),
+        57 => Empty()
+    )});
+    res.boxed()
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,8 @@
+pub mod core;
+pub mod client;
+pub mod io;
+pub mod room;
+pub mod network;
+pub mod coretypes;
+mod actions;
+mod handlers;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/actions.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,631 @@
+use std::{
+    io, io::Write,
+    iter::once,
+    mem::replace
+};
+use super::{
+    core::HWServer,
+    room::{GameInfo, RoomFlags},
+    client::HWClient,
+    coretypes::{ClientId, RoomId, GameCfg, VoteType},
+    room::HWRoom,
+    handlers
+};
+use crate::{
+    protocol::messages::{
+        HWProtocolMessage,
+        HWServerMessage,
+        HWServerMessage::*,
+        server_chat
+    },
+    utils::to_engine_msg
+};
+use rand::{thread_rng, Rng, distributions::Uniform};
+
+pub enum Destination {
+    ToId(ClientId),
+    ToSelf,
+    ToAll {
+        room_id: Option<RoomId>,
+        protocol: Option<u16>,
+        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_self(message: HWServerMessage) -> PendingMessage {
+        PendingMessage{ destination: Destination::ToSelf, message }
+    }
+
+    pub fn send_all(message: HWServerMessage) -> PendingMessage {
+        let destination = Destination::ToAll {
+            room_id: None,
+            protocol: None,
+            skip_self: false,
+        };
+        PendingMessage{ destination, message }
+    }
+
+    pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage {
+        if let Destination::ToAll {ref mut room_id, ..} = self.destination {
+            *room_id = Some(clients_room_id)
+        }
+        self
+    }
+
+    pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage {
+        if let Destination::ToAll {ref mut protocol, ..} = self.destination {
+            *protocol = Some(protocol_number)
+        }
+        self
+    }
+
+    pub fn but_self(mut self) -> PendingMessage {
+        if let Destination::ToAll {ref mut skip_self, ..} = self.destination {
+            *skip_self = true
+        }
+        self
+    }
+
+    pub fn action(self) -> Action { Send(self) }
+}
+
+impl Into<Action> for PendingMessage {
+    fn into(self) -> Action { self.action() }
+}
+
+impl HWServerMessage {
+    pub fn send(self, client_id: ClientId) -> PendingMessage { PendingMessage::send(self, client_id) }
+    pub fn send_self(self) -> PendingMessage { PendingMessage::send_self(self) }
+    pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) }
+}
+
+pub enum Action {
+    Send(PendingMessage),
+    RemoveClient,
+    ByeClient(String),
+    ReactProtocolMessage(HWProtocolMessage),
+    CheckRegistered,
+    JoinLobby,
+    AddRoom(String, Option<String>),
+    RemoveRoom(RoomId),
+    MoveToRoom(RoomId),
+    MoveToLobby(String),
+    ChangeMaster(RoomId, Option<ClientId>),
+    RemoveTeam(String),
+    RemoveClientTeams,
+    SendRoomUpdate(Option<String>),
+    StartRoomGame(RoomId),
+    SendTeamRemovalMessage(String),
+    FinishRoomGame(RoomId),
+    SendRoomData{to: ClientId, teams: bool, config: bool, flags: bool},
+    AddVote{vote: bool, is_forced: bool},
+    ApplyVoting(VoteType, RoomId),
+    Warn(String),
+    ProtocolError(String)
+}
+
+use self::Action::*;
+
+pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) {
+    match action {
+        Send(msg) => server.send(client_id, &msg.destination, msg.message),
+        ByeClient(msg) => {
+            let c = &server.clients[client_id];
+            let nick = c.nick.clone();
+
+            if let Some(id) = c.room_id{
+                if id != server.lobby_id {
+                    server.react(client_id, vec![
+                        MoveToLobby(format!("quit: {}", msg.clone()))]);
+                }
+            }
+
+            server.react(client_id, vec![
+                LobbyLeft(nick, msg.clone()).send_all().action(),
+                Bye(msg).send_self().action(),
+                RemoveClient]);
+        },
+        RemoveClient => {
+            server.removed_clients.push(client_id);
+            if server.clients.contains(client_id) {
+                server.clients.remove(client_id);
+            }
+        },
+        ReactProtocolMessage(msg) =>
+            handlers::handle(server, client_id, msg),
+        CheckRegistered => {
+            let client = &server.clients[client_id];
+            if client.protocol_number > 0 && client.nick != "" {
+                let has_nick_clash = server.clients.iter().any(
+                    |(id, c)| id != client_id && c.nick == client.nick);
+
+                let actions = if !client.is_checker() && has_nick_clash {
+                    if client.protocol_number < 38 {
+                        vec![ByeClient("Nickname is already in use".to_string())]
+                    } else {
+                        server.clients[client_id].nick.clear();
+                        vec![Notice("NickAlreadyInUse".to_string()).send_self().action()]
+                    }
+                } else {
+                    vec![JoinLobby]
+                };
+                server.react(client_id, actions);
+            }
+        },
+        JoinLobby => {
+            server.clients[client_id].room_id = Some(server.lobby_id);
+
+            let mut lobby_nicks = Vec::new();
+            for (_, c) in server.clients.iter() {
+                if c.room_id.is_some() {
+                    lobby_nicks.push(c.nick.clone());
+                }
+            }
+            let joined_msg = LobbyJoined(lobby_nicks);
+
+            let everyone_msg = LobbyJoined(vec![server.clients[client_id].nick.clone()]);
+            let flags_msg = ClientFlags(
+                "+i".to_string(),
+                server.clients.iter()
+                    .filter(|(_, c)| c.room_id.is_some())
+                    .map(|(_, c)| c.nick.clone())
+                    .collect());
+            let server_msg = ServerMessage("\u{1f994} is watching".to_string());
+            let rooms_msg = Rooms(server.rooms.iter()
+                .filter(|(id, _)| *id != server.lobby_id)
+                .flat_map(|(_, r)|
+                    r.info(r.master_id.map(|id| &server.clients[id])))
+                .collect());
+            server.react(client_id, vec![
+                everyone_msg.send_all().but_self().action(),
+                joined_msg.send_self().action(),
+                flags_msg.send_self().action(),
+                server_msg.send_self().action(),
+                rooms_msg.send_self().action(),
+                ]);
+        },
+        AddRoom(name, password) => {
+            let room_id = server.add_room();;
+
+            let r = &mut server.rooms[room_id];
+            let c = &mut server.clients[client_id];
+            r.master_id = Some(c.id);
+            r.name = name;
+            r.password = password;
+            r.protocol_number = c.protocol_number;
+
+            let actions = vec![
+                RoomAdd(r.info(Some(&c))).send_all()
+                    .with_protocol(r.protocol_number).action(),
+                MoveToRoom(room_id)];
+
+            server.react(client_id, actions);
+        },
+        RemoveRoom(room_id) => {
+            let r = &mut server.rooms[room_id];
+            let actions = vec![RoomRemove(r.name.clone()).send_all()
+                .with_protocol(r.protocol_number).action()];
+            server.rooms.remove(room_id);
+            server.react(client_id, actions);
+        }
+        MoveToRoom(room_id) => {
+            let r = &mut server.rooms[room_id];
+            let c = &mut server.clients[client_id];
+            r.players_number += 1;
+            c.room_id = Some(room_id);
+
+            let is_master = r.master_id == Some(c.id);
+            c.set_is_master(is_master);
+            c.set_is_ready(is_master);
+            c.set_is_joined_mid_game(false);
+
+            if is_master {
+                r.ready_players_number += 1;
+            }
+
+            let mut v = vec![
+                RoomJoined(vec![c.nick.clone()]).send_all().in_room(room_id).action(),
+                ClientFlags("+i".to_string(), vec![c.nick.clone()]).send_all().action(),
+                SendRoomUpdate(None)];
+
+            if !r.greeting.is_empty() {
+                v.push(ChatMsg {nick: "[greeting]".to_string(), msg: r.greeting.clone()}
+                    .send_self().action());
+            }
+
+            if !c.is_master() {
+                let team_names: Vec<_>;
+                if let Some(ref mut info) = r.game_info {
+                    c.set_is_in_game(true);
+                    c.set_is_joined_mid_game(true);
+
+                    {
+                        let teams = info.client_teams(c.id);
+                        c.teams_in_game = teams.clone().count() as u8;
+                        c.clan = teams.clone().next().map(|t| t.color);
+                        team_names = 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;
+                        r.teams = info.teams_at_start.iter()
+                            .filter(|(_, t)| !team_names.contains(&t.name))
+                            .cloned().collect();
+                    }
+                } else {
+                    team_names = Vec::new();
+                }
+
+                v.push(SendRoomData{ to: client_id, teams: true, config: true, flags: true});
+
+                if let Some(ref info) = r.game_info {
+                    v.push(RunGame.send_self().action());
+                    v.push(ClientFlags("+g".to_string(), vec![c.nick.clone()])
+                        .send_all().in_room(r.id).action());
+                    v.push(ForwardEngineMessage(
+                        vec![to_engine_msg("e$spectate 1".bytes())])
+                        .send_self().action());
+                    v.push(ForwardEngineMessage(info.msg_log.clone())
+                        .send_self().action());
+
+                    for name in &team_names {
+                        v.push(ForwardEngineMessage(
+                            vec![to_engine_msg(once(b'G').chain(name.bytes()))])
+                            .send_all().in_room(r.id).action());
+                    }
+                    if info.is_paused {
+                        v.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
+                            .send_all().in_room(r.id).action())
+                    }
+                }
+            }
+            server.react(client_id, v);
+        }
+        SendRoomData {to, teams, config, flags} => {
+            let mut actions = Vec::new();
+            let room_id = server.clients[client_id].room_id;
+            if let Some(r) = room_id.and_then(|id| server.rooms.get(id)) {
+                if config {
+                    actions.push(ConfigEntry("FULLMAPCONFIG".to_string(), r.map_config())
+                        .send(to).action());
+                    for cfg in r.game_config() {
+                        actions.push(cfg.to_server_msg().send(to).action());
+                    }
+                }
+                if teams {
+                    let current_teams = match r.game_info {
+                        Some(ref info) => &info.teams_at_start,
+                        None => &r.teams
+                    };
+                    for (owner_id, team) in current_teams.iter() {
+                        actions.push(TeamAdd(HWRoom::team_info(&server.clients[*owner_id], &team))
+                            .send(to).action());
+                        actions.push(TeamColor(team.name.clone(), team.color)
+                            .send(to).action());
+                        actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
+                            .send(to).action());
+                    }
+                }
+                if flags {
+                    if let Some(id) = r.master_id {
+                        actions.push(ClientFlags("+h".to_string(), vec![server.clients[id].nick.clone()])
+                            .send(to).action());
+                    }
+                    let nicks: Vec<_> = server.clients.iter()
+                        .filter(|(_, c)| c.room_id == Some(r.id) && c.is_ready())
+                        .map(|(_, c)| c.nick.clone()).collect();
+                    if !nicks.is_empty() {
+                        actions.push(ClientFlags("+r".to_string(), nicks)
+                            .send(to).action());
+                    }
+                }
+            }
+            server.react(client_id, actions);
+        }
+        AddVote{vote, is_forced} => {
+            let mut actions = Vec::new();
+            if let Some(r) = server.room(client_id) {
+                let mut result = None;
+                if let Some(ref mut voting) = r.voting {
+                    if is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) {
+                        actions.push(server_chat("Your vote has been counted.".to_string())
+                            .send_self().action());
+                        voting.votes.push((client_id, vote));
+                        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 is_forced && vote || pro >= success_quota {
+                            result = Some(true);
+                        } else if is_forced && !vote || contra > voting.voters.len() - success_quota {
+                            result = Some(false);
+                        }
+                    } else {
+                        actions.push(server_chat("You already have voted.".to_string())
+                            .send_self().action());
+                    }
+                } else {
+                    actions.push(server_chat("There's no voting going on.".to_string())
+                        .send_self().action());
+                }
+
+                if let Some(res) = result {
+                    actions.push(server_chat("Voting closed.".to_string())
+                        .send_all().in_room(r.id).action());
+                    let voting = replace(&mut r.voting, None).unwrap();
+                    if res {
+                        actions.push(ApplyVoting(voting.kind, r.id));
+                    }
+                }
+            }
+
+            server.react(client_id, actions);
+        }
+        ApplyVoting(kind, room_id) => {
+            let mut actions = Vec::new();
+            let mut id = client_id;
+            match kind {
+                VoteType::Kick(nick) => {
+                    if let Some(c) = server.find_client(&nick) {
+                        if c.room_id == Some(room_id) {
+                            id = c.id;
+                            actions.push(Kicked.send_self().action());
+                            actions.push(MoveToLobby("kicked".to_string()));
+                        }
+                    }
+                },
+                VoteType::Map(None) => (),
+                VoteType::Map(Some(name)) => {
+                    if let Some(location) = server.rooms[room_id].load_config(&name) {
+                        actions.push(server_chat(location.to_string())
+                            .send_all().in_room(room_id).action());
+                        actions.push(SendRoomUpdate(None));
+                        for (_, c) in server.clients.iter() {
+                            if c.room_id == Some(room_id) {
+                               actions.push(SendRoomData{
+                                   to: c.id, teams: false,
+                                   config: true, flags: false})
+                            }
+                        }
+                    }
+                },
+                VoteType::Pause => {
+                    if let Some(ref mut info) = server.rooms[room_id].game_info {
+                        info.is_paused = !info.is_paused;
+                        actions.push(server_chat("Pause toggled.".to_string())
+                            .send_all().in_room(room_id).action());
+                        actions.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
+                            .send_all().in_room(room_id).action());
+                    }
+                },
+                VoteType::NewSeed => {
+                    let seed = thread_rng().gen_range(0, 1_000_000_000).to_string();
+                    let cfg = GameCfg::Seed(seed);
+                    actions.push(cfg.to_server_msg().send_all().in_room(room_id).action());
+                    server.rooms[room_id].set_config(cfg);
+                },
+                VoteType::HedgehogsPerTeam(number) => {
+                    let r = &mut server.rooms[room_id];
+                    let nicks = r.set_hedgehogs_number(number);
+                    actions.extend(nicks.into_iter().map(|n|
+                        HedgehogsNumber(n, number).send_all().in_room(room_id).action()
+                    ));
+                },
+            }
+            server.react(id, actions);
+        }
+        MoveToLobby(msg) => {
+            let mut actions = Vec::new();
+            let lobby_id = server.lobby_id;
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                r.players_number -= 1;
+                if c.is_ready() && r.ready_players_number > 0 {
+                    r.ready_players_number -= 1;
+                }
+                if c.is_master() && (r.players_number > 0 || r.is_fixed()) {
+                    actions.push(ChangeMaster(r.id, None));
+                }
+                actions.push(ClientFlags("-i".to_string(), vec![c.nick.clone()])
+                    .send_all().action());
+            }
+            server.react(client_id, actions);
+            actions = Vec::new();
+
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                c.room_id = Some(lobby_id);
+                if r.players_number == 0 && !r.is_fixed() {
+                    actions.push(RemoveRoom(r.id));
+                } else {
+                    actions.push(RemoveClientTeams);
+                    actions.push(RoomLeft(c.nick.clone(), msg)
+                        .send_all().in_room(r.id).but_self().action());
+                    actions.push(SendRoomUpdate(Some(r.name.clone())));
+                }
+            }
+            server.react(client_id, actions)
+        }
+        ChangeMaster(room_id, new_id) => {
+            let mut actions = Vec::new();
+            let room_client_ids = server.room_clients(room_id);
+            let new_id = if server.room(client_id).map(|r| r.is_fixed()).unwrap_or(false) {
+                new_id
+            } else {
+                new_id.or_else(||
+                    room_client_ids.iter().find(|id| **id != client_id).cloned())
+            };
+            let new_nick = new_id.map(|id| server.clients[id].nick.clone());
+
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                match r.master_id {
+                    Some(id) if id == c.id => {
+                        c.set_is_master(false);
+                        r.master_id = None;
+                        actions.push(ClientFlags("-h".to_string(), vec![c.nick.clone()])
+                            .send_all().in_room(r.id).action());
+                    }
+                    Some(_) => unreachable!(),
+                    None => {}
+                }
+                r.master_id = new_id;
+                if !r.is_fixed() && c.protocol_number < 42 {
+                    r.name.replace_range(.., new_nick.as_ref().map_or("[]", String::as_str));
+                }
+                r.set_join_restriction(false);
+                r.set_team_add_restriction(false);
+                let is_fixed = r.is_fixed();
+                r.set_unregistered_players_restriction(is_fixed);
+                if let Some(nick) = new_nick {
+                    actions.push(ClientFlags("+h".to_string(), vec![nick])
+                        .send_all().in_room(r.id).action());
+                }
+            }
+            if let Some(id) = new_id {
+                server.clients[id].set_is_master(true)
+            }
+            server.react(client_id, actions);
+        }
+        RemoveTeam(name) => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                r.remove_team(&name);
+                if let Some(ref mut info) = r.game_info {
+                    info.left_teams.push(name.clone());
+                }
+                actions.push(TeamRemove(name.clone()).send_all().in_room(r.id).action());
+                actions.push(SendRoomUpdate(None));
+                if r.game_info.is_some() && c.is_in_game() {
+                    actions.push(SendTeamRemovalMessage(name));
+                }
+            }
+            server.react(client_id, actions);
+        },
+        RemoveClientTeams => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let actions = r.client_teams(c.id).map(|t| RemoveTeam(t.name.clone())).collect();
+                server.react(client_id, actions);
+            }
+        }
+        SendRoomUpdate(old_name) => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let name = old_name.unwrap_or_else(|| r.name.clone());
+                let actions = vec![RoomUpdated(name, r.info(Some(&c)))
+                    .send_all().with_protocol(r.protocol_number).action()];
+                server.react(client_id, actions);
+            }
+        },
+        StartRoomGame(room_id) => {
+            let actions = {
+                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() {
+                    vec![Warn("The game can't be started with less than two clans!".to_string())]
+                } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number {
+                    vec![Warn("Not all players are ready".to_string())]
+                } else if room.game_info.is_some() {
+                    vec![Warn("The game is already in progress".to_string())]
+                } else {
+                    room.start_round();
+                    for id in room_clients {
+                        let c = &mut server.clients[id];
+                        c.set_is_in_game(false);
+                        c.team_indices = room.client_team_indices(c.id);
+                    }
+                    vec![RunGame.send_all().in_room(room.id).action(),
+                         SendRoomUpdate(None),
+                         ClientFlags("+g".to_string(), room_nicks)
+                             .send_all().in_room(room.id).action()]
+                }
+            };
+            server.react(client_id, actions);
+        }
+        SendTeamRemovalMessage(team_name) => {
+            let mut actions = Vec::new();
+            if let Some(r) = server.room(client_id) {
+                if let Some(ref mut info) = r.game_info {
+                    let msg = once(b'F').chain(team_name.bytes());
+                    actions.push(ForwardEngineMessage(vec![to_engine_msg(msg)]).
+                        send_all().in_room(r.id).but_self().action());
+                    info.teams_in_game -= 1;
+                    if info.teams_in_game == 0 {
+                        actions.push(FinishRoomGame(r.id));
+                    }
+                    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());
+                    actions.push(ForwardEngineMessage(vec![remove_msg])
+                        .send_all().in_room(r.id).but_self().action());
+                }
+            }
+            server.react(client_id, actions);
+        }
+        FinishRoomGame(room_id) => {
+            let mut actions = Vec::new();
+
+            let r = &mut server.rooms[room_id];
+            r.ready_players_number = 1;
+            actions.push(SendRoomUpdate(None));
+            actions.push(RoundFinished.send_all().in_room(r.id).action());
+
+            if let Some(info) = replace(&mut r.game_info, None) {
+                for (_, c) in server.clients.iter() {
+                    if c.room_id == Some(room_id) && c.is_joined_mid_game() {
+                        actions.push(SendRoomData{
+                            to: c.id, teams: false,
+                            config: true, flags: false});
+                        for name in &info.left_teams {
+                            actions.push(TeamRemove(name.clone())
+                                .send(c.id).action());
+                        }
+                    }
+                }
+            }
+
+            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 r.protocol_number < 38 {
+                    LegacyReady(false, nicks)
+                } else {
+                    ClientFlags("-r".to_string(), nicks)
+                };
+                actions.push(msg.send_all().in_room(room_id).action());
+            }
+            server.react(client_id, actions);
+        }
+        Warn(msg) => {
+            run_action(server, client_id, Warning(msg).send_self().action());
+        }
+        ProtocolError(msg) => {
+            run_action(server, client_id, Error(msg).send_self().action())
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/client.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,68 @@
+use super::coretypes::ClientId;
+use bitflags::*;
+
+bitflags!{
+    pub struct ClientFlags: u8 {
+        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 NONE = 0b0000_0000;
+        const DEFAULT = Self::NONE.bits;
+    }
+}
+
+pub struct HWClient {
+    pub id: ClientId,
+    pub room_id: Option<usize>,
+    pub nick: String,
+    pub web_password: String,
+    pub server_salt: String,
+    pub protocol_number: u16,
+    pub flags: ClientFlags,
+    pub teams_in_game: u8,
+    pub team_indices: Vec<u8>,
+    pub clan: Option<u8>
+}
+
+impl HWClient {
+    pub fn new(id: ClientId, salt: String) -> HWClient {
+        HWClient {
+            id,
+            room_id: None,
+            nick: String::new(),
+            web_password: String::new(),
+            server_salt: salt,
+            protocol_number: 0,
+            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 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) }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/core.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,158 @@
+use slab;
+use crate::utils;
+use super::{
+    io::HWServerIO,
+    client::HWClient, room::HWRoom, actions, handlers,
+    coretypes::{ClientId, RoomId},
+    actions::{Destination, PendingMessage}
+};
+use crate::protocol::messages::*;
+use rand::{RngCore, thread_rng};
+use base64::{encode};
+use log::*;
+
+type Slab<T> = slab::Slab<T>;
+
+pub struct HWServer {
+    pub clients: Slab<HWClient>,
+    pub rooms: Slab<HWRoom>,
+    pub lobby_id: RoomId,
+    pub output: Vec<(Vec<ClientId>, HWServerMessage)>,
+    pub removed_clients: Vec<ClientId>,
+    pub io: Box<dyn HWServerIO>
+}
+
+impl HWServer {
+    pub fn new(clients_limit: usize, rooms_limit: usize, io: Box<dyn HWServerIO>) -> HWServer {
+        let rooms = Slab::with_capacity(rooms_limit);
+        let clients = Slab::with_capacity(clients_limit);
+        let mut server = HWServer {
+            clients, rooms,
+            lobby_id: 0,
+            output: vec![],
+            removed_clients: vec![],
+            io
+        };
+        server.lobby_id = server.add_room();
+        server
+    }
+
+    pub fn add_client(&mut self) -> ClientId {
+        let key: ClientId;
+        {
+            let entry = self.clients.vacant_entry();
+            key = entry.key();
+            let mut salt = [0u8; 18];
+            thread_rng().fill_bytes(&mut salt);
+
+            let client = HWClient::new(entry.key(), encode(&salt));
+            entry.insert(client);
+        }
+        self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
+        key
+    }
+
+    pub fn client_lost(&mut self, client_id: ClientId) {
+        actions::run_action(self, client_id,
+                            actions::Action::ByeClient("Connection reset".to_string()));
+    }
+
+    pub fn add_room(&mut self) -> RoomId {
+        let entry = self.rooms.vacant_entry();
+        let key = entry.key();
+        let room = HWRoom::new(entry.key());
+        entry.insert(room);
+        key
+    }
+
+    pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) {
+        debug!("Handling message {:?} for client {}", msg, client_id);
+        if self.clients.contains(client_id) {
+            handlers::handle(self, client_id, msg);
+        }
+    }
+
+    fn get_recipients(&self, client_id: ClientId, destination: &Destination) -> Vec<ClientId> {
+        let mut ids = match *destination {
+            Destination::ToSelf => vec![client_id],
+            Destination::ToId(id) => vec![id],
+            Destination::ToAll {room_id: Some(id), ..} =>
+                self.room_clients(id),
+            Destination::ToAll {protocol: Some(proto), ..} =>
+                self.protocol_clients(proto),
+            Destination::ToAll {..} =>
+                self.clients.iter().map(|(id, _)| id).collect::<Vec<_>>()
+        };
+        if let Destination::ToAll {skip_self: true, ..} = destination {
+            if let Some(index) = ids.iter().position(|id| *id == client_id) {
+                ids.remove(index);
+            }
+        }
+        ids
+    }
+
+    pub fn send(&mut self, client_id: ClientId, destination: &Destination, message: HWServerMessage) {
+        let ids = self.get_recipients(client_id, &destination);
+        self.output.push((ids, message));
+    }
+
+    pub fn react(&mut self, client_id: ClientId, actions: Vec<actions::Action>) {
+        for action in actions {
+            actions::run_action(self, client_id, action);
+        }
+    }
+
+    pub fn lobby(&self) -> &HWRoom { &self.rooms[self.lobby_id] }
+
+    pub fn has_room(&self, name: &str) -> bool {
+        self.rooms.iter().any(|(_, r)| r.name == name)
+    }
+
+    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 select_clients<F>(&self, f: F) -> Vec<ClientId>
+        where F: Fn(&(usize, &HWClient)) -> bool {
+        self.clients.iter().filter(f)
+            .map(|(_, c)| c.id).collect()
+    }
+
+    pub fn room_clients(&self, room_id: RoomId) -> Vec<ClientId> {
+        self.select_clients(|(_, c)| c.room_id == Some(room_id))
+    }
+
+    pub fn protocol_clients(&self, protocol: u16) -> Vec<ClientId> {
+        self.select_clients(|(_, c)| c.protocol_number == protocol)
+    }
+
+    pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec<ClientId> {
+        let room_id = self.clients[self_id].room_id;
+        self.select_clients(|(id, c)| *id != self_id && c.room_id == room_id )
+    }
+
+    pub fn client_and_room(&mut self, client_id: ClientId) -> (&mut HWClient, Option<&mut HWRoom>) {
+        let c = &mut self.clients[client_id];
+        if let Some(room_id) = c.room_id {
+            (c, Some(&mut self.rooms[room_id]))
+        } else {
+            (c, None)
+        }
+    }
+
+    pub fn room(&mut self, client_id: ClientId) -> Option<&mut HWRoom> {
+        self.client_and_room(client_id).1
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/coretypes.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,72 @@
+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(u32),
+}
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum GameCfg {
+    FeatureSize(u32),
+    MapType(String),
+    MapGenerator(u32),
+    MazeSize(u32),
+    Seed(String),
+    Template(u32),
+
+    Ammo(String, Option<String>),
+    Scheme(String, Vec<String>),
+    Script(String),
+    Theme(String),
+    DrawnMap(String)
+}
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct TeamInfo {
+    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(PartialEq, Eq, Clone, Debug)]
+pub enum VoteType {
+    Kick(String),
+    Map(Option<String>),
+    Pause,
+    NewSeed,
+    HedgehogsPerTeam(u8)
+}
+
+#[derive(Clone, Debug)]
+pub struct Voting {
+    pub ttl: u32,
+    pub voters: Vec<ClientId>,
+    pub votes: Vec<(ClientId, bool)>,
+    pub kind: VoteType
+}
+
+impl Voting {
+    pub fn new(kind: VoteType, voters: Vec<ClientId>) -> Voting {
+        Voting {
+            kind, voters, ttl: 2,
+            votes: Vec::new()
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/handlers.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,44 @@
+use mio;
+use std::{io, io::Write};
+
+use super::{
+    core::HWServer,
+    actions::{Action, Action::*},
+    coretypes::ClientId
+};
+use crate::{
+    protocol::messages::{
+        HWProtocolMessage,
+        HWServerMessage::*
+    }
+};
+use log::*;
+
+mod loggingin;
+mod lobby;
+mod inroom;
+mod common;
+mod checker;
+
+pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
+    match message {
+        HWProtocolMessage::Ping =>
+            server.react(client_id, vec![Pong.send_self().action()]),
+        HWProtocolMessage::Quit(Some(msg)) =>
+            server.react(client_id, vec![ByeClient("User quit: ".to_string() + &msg)]),
+        HWProtocolMessage::Quit(None) =>
+            server.react(client_id, vec![ByeClient("User quit".to_string())]),
+        HWProtocolMessage::Malformed => warn!("Malformed/unknown message"),
+        HWProtocolMessage::Empty => warn!("Empty message"),
+        _ => {
+            match server.clients[client_id].room_id {
+                None =>
+                    loggingin::handle(server, client_id, message),
+                Some(id) if id == server.lobby_id =>
+                    lobby::handle(server, client_id, message),
+                Some(id) =>
+                    inroom::handle(server, client_id, id, message)
+            }
+        },
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/handlers/checker.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,18 @@
+use mio;
+use log::*;
+
+use crate::{
+    server::{
+        core::HWServer,
+        coretypes::ClientId,
+    },
+    protocol::messages::{
+        HWProtocolMessage
+    },
+};
+
+pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
+    match message {
+        _ => warn!("Unknown command"),
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/handlers/common.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,73 @@
+use crate::{
+    server::{actions::Action, core::HWServer},
+    protocol::messages::{
+        HWProtocolMessage::{self, Rnd}, HWServerMessage::{self, ChatMsg},
+    }
+};
+use rand::{self, Rng, thread_rng};
+
+pub fn rnd_reply(options: &[String]) -> HWServerMessage {
+    let mut rng = thread_rng();
+    let reply = if options.is_empty() {
+        (*rng.choose(&["heads", "tails"]).unwrap()).to_owned()
+    } else {
+        rng.choose(&options).unwrap().clone()
+    };
+
+    ChatMsg {
+        nick: "[random]".to_owned(),
+        msg: reply.clone(),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::protocol::messages::HWServerMessage::ChatMsg;
+    use crate::server::actions::{
+        Action::{self, Send}, PendingMessage,
+    };
+
+    fn reply2string(r: HWServerMessage) -> String {
+        match r {
+            ChatMsg { msg: p, .. } => String::from(p),
+            _ => panic!("expected a ChatMsg"),
+        }
+    }
+
+    fn run_handle_test(opts: Vec<String>) {
+        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;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/handlers/inroom.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,486 @@
+use mio;
+
+use crate::{
+    server::{
+        coretypes::{
+            ClientId, RoomId, Voting, VoteType, GameCfg,
+            MAX_HEDGEHOGS_PER_TEAM
+        },
+        core::HWServer,
+        room::{HWRoom, RoomFlags},
+        actions::{Action, Action::*}
+    },
+    protocol::messages::{
+        HWProtocolMessage,
+        HWServerMessage::*,
+        server_chat
+    },
+    utils::is_name_illegal
+};
+use std::{
+    mem::swap
+};
+use base64::{encode, decode};
+use super::common::rnd_reply;
+use log::*;
+
+#[derive(Clone)]
+struct ByMsg<'a> {
+    messages: &'a[u8]
+}
+
+impl <'a> Iterator for ByMsg<'a> {
+    type Item = &'a[u8];
+
+    fn next(&mut self) -> Option<<Self as Iterator>::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, room_id: RoomId, message: HWProtocolMessage) {
+    use crate::protocol::messages::HWProtocolMessage::*;
+    match message {
+        Part(None) => server.react(client_id, vec![
+            MoveToLobby("part".to_string())]),
+        Part(Some(msg)) => server.react(client_id, vec![
+            MoveToLobby(format!("part: {}", msg))]),
+        Chat(msg) => {
+            let actions = {
+                let c = &mut server.clients[client_id];
+                let chat_msg = ChatMsg {nick: c.nick.clone(), msg};
+                vec![chat_msg.send_all().in_room(room_id).but_self().action()]
+            };
+            server.react(client_id, actions);
+        },
+        Fix => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.is_admin() { r.set_is_fixed(true) }
+            }
+        }
+        Unfix => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.is_admin() { r.set_is_fixed(false) }
+            }
+        }
+        Greeting(text) => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.is_admin() || c.is_master() && !r.is_fixed() {
+                    r.greeting = text
+                }
+            }
+        }
+        RoomName(new_name) => {
+            let actions =
+                if is_name_illegal(&new_name) {
+                    vec![Warn("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())]
+                } else if server.rooms[room_id].is_fixed() {
+                    vec![Warn("Access denied.".to_string())]
+                } else if server.has_room(&new_name) {
+                    vec![Warn("A room with the same name already exists.".to_string())]
+                } else {
+                    let mut old_name = new_name.clone();
+                    swap(&mut server.rooms[room_id].name, &mut old_name);
+                    vec![SendRoomUpdate(Some(old_name))]
+                };
+            server.react(client_id, actions);
+        },
+        ToggleReady => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let flags = if c.is_ready() {
+                    r.ready_players_number -= 1;
+                    "-r"
+                } else {
+                    r.ready_players_number += 1;
+                    "+r"
+                };
+
+                let msg = if c.protocol_number < 38 {
+                    LegacyReady(c.is_ready(), vec![c.nick.clone()])
+                } else {
+                    ClientFlags(flags.to_string(), vec![c.nick.clone()])
+                };
+
+                let mut v = vec![msg.send_all().in_room(r.id).action()];
+
+                if r.is_fixed() && r.ready_players_number == r.players_number {
+                    v.push(StartRoomGame(r.id))
+                }
+
+                c.set_is_ready(!c.is_ready());
+                server.react(client_id, v);
+            }
+        }
+        AddTeam(info) => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if r.teams.len() >= r.team_limit as usize {
+                    actions.push(Warn("Too many teams!".to_string()))
+                } else if r.addable_hedgehogs() == 0 {
+                    actions.push(Warn("Too many hedgehogs!".to_string()))
+                } else if r.find_team(|t| t.name == info.name) != None {
+                    actions.push(Warn("There's already a team with same name in the list.".to_string()))
+                } else if r.game_info.is_some() {
+                    actions.push(Warn("Joining not possible: Round is in progress.".to_string()))
+                } else if r.is_team_add_restricted() {
+                    actions.push(Warn("This room currently does not allow adding new teams.".to_string()));
+                } else {
+                    let team = r.add_team(c.id, *info, c.protocol_number < 42);
+                    c.teams_in_game += 1;
+                    c.clan = Some(team.color);
+                    actions.push(TeamAccepted(team.name.clone())
+                        .send_self().action());
+                    actions.push(TeamAdd(HWRoom::team_info(&c, team))
+                        .send_all().in_room(room_id).but_self().action());
+                    actions.push(TeamColor(team.name.clone(), team.color)
+                        .send_all().in_room(room_id).action());
+                    actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
+                        .send_all().in_room(room_id).action());
+                    actions.push(SendRoomUpdate(None));
+                }
+            }
+            server.react(client_id, actions);
+        },
+        RemoveTeam(name) => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                match r.find_team_owner(&name) {
+                    None =>
+                        actions.push(Warn("Error: The team you tried to remove does not exist.".to_string())),
+                    Some((id, _)) if id != client_id =>
+                        actions.push(Warn("You can't remove a team you don't own.".to_string())),
+                    Some((_, name)) => {
+                        c.teams_in_game -= 1;
+                        c.clan = r.find_team_color(c.id);
+                        actions.push(Action::RemoveTeam(name.to_string()));
+                    }
+                }
+            };
+            server.react(client_id, actions);
+        },
+        SetHedgehogsNumber(team_name, number) => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let addable_hedgehogs = r.addable_hedgehogs();
+                let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
+                    if !c.is_master() {
+                        vec![ProtocolError("You're not the room master!".to_string())]
+                    } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM
+                           || number > addable_hedgehogs + team.hedgehogs_number {
+                        vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
+                            .send_self().action()]
+                    } else {
+                        team.hedgehogs_number = number;
+                        vec![HedgehogsNumber(team.name.clone(), number)
+                            .send_all().in_room(room_id).but_self().action()]
+                    }
+                } else {
+                    vec![(Warn("No such team.".to_string()))]
+                };
+                server.react(client_id, actions);
+            }
+        },
+        SetTeamColor(team_name, color) => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let mut owner_id = None;
+                let actions = if let Some((owner, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
+                    if !c.is_master() {
+                        vec![ProtocolError("You're not the room master!".to_string())]
+                    } else if false  {
+                        Vec::new()
+                    } else {
+                        owner_id = Some(owner);
+                        team.color = color;
+                        vec![TeamColor(team.name.clone(), color)
+                            .send_all().in_room(room_id).but_self().action()]
+                    }
+                } else {
+                    vec![(Warn("No such team.".to_string()))]
+                };
+
+                if let Some(id) = owner_id {
+                    server.clients[id].clan = Some(color);
+                }
+
+                server.react(client_id, actions);
+            };
+        },
+        Cfg(cfg) => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                let actions = if r.is_fixed() {
+                    vec![Warn("Access denied.".to_string())]
+                } else if !c.is_master() {
+                    vec![ProtocolError("You're not the room master!".to_string())]
+                } else {
+                    let cfg = match cfg {
+                        GameCfg::Scheme(name, mut values) => {
+                            if c.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
+                    };
+
+                    let v = vec![cfg.to_server_msg()
+                        .send_all().in_room(r.id).but_self().action()];
+                    r.set_config(cfg);
+                    v
+                };
+                server.react(client_id, actions);
+            }
+        }
+        Save(name, location) => {
+            let actions = vec![server_chat(format!("Room config saved as {}", name))
+                .send_all().in_room(room_id).action()];
+            server.rooms[room_id].save_config(name, location);
+            server.react(client_id, actions);
+        }
+        SaveRoom(filename) => {
+            if server.clients[client_id].is_admin() {
+                let actions = match server.rooms[room_id].get_saves() {
+                    Ok(text) => match server.io.write_file(&filename, &text) {
+                        Ok(_) => vec![server_chat("Room configs saved successfully.".to_string())
+                            .send_self().action()],
+                        Err(e) => {
+                            warn!("Error while writing the config file \"{}\": {}", filename, e);
+                            vec![Warn("Unable to save the room configs.".to_string())]
+                        }
+                    }
+                    Err(e) => {
+                        warn!("Error while serializing the room configs: {}", e);
+                        vec![Warn("Unable to serialize the room configs.".to_string())]
+                    }
+                };
+                server.react(client_id, actions);
+            }
+        }
+        LoadRoom(filename) => {
+            if server.clients[client_id].is_admin() {
+                let actions = match server.io.read_file(&filename) {
+                    Ok(text) => match server.rooms[room_id].set_saves(&text) {
+                        Ok(_) => vec![server_chat("Room configs loaded successfully.".to_string())
+                            .send_self().action()],
+                        Err(e) => {
+                            warn!("Error while deserializing the room configs: {}", e);
+                            vec![Warn("Unable to deserialize the room configs.".to_string())]
+                        }
+                    }
+                    Err(e) => {
+                        warn!("Error while reading the config file \"{}\": {}", filename, e);
+                        vec![Warn("Unable to load the room configs.".to_string())]
+                    }
+                };
+                server.react(client_id, actions);
+            }
+        }
+        Delete(name) => {
+            let actions = if !server.rooms[room_id].delete_config(&name) {
+                vec![Warn(format!("Save doesn't exist: {}", name))]
+            } else {
+                vec![server_chat(format!("Room config {} has been deleted", name))
+                    .send_all().in_room(room_id).action()]
+            };
+            server.react(client_id, actions);
+        }
+        CallVote(None) => {
+            server.react(client_id, vec![
+                server_chat("Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs <number>".to_string())
+                    .send_self().action()])
+        }
+        CallVote(Some(kind)) => {
+            let is_in_game = server.rooms[room_id].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 server.rooms[room_id].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));
+                    server.rooms[room_id].voting = Some(voting);
+                    server.react(client_id, vec![
+                        server_chat(msg).send_all().in_room(room_id).action(),
+                        AddVote{ vote: true, is_forced: false}]);
+                }
+                Some(msg) => {
+                    server.react(client_id, vec![
+                        server_chat(msg).send_self().action()])
+                }
+            }
+        }
+        Vote(vote) => {
+            server.react(client_id, vec![AddVote{ vote, is_forced: false }]);
+        }
+        ForceVote(vote) => {
+            let is_forced = server.clients[client_id].is_admin();
+            server.react(client_id, vec![AddVote{ vote, is_forced }]);
+        }
+        ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly  => {
+            if server.clients[client_id].is_master() {
+                server.rooms[room_id].flags.toggle(room_message_flag(&message));
+            }
+            server.react(client_id, vec![SendRoomUpdate(None)]);
+        }
+        StartGame => {
+            server.react(client_id, vec![StartRoomGame(room_id)]);
+        }
+        EngineMessage(em) => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.teams_in_game > 0 {
+                    let decoding = decode(&em[..]).unwrap();
+                    let messages = by_msg(&decoding);
+                    let valid = messages.filter(|m| is_msg_valid(m, &c.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::<Vec<_>>());
+                    if !em_response.is_empty() {
+                        actions.push(ForwardEngineMessage(vec![em_response])
+                            .send_all().in_room(r.id).but_self().action());
+                    }
+                    let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::<Vec<_>>());
+                    if let Some(ref mut info) = r.game_info {
+                        if !em_log.is_empty() {
+                            info.msg_log.push(em_log);
+                        }
+                        if let Some(msg) = sync_msg {
+                            info.sync_msg = msg;
+                        }
+                    }
+                }
+            }
+            server.react(client_id, actions)
+        }
+        RoundFinished => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.is_in_game() {
+                    c.set_is_in_game(false);
+                    actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]).
+                        send_all().in_room(r.id).action());
+                    if r.game_info.is_some() {
+                        for team in r.client_teams(c.id) {
+                            actions.push(SendTeamRemovalMessage(team.name.clone()));
+                        }
+                    }
+                }
+            }
+            server.react(client_id, actions)
+        },
+        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(" ")
+            };
+            server.react(client_id, vec![
+                chat_msg.send_all().in_room(room_id).action(),
+                result.send_all().in_room(room_id).action()])
+        },
+        _ => warn!("Unimplemented!")
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/handlers/lobby.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,72 @@
+use mio;
+
+use crate::{
+    server::{
+        core::HWServer,
+        coretypes::ClientId,
+        actions::{Action, Action::*}
+    },
+    protocol::messages::{
+        HWProtocolMessage,
+        HWServerMessage::*
+    },
+    utils::is_name_illegal
+};
+use super::common::rnd_reply;
+use log::*;
+
+pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
+    use crate::protocol::messages::HWProtocolMessage::*;
+    match message {
+        CreateRoom(name, password) => {
+            let actions =
+                if is_name_illegal(&name) {
+                    vec![Warn("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())]
+                } else if server.has_room(&name) {
+                    vec![Warn("A room with the same name already exists.".to_string())]
+                } else {
+                    let flags_msg = ClientFlags(
+                        "+hr".to_string(),
+                        vec![server.clients[client_id].nick.clone()]);
+                    vec![AddRoom(name, password),
+                         flags_msg.send_self().action()]
+                };
+            server.react(client_id, actions);
+        },
+        Chat(msg) => {
+            let actions = vec![ChatMsg {nick: server.clients[client_id].nick.clone(), msg}
+                .send_all().in_room(server.lobby_id).but_self().action()];
+            server.react(client_id, actions);
+        },
+        JoinRoom(name, _password) => {
+            let room = server.rooms.iter().find(|(_, r)| r.name == name);
+            let room_id = room.map(|(_, r)| r.id);
+            let nicks = server.clients.iter()
+                .filter(|(_, c)| c.room_id == room_id)
+                .map(|(_, c)| c.nick.clone())
+                .collect();
+            let c = &mut server.clients[client_id];
+
+            let actions = if let Some((_, r)) = room {
+                if c.protocol_number != r.protocol_number {
+                    vec![Warn("Room version incompatible to your Hedgewars version!".to_string())]
+                } else if r.is_join_restricted() {
+                    vec![Warn("Access denied. This room currently doesn't allow joining.".to_string())]
+                } else if r.players_number == u8::max_value() {
+                    vec![Warn("This room is already full".to_string())]
+                } else {
+                    vec![MoveToRoom(r.id),
+                         RoomJoined(nicks).send_self().action()]
+                }
+            } else {
+                vec![Warn("No such room.".to_string())]
+            };
+            server.react(client_id, actions);
+        },
+        Rnd(v) => {
+            server.react(client_id, vec![rnd_reply(&v).send_self().action()]);
+        },
+        List => warn!("Deprecated LIST message received"),
+        _ => warn!("Incorrect command in lobby state"),
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/handlers/loggingin.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,99 @@
+use mio;
+
+use crate::{
+    server::{
+        client::HWClient,
+        core::HWServer,
+        coretypes::ClientId,
+        actions::{Action, Action::*}
+    },
+    protocol::messages::{
+        HWProtocolMessage, HWServerMessage::*
+    },
+    utils::is_name_illegal
+};
+#[cfg(feature = "official-server")]
+use openssl::sha::sha1;
+use std::fmt::{Formatter, LowerHex};
+use log::*;
+
+#[derive(PartialEq)]
+struct Sha1Digest([u8; 20]);
+
+impl LowerHex for Sha1Digest {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
+        for byte in &self.0 {
+            write!(f, "{:02x}", byte)?;
+        }
+        Ok(())
+    }
+}
+
+#[cfg(feature = "official-server")]
+fn get_hash(client: &HWClient, salt1: &str, salt2: &str) -> Sha1Digest {
+    let s = format!("{}{}{}{}{}", salt1, salt2,
+                    client.web_password, client.protocol_number, "!hedgewars");
+    Sha1Digest(sha1(s.as_bytes()))
+}
+
+pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
+    match message {
+        HWProtocolMessage::Nick(nick) => {
+            let client = &mut server.clients[client_id];
+            debug!("{} {}", nick, is_name_illegal(&nick));
+            let actions = if client.room_id != None {
+                unreachable!()
+            }
+            else if !client.nick.is_empty() {
+                vec![ProtocolError("Nickname already provided.".to_string())]
+            }
+            else if is_name_illegal(&nick) {
+                vec![ByeClient("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())]
+            }
+            else {
+                client.nick = nick.clone();
+                vec![Nick(nick).send_self().action(),
+                     CheckRegistered]
+            };
+
+            server.react(client_id, actions);
+        }
+        HWProtocolMessage::Proto(proto) => {
+            let client = &mut server.clients[client_id];
+            let actions = if client.protocol_number != 0 {
+                vec![ProtocolError("Protocol already known.".to_string())]
+            }
+            else if proto == 0 {
+                vec![ProtocolError("Bad number.".to_string())]
+            }
+            else {
+                client.protocol_number = proto;
+                vec![Proto(proto).send_self().action(),
+                     CheckRegistered]
+            };
+            server.react(client_id, actions);
+        }
+        #[cfg(feature = "official-server")]
+        HWProtocolMessage::Password(hash, salt) => {
+            let c = &server.clients[client_id];
+
+            let client_hash = get_hash(c, &salt, &c.server_salt);
+            let server_hash = get_hash(c, &c.server_salt, &salt);
+            let actions = if client_hash == server_hash {
+                vec![ServerAuth(format!("{:x}", server_hash)).send_self().action(),
+                     JoinLobby]
+            } else {
+                vec![ByeClient("Authentication failed".to_string())]
+            };
+            server.react(client_id, actions);
+        }
+        #[cfg(feature = "official-server")]
+        HWProtocolMessage::Checker(protocol, nick, password) => {
+            let c = &mut server.clients[client_id];
+            c.nick = nick;
+            c.web_password = password;
+            c.set_is_checker(true);
+        }
+        _ => warn!("Incorrect command in logging-in state"),
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/io.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,49 @@
+use std::{
+    fs::{File, OpenOptions},
+    io::{Read, Write, Result, Error, ErrorKind}
+};
+
+pub trait HWServerIO {
+    fn write_file(&mut self, name: &str, content: &str) -> Result<()>;
+    fn read_file(&mut self, name: &str) -> Result<String>;
+}
+
+pub struct EmptyServerIO {}
+
+impl EmptyServerIO {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl HWServerIO for EmptyServerIO {
+    fn write_file(&mut self, _name: &str, _content: &str) -> Result<()> {
+        Ok(())
+    }
+
+    fn read_file(&mut self, _name: &str) -> Result<String> {
+        Ok("".to_string())
+    }
+}
+
+pub struct FileServerIO {}
+
+impl FileServerIO {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl HWServerIO for FileServerIO {
+    fn write_file(&mut self, name: &str, content: &str) -> Result<()> {
+        let mut writer = OpenOptions::new().create(true).write(true).open(name)?;
+        writer.write_all(content.as_bytes())
+    }
+
+    fn read_file(&mut self, name: &str) -> Result<String> {
+        let mut reader = File::open(name)?;
+        let mut result = String::new();
+        reader.read_to_string(&mut result)?;
+        Ok(result)
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/network.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,444 @@
+extern crate slab;
+
+use std::{
+    io, io::{Error, ErrorKind, Read, Write},
+    net::{SocketAddr, IpAddr, Ipv4Addr},
+    collections::HashSet,
+    mem::{swap, replace}
+};
+
+use mio::{
+    net::{TcpStream, TcpListener},
+    Poll, PollOpt, Ready, Token
+};
+use netbuf;
+use slab::Slab;
+use log::*;
+
+use crate::{
+    utils,
+    protocol::{ProtocolDecoder, messages::*}
+};
+use super::{
+    io::FileServerIO,
+    core::{HWServer},
+    coretypes::ClientId
+};
+#[cfg(feature = "tls-connections")]
+use openssl::{
+    ssl::{
+        SslMethod, SslContext, Ssl, SslContextBuilder,
+        SslVerifyMode, SslFiletype, SslOptions,
+        SslStreamBuilder, HandshakeError, MidHandshakeSslStream, SslStream
+    },
+    error::ErrorStack
+};
+
+const MAX_BYTES_PER_READ: usize = 2048;
+
+#[derive(Hash, Eq, PartialEq, Copy, Clone)]
+pub enum NetworkClientState {
+    Idle,
+    NeedsWrite,
+    NeedsRead,
+    Closed,
+}
+
+type NetworkResult<T> = io::Result<(T, NetworkClientState)>;
+
+#[cfg(not(feature = "tls-connections"))]
+pub enum ClientSocket {
+    Plain(TcpStream)
+}
+
+#[cfg(feature = "tls-connections")]
+pub enum ClientSocket {
+    SslHandshake(Option<MidHandshakeSslStream<TcpStream>>),
+    SslStream(SslStream<TcpStream>)
+}
+
+impl ClientSocket {
+    fn inner(&self) -> &TcpStream {
+        #[cfg(not(feature = "tls-connections"))]
+        match self {
+            ClientSocket::Plain(stream) => stream,
+        }
+
+        #[cfg(feature = "tls-connections")]
+        match self {
+            ClientSocket::SslHandshake(Some(builder)) => builder.get_ref(),
+            ClientSocket::SslHandshake(None) => unreachable!(),
+            ClientSocket::SslStream(ssl_stream) => ssl_stream.get_ref()
+        }
+    }
+}
+
+pub struct NetworkClient {
+    id: ClientId,
+    socket: ClientSocket,
+    peer_addr: SocketAddr,
+    decoder: ProtocolDecoder,
+    buf_out: netbuf::Buf
+}
+
+impl NetworkClient {
+    pub fn new(id: ClientId, socket: ClientSocket, peer_addr: SocketAddr) -> NetworkClient {
+        NetworkClient {
+            id, socket, peer_addr,
+            decoder: ProtocolDecoder::new(),
+            buf_out: netbuf::Buf::new()
+        }
+    }
+
+    #[cfg(feature = "tls-connections")]
+    fn handshake_impl(&mut self, handshake: MidHandshakeSslStream<TcpStream>) -> io::Result<NetworkClientState> {
+        match handshake.handshake() {
+            Ok(stream) => {
+                self.socket = ClientSocket::SslStream(stream);
+                debug!("TLS handshake with {} ({}) completed", self.id, self.peer_addr);
+                Ok(NetworkClientState::Idle)
+            }
+            Err(HandshakeError::WouldBlock(new_handshake)) => {
+                self.socket = ClientSocket::SslHandshake(Some(new_handshake));
+                Ok(NetworkClientState::Idle)
+            }
+            Err(HandshakeError::Failure(new_handshake)) => {
+                self.socket = ClientSocket::SslHandshake(Some(new_handshake));
+                debug!("TLS handshake with {} ({}) failed", self.id, self.peer_addr);
+                Err(Error::new(ErrorKind::Other, "Connection failure"))
+            }
+            Err(HandshakeError::SetupFailure(_)) => unreachable!()
+        }
+    }
+
+    fn read_impl<R: Read>(decoder: &mut ProtocolDecoder, source: &mut R,
+                          id: ClientId, addr: &SocketAddr) -> NetworkResult<Vec<HWProtocolMessage>> {
+        let mut bytes_read = 0;
+        let result = loop {
+            match decoder.read_from(source) {
+                Ok(bytes) => {
+                    debug!("Client {}: read {} bytes", id, bytes);
+                    bytes_read += bytes;
+                    if bytes == 0 {
+                        let result = if bytes_read == 0 {
+                            info!("EOF for client {} ({})", id, addr);
+                            (Vec::new(), NetworkClientState::Closed)
+                        } else {
+                            (decoder.extract_messages(), NetworkClientState::NeedsRead)
+                        };
+                        break Ok(result);
+                    }
+                    else if bytes_read >= MAX_BYTES_PER_READ {
+                        break Ok((decoder.extract_messages(), NetworkClientState::NeedsRead))
+                    }
+                }
+                Err(ref error) if error.kind() == ErrorKind::WouldBlock => {
+                    let messages =  if bytes_read == 0 {
+                        Vec::new()
+                    } else {
+                        decoder.extract_messages()
+                    };
+                    break Ok((messages, NetworkClientState::Idle));
+                }
+                Err(error) =>
+                    break Err(error)
+            }
+        };
+        decoder.sweep();
+        result
+    }
+
+    pub fn read(&mut self) -> NetworkResult<Vec<HWProtocolMessage>> {
+        #[cfg(not(feature = "tls-connections"))]
+        match self.socket {
+            ClientSocket::Plain(ref mut stream) =>
+                NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr),
+        }
+
+        #[cfg(feature = "tls-connections")]
+        match self.socket {
+            ClientSocket::SslHandshake(ref mut handshake_opt) => {
+                let handshake = std::mem::replace(handshake_opt, None).unwrap();
+                Ok((Vec::new(), self.handshake_impl(handshake)?))
+            },
+            ClientSocket::SslStream(ref mut stream) =>
+                NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr)
+        }
+    }
+
+    fn write_impl<W: Write>(buf_out: &mut netbuf::Buf, destination: &mut W) -> NetworkResult<()> {
+        let result = loop {
+            match buf_out.write_to(destination) {
+                Ok(bytes) if buf_out.is_empty() || bytes == 0 =>
+                    break Ok(((), NetworkClientState::Idle)),
+                Ok(_) => (),
+                Err(ref error) if error.kind() == ErrorKind::Interrupted
+                    || error.kind() == ErrorKind::WouldBlock => {
+                    break Ok(((), NetworkClientState::NeedsWrite));
+                },
+                Err(error) =>
+                    break Err(error)
+            }
+        };
+        result
+    }
+
+    pub fn write(&mut self) -> NetworkResult<()> {
+        let result = {
+            #[cfg(not(feature = "tls-connections"))]
+            match self.socket {
+                ClientSocket::Plain(ref mut stream) =>
+                    NetworkClient::write_impl(&mut self.buf_out, stream)
+            }
+
+            #[cfg(feature = "tls-connections")] {
+                match self.socket {
+                    ClientSocket::SslHandshake(ref mut handshake_opt) => {
+                        let handshake = std::mem::replace(handshake_opt, None).unwrap();
+                        Ok(((), self.handshake_impl(handshake)?))
+                    }
+                    ClientSocket::SslStream(ref mut stream) =>
+                        NetworkClient::write_impl(&mut self.buf_out, stream)
+                }
+            }
+        };
+
+        self.socket.inner().flush()?;
+        result
+    }
+
+    pub fn send_raw_msg(&mut self, msg: &[u8]) {
+        self.buf_out.write_all(msg).unwrap();
+    }
+
+    pub fn send_string(&mut self, msg: &str) {
+        self.send_raw_msg(&msg.as_bytes());
+    }
+
+    pub fn send_msg(&mut self, msg: &HWServerMessage) {
+        self.send_string(&msg.to_raw_protocol());
+    }
+}
+
+#[cfg(feature = "tls-connections")]
+struct ServerSsl {
+    context: SslContext
+}
+
+pub struct NetworkLayer {
+    listener: TcpListener,
+    server: HWServer,
+    clients: Slab<NetworkClient>,
+    pending: HashSet<(ClientId, NetworkClientState)>,
+    pending_cache: Vec<(ClientId, NetworkClientState)>,
+    #[cfg(feature = "tls-connections")]
+    ssl: ServerSsl
+}
+
+impl NetworkLayer {
+    pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer {
+        let server = HWServer::new(clients_limit, rooms_limit, Box::new(FileServerIO::new()));
+        let clients = Slab::with_capacity(clients_limit);
+        let pending = HashSet::with_capacity(2 * clients_limit);
+        let pending_cache = Vec::with_capacity(2 * clients_limit);
+
+        NetworkLayer {
+            listener, server, clients, pending, pending_cache,
+            #[cfg(feature = "tls-connections")]
+            ssl: NetworkLayer::create_ssl_context()
+        }
+    }
+
+    #[cfg(feature = "tls-connections")]
+    fn create_ssl_context() -> ServerSsl {
+        let mut builder = SslContextBuilder::new(SslMethod::tls()).unwrap();
+        builder.set_verify(SslVerifyMode::NONE);
+        builder.set_read_ahead(true);
+        builder.set_certificate_file("ssl/cert.pem", SslFiletype::PEM).unwrap();
+        builder.set_private_key_file("ssl/key.pem", SslFiletype::PEM).unwrap();
+        builder.set_options(SslOptions::NO_COMPRESSION);
+        builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap();
+        ServerSsl { context: builder.build() }
+    }
+
+    pub fn register_server(&self, poll: &Poll) -> io::Result<()> {
+        poll.register(&self.listener, utils::SERVER, Ready::readable(),
+                      PollOpt::edge())
+    }
+
+    fn deregister_client(&mut self, poll: &Poll, id: ClientId) {
+        let mut client_exists = false;
+        if let Some(ref client) = self.clients.get(id) {
+            poll.deregister(client.socket.inner())
+                .expect("could not deregister socket");
+            info!("client {} ({}) removed", client.id, client.peer_addr);
+            client_exists = true;
+        }
+        if client_exists {
+            self.clients.remove(id);
+        }
+    }
+
+    fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: ClientSocket, addr: SocketAddr) {
+        poll.register(client_socket.inner(), Token(id),
+                      Ready::readable() | Ready::writable(),
+                      PollOpt::edge())
+            .expect("could not register socket with event loop");
+
+        let entry = self.clients.vacant_entry();
+        let client = NetworkClient::new(id, client_socket, addr);
+        info!("client {} ({}) added", client.id, client.peer_addr);
+        entry.insert(client);
+    }
+
+    fn flush_server_messages(&mut self) {
+        debug!("{} pending server messages", self.server.output.len());
+        for (clients, message) in self.server.output.drain(..) {
+            debug!("Message {:?} to {:?}", message, clients);
+            let msg_string = message.to_raw_protocol();
+            for client_id in clients {
+                if let Some(client) = self.clients.get_mut(client_id) {
+                    client.send_string(&msg_string);
+                    self.pending.insert((client_id, NetworkClientState::NeedsWrite));
+                }
+            }
+        }
+    }
+
+    fn create_client_socket(&self, socket: TcpStream) -> io::Result<ClientSocket> {
+        #[cfg(not(feature = "tls-connections"))] {
+            Ok(ClientSocket::Plain(socket))
+        }
+
+        #[cfg(feature = "tls-connections")] {
+            let ssl = Ssl::new(&self.ssl.context).unwrap();
+            let mut builder = SslStreamBuilder::new(ssl, socket);
+            builder.set_accept_state();
+            match builder.handshake() {
+                Ok(stream) =>
+                    Ok(ClientSocket::SslStream(stream)),
+                Err(HandshakeError::WouldBlock(stream)) =>
+                    Ok(ClientSocket::SslHandshake(Some(stream))),
+                Err(e) => {
+                    debug!("OpenSSL handshake failed: {}", e);
+                    Err(Error::new(ErrorKind::Other, "Connection failure"))
+                }
+            }
+        }
+    }
+
+    pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> {
+        let (client_socket, addr) = self.listener.accept()?;
+        info!("Connected: {}", addr);
+
+        let client_id = self.server.add_client();
+        self.register_client(poll, client_id, self.create_client_socket(client_socket)?, addr);
+        self.flush_server_messages();
+
+        Ok(())
+    }
+
+    fn operation_failed(&mut self, poll: &Poll, client_id: ClientId, error: &Error, msg: &str) -> io::Result<()> {
+        let addr = if let Some(ref mut client) = self.clients.get_mut(client_id) {
+            client.peer_addr
+        } else {
+            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
+        };
+        debug!("{}({}): {}", msg, addr, error);
+        self.client_error(poll, client_id)
+    }
+
+    pub fn client_readable(&mut self, poll: &Poll,
+                           client_id: ClientId) -> io::Result<()> {
+        let messages =
+            if let Some(ref mut client) = self.clients.get_mut(client_id) {
+                client.read()
+            } else {
+                warn!("invalid readable client: {}", client_id);
+                Ok((Vec::new(), NetworkClientState::Idle))
+            };
+
+        match messages {
+            Ok((messages, state)) => {
+                for message in messages {
+                    self.server.handle_msg(client_id, message);
+                }
+                match state {
+                    NetworkClientState::NeedsRead => {
+                        self.pending.insert((client_id, state));
+                    },
+                    NetworkClientState::Closed =>
+                        self.client_error(&poll, client_id)?,
+                    _ => {}
+                };
+            }
+            Err(e) => self.operation_failed(
+                poll, client_id, &e,
+                "Error while reading from client socket")?
+        }
+
+        self.flush_server_messages();
+
+        if !self.server.removed_clients.is_empty() {
+            let ids: Vec<_> = self.server.removed_clients.drain(..).collect();
+            for client_id in ids {
+                self.deregister_client(poll, client_id);
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn client_writable(&mut self, poll: &Poll,
+                           client_id: ClientId) -> io::Result<()> {
+        let result =
+            if let Some(ref mut client) = self.clients.get_mut(client_id) {
+                client.write()
+            } else {
+                warn!("invalid writable client: {}", client_id);
+                Ok(((), NetworkClientState::Idle))
+            };
+
+        match result {
+            Ok(((), state)) if state == NetworkClientState::NeedsWrite => {
+                self.pending.insert((client_id, state));
+            },
+            Ok(_) => {}
+            Err(e) => self.operation_failed(
+                poll, client_id, &e,
+                "Error while writing to client socket")?
+        }
+
+        Ok(())
+    }
+
+    pub fn client_error(&mut self, poll: &Poll,
+                        client_id: ClientId) -> io::Result<()> {
+        self.deregister_client(poll, client_id);
+        self.server.client_lost(client_id);
+
+        Ok(())
+    }
+
+    pub fn has_pending_operations(&self) -> bool {
+        !self.pending.is_empty()
+    }
+
+    pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> {
+        if self.has_pending_operations() {
+            let mut cache = replace(&mut self.pending_cache, Vec::new());
+            cache.extend(self.pending.drain());
+            for (id, state) in cache.drain(..) {
+                match state {
+                    NetworkClientState::NeedsRead =>
+                        self.client_readable(poll, id)?,
+                    NetworkClientState::NeedsWrite =>
+                        self.client_writable(poll, id)?,
+                    _ => {}
+                }
+            }
+            swap(&mut cache, &mut self.pending_cache);
+        }
+        Ok(())
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/server/room.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,391 @@
+use std::{
+    iter, collections::HashMap
+};
+use crate::server::{
+    coretypes::{
+        ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting,
+        MAX_HEDGEHOGS_PER_TEAM
+    },
+    client::{HWClient}
+};
+use bitflags::*;
+use serde::{Serialize, Deserialize};
+use serde_derive::{Serialize, Deserialize};
+use serde_yaml;
+
+const MAX_TEAMS_IN_ROOM: u8 = 8;
+const MAX_HEDGEHOGS_IN_ROOM: u8 =
+    MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM;
+
+#[derive(Clone, Serialize, Deserialize)]
+struct Ammo {
+    name: String,
+    settings: Option<String>
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+struct Scheme {
+    name: String,
+    settings: Vec<String>
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+struct RoomConfig {
+    feature_size: u32,
+    map_type: String,
+    map_generator: u32,
+    maze_size: u32,
+    seed: String,
+    template: u32,
+
+    ammo: Ammo,
+    scheme: Scheme,
+    script: String,
+    theme: String,
+    drawn_map: Option<String>
+}
+
+impl RoomConfig {
+    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
+        }
+    }
+}
+
+fn client_teams_impl(teams: &[(ClientId, TeamInfo)], client_id: ClientId)
+    -> impl Iterator<Item = &TeamInfo> + Clone
+{
+    teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
+}
+
+fn map_config_from(c: &RoomConfig) -> Vec<String> {
+    vec![c.feature_size.to_string(), c.map_type.to_string(),
+         c.map_generator.to_string(), c.maze_size.to_string(),
+         c.seed.to_string(), c.template.to_string()]
+}
+
+fn game_config_from(c: &RoomConfig) -> Vec<GameCfg> {
+    use crate::server::coretypes::GameCfg::*;
+    let mut v = vec![
+        Ammo(c.ammo.name.to_string(), c.ammo.settings.clone()),
+        Scheme(c.scheme.name.to_string(), c.scheme.settings.clone()),
+        Script(c.script.to_string()),
+        Theme(c.theme.to_string())];
+    if let Some(ref m) = c.drawn_map {
+        v.push(DrawnMap(m.to_string()))
+    }
+    v
+}
+
+pub struct GameInfo {
+    pub teams_in_game: u8,
+    pub teams_at_start: Vec<(ClientId, TeamInfo)>,
+    pub left_teams: Vec<String>,
+    pub msg_log: Vec<String>,
+    pub sync_msg: Option<String>,
+    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<Item = &TeamInfo> + 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<ClientId>,
+    pub name: String,
+    pub password: Option<String>,
+    pub greeting: String,
+    pub protocol_number: u16,
+    pub flags: RoomFlags,
+
+    pub players_number: u8,
+    pub default_hedgehog_number: u8,
+    pub team_limit: u8,
+    pub ready_players_number: u8,
+    pub teams: Vec<(ClientId, TeamInfo)>,
+    config: RoomConfig,
+    pub voting: Option<Voting>,
+    pub saves: HashMap<String, RoomSave>,
+    pub game_info: Option<GameInfo>
+}
+
+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,
+            team_limit: 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<String> {
+        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<F>(&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<F>(&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<Item = &TeamInfo> {
+        client_teams_impl(&self.teams, client_id)
+    }
+
+    pub fn client_team_indices(&self, client_id: ClientId) -> Vec<u8> {
+        self.teams.iter().enumerate()
+            .filter(move |(_, (id, _))| *id == client_id)
+            .map(|(i, _)| i as u8).collect()
+    }
+
+    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<u8> {
+        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) {
+        let c = &mut self.config;
+        match cfg {
+            FeatureSize(s) => c.feature_size = s,
+            MapType(t) => c.map_type = t,
+            MapGenerator(g) => c.map_generator = g,
+            MazeSize(s) => c.maze_size = s,
+            Seed(s) => c.seed = s,
+            Template(t) => c.template = t,
+
+            Ammo(n, s) => c.ammo = Ammo {name: n, settings: s},
+            Scheme(n, s) => c.scheme = Scheme {name: n, settings: s},
+            Script(s) => c.script = s,
+            Theme(t) => c.theme = t,
+            DrawnMap(m) => c.drawn_map = Some(m)
+        };
+    }
+
+    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<String> {
+        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 map_config(&self) -> Vec<String> {
+        match self.game_info {
+            Some(ref info) => map_config_from(&info.config),
+            None => map_config_from(&self.config)
+        }
+    }
+
+    pub fn game_config(&self) -> Vec<GameCfg> {
+        match self.game_info {
+            Some(ref info) => game_config_from(&info.config),
+            None => game_config_from(&self.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<String, serde_yaml::Error> {
+        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<String, RoomSave>)>(text).map(|(greeting, saves)| {
+            self.greeting = greeting;
+            self.saves = saves;
+        })
+    }
+
+    pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec<String> {
+        let mut info = vec![
+            team.name.clone(),
+            team.grave.clone(),
+            team.fort.clone(),
+            team.voice_pack.clone(),
+            team.flag.clone(),
+            owner.nick.clone(),
+            team.difficulty.to_string()];
+        let hogs = team.hedgehogs.iter().flat_map(|h|
+            iter::once(h.name.clone()).chain(iter::once(h.hat.clone())));
+        info.extend(hogs);
+        info
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/utils.rs	Mon Dec 10 22:44:46 2018 +0100
@@ -0,0 +1,67 @@
+use std::iter::Iterator;
+use mio;
+use base64::{encode};
+
+pub const PROTOCOL_VERSION : u32 = 3;
+pub const SERVER: mio::Token = mio::Token(1_000_000_000);
+
+pub fn is_name_illegal(name: &str ) -> bool{
+    name.len() > 40 ||
+        name.trim().is_empty() ||
+        name.chars().any(|c|
+            "$()*+?[]^{|}\x7F".contains(c) ||
+                '\x00' <= c && c <= '\x1F')
+}
+
+pub fn to_engine_msg<T>(msg: T) -> String
+    where T: Iterator<Item = u8> + Clone
+{
+    let mut tmp = Vec::new();
+    tmp.push(msg.clone().count() as u8);
+    tmp.extend(msg);
+    encode(&tmp)
+}
+
+pub fn protocol_version_string(protocol_number: u16) -> &'static str {
+    match protocol_number {
+        17 => "0.9.7-dev",
+        19 => "0.9.7",
+        20 => "0.9.8-dev",
+        21 => "0.9.8",
+        22 => "0.9.9-dev",
+        23 => "0.9.9",
+        24 => "0.9.10-dev",
+        25 => "0.9.10",
+        26 => "0.9.11-dev",
+        27 => "0.9.11",
+        28 => "0.9.12-dev",
+        29 => "0.9.12",
+        30 => "0.9.13-dev",
+        31 => "0.9.13",
+        32 => "0.9.14-dev",
+        33 => "0.9.14",
+        34 => "0.9.15-dev",
+        35 => "0.9.14.1",
+        37 => "0.9.15",
+        38 => "0.9.16-dev",
+        39 => "0.9.16",
+        40 => "0.9.17-dev",
+        41 => "0.9.17",
+        42 => "0.9.18-dev",
+        43 => "0.9.18",
+        44 => "0.9.19-dev",
+        45 => "0.9.19",
+        46 => "0.9.20-dev",
+        47 => "0.9.20",
+        48 => "0.9.21-dev",
+        49 => "0.9.21",
+        50 => "0.9.22-dev",
+        51 => "0.9.22",
+        52 => "0.9.23-dev",
+        53 => "0.9.23",
+        54 => "0.9.24-dev",
+        55 => "0.9.24",
+        56 => "0.9.25-dev",
+        _ => "Unknown"
+    }
+}
\ No newline at end of file