merge in 0.9.25 fixes
authornemo
Thu, 13 Dec 2018 10:51:07 -0500
changeset 14442 94f10f69fe76
parent 14440 b33d1c694b1d (diff)
parent 14441 1ffa8bfc5c58 (current diff)
child 14445 2c4f71779302
child 14446 bf0ec13a21ea
merge in 0.9.25 fixes
gameServer/CoreTypes.hs
--- a/CMakeLists.txt	Thu Dec 13 10:49:30 2018 -0500
+++ b/CMakeLists.txt	Thu Dec 13 10:51:07 2018 -0500
@@ -85,7 +85,7 @@
 set(CPACK_PACKAGE_VERSION_MAJOR 0)
 set(CPACK_PACKAGE_VERSION_MINOR 9)
 set(CPACK_PACKAGE_VERSION_PATCH 25)
-set(HEDGEWARS_PROTO_VER 57)
+set(HEDGEWARS_PROTO_VER 58)
 set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
 include(${CMAKE_MODULE_PATH}/revinfo.cmake)
 
--- a/ChangeLog.txt	Thu Dec 13 10:49:30 2018 -0500
+++ b/ChangeLog.txt	Thu Dec 13 10:51:07 2018 -0500
@@ -1,5 +1,21 @@
 + features
 * bugfixes
+=============== 1.0.0 (unreleased) =================
+ + New chat command: “/help room” (shows room chat commands within the game)
+ + Colorize switching arrows, pointing arrow and target cross in clan color
+ + Longer delays between turns so its easier to see damage and messages
+ + Skip ammo menu animation when playing with turn time of 10s or less
+ * King Mode: Fix team sometimes not being killed properly if king drowned
+ * King Mode: Kill resurrected minions if king is not alive
+ * Fix poison damage not working in first round
+ * Hide most HUD elements in cinematic mode
+ * Don't show "F1", "F2", etc. in ammo menu if these aren't the actual slot keys
+
+Lua API:
+ + New call: SetTurnTimePaused(isPaused): Call with true to pause turn time, false to unpause
+ + New call: GetTurnTimePaused(): Returns true if turn time is paused due to Lua
+ + Params explode, poison in the SpawnFake*Crate functions now optional and default to false
+
 ====================== 0.9.25 ======================
 HIGHLIGHTS:
  + Complete overhaul of Continental supplies
--- a/QTfrontend/ui/page/pagenet.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/QTfrontend/ui/page/pagenet.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -47,6 +47,7 @@
     BtnNetConnect = new QPushButton(ConnGroupBox);
     BtnNetConnect->setFont(*font14);
     BtnNetConnect->setText(QPushButton::tr("Connect"));
+    BtnNetConnect->setWhatsThis(tr("Connect to the selected server"));
     GBClayout->addWidget(BtnNetConnect, 2, 2);
 
     tvServersList = new QTableView(ConnGroupBox);
@@ -56,11 +57,13 @@
     BtnUpdateSList = new QPushButton(ConnGroupBox);
     BtnUpdateSList->setFont(*font14);
     BtnUpdateSList->setText(QPushButton::tr("Update"));
+    BtnUpdateSList->setWhatsThis(tr("Update the list of servers"));
     GBClayout->addWidget(BtnUpdateSList, 2, 0);
 
     BtnSpecifyServer = new QPushButton(ConnGroupBox);
     BtnSpecifyServer->setFont(*font14);
-    BtnSpecifyServer->setText(QPushButton::tr("Specify"));
+    BtnSpecifyServer->setText(QPushButton::tr("Specify address"));
+    BtnSpecifyServer->setWhatsThis(tr("Specify the address and port number of a known server and connect to it directly"));
     GBClayout->addWidget(BtnSpecifyServer, 2, 1);
 
     return pageLayout;
@@ -71,6 +74,7 @@
     QHBoxLayout * footerLayout = new QHBoxLayout();
 
     BtnNetSvrStart = formattedButton(QPushButton::tr("Start server"));
+    BtnNetSvrStart->setWhatsThis(tr("Start private server"));
     BtnNetSvrStart->setMinimumSize(180, 50);
     QString serverPath = bindir->absolutePath() + "/hedgewars-server";
 #ifdef Q_OS_WIN
--- a/QTfrontend/ui/page/pagesingleplayer.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/QTfrontend/ui/page/pagesingleplayer.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -48,7 +48,7 @@
     BtnCampaignPage->setVisible(true);
 
     BtnTrainPage = addButton(":/res/Trainings.png", middleLine, 1, true);
-    BtnTrainPage->setWhatsThis(tr("Practice your skills in a range of training missions"));
+    BtnTrainPage->setWhatsThis(tr("Singleplayer missions: Learn how to play in the training, practice your skills in challenges or try to complete goals in scenarios."));
 
     return vLayout;
 }
--- a/QTfrontend/ui/widget/about.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/QTfrontend/ui/widget/about.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -102,6 +102,10 @@
 
 #if defined(__GNUC__)
     libinfo.append(QString(tr("<a href=\"https://gcc.gnu.org\">GCC</a>: %1")).arg(__VERSION__));
+#elif defined(WIN32_VCPKG)
+    libinfo.append(QString(tr("<a href=\"https://visualstudio.microsoft.com\">VC++</a>: %1")).arg(_MSC_FULL_VER));
+#elif defined(__VERSION__)
+    libinfo.append(QString(tr("Unknown Compiler: %1")).arg(__VERSION__));
 #else
     libinfo.append(QString(tr("Unknown Compiler")));
 #endif
--- a/QTfrontend/ui/widget/mapContainer.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/QTfrontend/ui/widget/mapContainer.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -921,6 +921,7 @@
     QString randomNoMapPrev = tr("Click to randomize the theme and seed");
     QString mfsComplex = QString(tr("Adjust the complexity of the generated map"));
     QString mfsFortsDistance = QString(tr("Adjust the distance between forts"));
+    QString mfsDrawnMap = QString(tr("Scale size of the drawn map"));
     switch (type)
     {
         case MapModel::GeneratedMap:
@@ -938,6 +939,7 @@
         case MapModel::HandDrawnMap:
             mapPreview->setWhatsThis(tr("Click to edit"));
             btnRandomize->setWhatsThis(randomSeed);
+            mapFeatureSize->setWhatsThis(mfsDrawnMap);
             break;
         case MapModel::FortsMap:
             mapPreview->setWhatsThis(randomNoMapPrev);
@@ -990,7 +992,7 @@
             mapgen = MAPGEN_DRAWN;
             setMapInfo(MapModel::MapInfoDrawn);
             btnLoadMap->show();
-            mapFeatureSize->hide();
+            //mapFeatureSize->hide();
             btnEditMap->show();
             break;
         case MapModel::MissionMap:
@@ -1075,14 +1077,15 @@
     //if (qAbs(m_prevMapFeatureSize-m_mapFeatureSize) > 4)
     {
         m_prevMapFeatureSize = m_mapFeatureSize;
-        updatePreview();
+		if(m_mapInfo.type!= MapModel::HandDrawnMap)
+			updatePreview();
     }
 }
 
 // unused because I needed the space for the slider
 void HWMapContainer::updateThemeButtonSize()
 {
-    if (m_mapInfo.type != MapModel::StaticMap && m_mapInfo.type != MapModel::HandDrawnMap)
+    if (m_mapInfo.type != MapModel::StaticMap)
     {
         btnTheme->setIconSize(QSize(30, 30));
         btnTheme->setFixedHeight(30);
--- a/gameServer/Actions.hs	Thu Dec 13 10:49:30 2018 -0500
+++ b/gameServer/Actions.hs	Thu Dec 13 10:51:07 2018 -0500
@@ -885,3 +885,13 @@
 
 processAction CheckVotes =
     checkVotes >>= mapM_ processAction
+
+processAction (ShowRegisteredOnlyState chans) = do
+    si <- gets serverInfo
+    processAction $ AnswerClients chans
+        ["CHAT", nickServer,
+        if isRegisteredUsersOnly si then
+            loc "This server no longer allows unregistered players to join."
+        else
+            loc "This server now allows unregistered players to join."
+        ]
--- a/gameServer/CoreTypes.hs	Thu Dec 13 10:49:30 2018 -0500
+++ b/gameServer/CoreTypes.hs	Thu Dec 13 10:51:07 2018 -0500
@@ -103,6 +103,7 @@
     | ReactCmd [B.ByteString]
     | CheckVotes
     | SetRandomSeed
+    | ShowRegisteredOnlyState [ClientChan]
 
 
 data Event = LobbyChatMessage
--- a/gameServer/HWProtoCore.hs	Thu Dec 13 10:49:30 2018 -0500
+++ b/gameServer/HWProtoCore.hs	Thu Dec 13 10:51:07 2018 -0500
@@ -107,7 +107,7 @@
 
         -- lobby-only commands
         h "STATS" _ = handleCmd_lobbyOnly ["STATS"]
-        h "RESTART_SERVER" "YES" = handleCmd_lobbyOnly ["RESTART_SERVER"]
+        h "RESTART_SERVER" p = handleCmd_lobbyOnly ["RESTART_SERVER", upperCase p]
 
         -- room and lobby commands
         h "QUIT" _ = handleCmd ["QUIT"]
@@ -120,11 +120,11 @@
         h "INFO" n | not $ B.null n = handleCmd ["INFO", n]
         h "HELP" _ = handleCmd ["HELP"]
         h "REGISTERED_ONLY" _ = serverAdminOnly $ do
-            cl <- thisClient
+            rnc <- liftM snd ask
+            let chans = map (sendChan . client rnc) $ allClients rnc
             return
                 [ModifyServerInfo(\s -> s{isRegisteredUsersOnly = not $ isRegisteredUsersOnly s})
-                -- TODO: Say whether 'registered only' state is on or off
-                , AnswerClients [sendChan cl] ["CHAT", nickServer, loc "'Registered only' state toggled."]
+                , ShowRegisteredOnlyState chans
                 ]
         h "SUPER_POWER" _ = serverAdminOnly $ do
             cl <- thisClient
--- a/gameServer/HWProtoInRoomState.hs	Thu Dec 13 10:49:30 2018 -0500
+++ b/gameServer/HWProtoInRoomState.hs	Thu Dec 13 10:51:07 2018 -0500
@@ -452,7 +452,7 @@
 handleCmd_inRoom ["CALLVOTE"] = do
     cl <- thisClient
     return [AnswerClients [sendChan cl]
-        ["CHAT", nickServer, loc "Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs"]
+        ["CHAT", nickServer, loc "Available callvote commands: hedgehogs <number>, pause, newseed, map <name>, kick <player>"]
         ]
 
 handleCmd_inRoom ["CALLVOTE", "KICK"] = do
--- a/gameServer/HWProtoLobbyState.hs	Thu Dec 13 10:49:30 2018 -0500
+++ b/gameServer/HWProtoLobbyState.hs	Thu Dec 13 10:51:07 2018 -0500
@@ -220,12 +220,18 @@
 handleCmd_lobby ["CLEAR_ACCOUNTS_CACHE"] = serverAdminOnly $
     return [ClearAccountsCache]
 
+handleCmd_lobby ["RESTART_SERVER", "YES"] = serverAdminOnly $
+    return [RestartServer]
+
 handleCmd_lobby ["RESTART_SERVER"] = serverAdminOnly $
-    return [RestartServer]
+    return [Warning $ loc "Please confirm server restart with '/restart_server yes'."]
+
+handleCmd_lobby ["RESTART_SERVER", _] = handleCmd_lobby ["RESTART_SERVER"]
+
 
 handleCmd_lobby ["STATS"] = serverAdminOnly $
     return [Stats]
 
 handleCmd_lobby (s:_) = return [ProtocolError $ "Incorrect command '" `B.append` s `B.append` "' (state: in lobby)"]
 
-handleCmd_lobby [] = return [ProtocolError "Empty command (state: in lobby)"]
\ No newline at end of file
+handleCmd_lobby [] = return [ProtocolError "Empty command (state: in lobby)"]
--- a/gameServer2/Cargo.toml	Thu Dec 13 10:49:30 2018 -0500
+++ /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	Thu Dec 13 10:49:30 2018 -0500
+++ /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/messages.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /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/mod.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /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/parser.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /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	Thu Dec 13 10:49:30 2018 -0500
+++ /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/actions.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /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::{
-    server::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	Thu Dec 13 10:49:30 2018 -0500
+++ /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/coretypes.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /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/checker.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-use mio;
-use log::*;
-
-use crate::{
-    server::{
-        server::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	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-use crate::{
-    server::{actions::Action, server::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	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,499 +0,0 @@
-use mio;
-
-use crate::{
-    server::{
-        coretypes::{
-            ClientId, RoomId, Voting, VoteType, GameCfg,
-            MAX_HEDGEHOGS_PER_TEAM
-        },
-        server::HWServer,
-        room::{HWRoom, RoomFlags},
-        actions::{Action, Action::*}
-    },
-    protocol::messages::{
-        HWProtocolMessage,
-        HWServerMessage::*,
-        server_chat
-    },
-    utils::is_name_illegal
-};
-use std::{
-    mem::swap, fs::{File, OpenOptions},
-    io::{Read, Write, Result, Error, ErrorKind}
-};
-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()
-    }
-}
-
-fn read_file(filename: &str) -> Result<String> {
-    let mut reader = File::open(filename)?;
-    let mut result = String::new();
-    reader.read_to_string(&mut result)?;
-    Ok(result)
-}
-
-fn write_file(filename: &str, content: &str) -> Result<()> {
-    let mut writer = OpenOptions::new().create(true).write(true).open(filename)?;
-    writer.write_all(content.as_bytes())
-}
-
-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 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 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	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-use mio;
-
-use crate::{
-    server::{
-        server::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	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-use mio;
-
-use crate::{
-    server::{
-        client::HWClient,
-        server::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/handlers/mod.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-use mio;
-use std::{io, io::Write};
-
-use super::{
-    server::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/mod.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-pub mod server;
-pub mod client;
-pub mod room;
-pub mod network;
-pub mod coretypes;
-mod actions;
-mod handlers;
--- a/gameServer2/src/server/network.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,443 +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::{
-    server::{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);
-        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	Thu Dec 13 10:49:30 2018 -0500
+++ /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/server/server.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-use slab;
-use crate::utils;
-use super::{
-    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>,
-}
-
-impl HWServer {
-    pub fn new(clients_limit: usize, rooms_limit: usize) -> 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![]
-        };
-        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/utils.rs	Thu Dec 13 10:49:30 2018 -0500
+++ /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
--- a/hedgewars/uChat.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uChat.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -589,6 +589,15 @@
         exit
         end;
 
+    if (copy(s, 2, 9) = 'help room') then
+        begin
+        if (gameType = gmtNet) then
+            SendConsoleCommand('/help')
+        else
+            AddChatString(#0 + shortstring(trcmd[sidCmdHelpRoomFail]));
+        exit;
+        end;
+
     if (copy(s, 2, 4) = 'help') then
         begin
         AddChatString(#3 + shortstring(trcmd[sidCmdHeaderBasic]));
@@ -608,6 +617,8 @@
         AddChatString(#3 + shortstring(trcmd[sidCmdHistory]));
         AddChatString(#3 + shortstring(trcmd[sidCmdHelp]));
         AddChatString(#3 + shortstring(trcmd[sidCmdHelpTaunts]));
+        if gameType = gmtNet then
+            AddChatString(#3 + shortstring(trcmd[sidCmdHelpRoom]));
         exit
         end;
 
--- a/hedgewars/uGears.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uGears.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -62,12 +62,20 @@
 
 var delay: LongWord;
     delay2: LongWord;
-    step: (stInit, stDelay, stChDmg, stSweep, stTurnStats, stChWin1,
-    stTurnReact, stAfterDelay, stChWin2, stWater, stChWin3,
-    stHealth, stSpawn, stNTurn);
+    step: (stInit, stDelay1, stChDmg, stSweep, stTurnStats, stChWin1,
+    stTurnReact, stDelay2, stChWin2, stWater, stChWin3,
+    stChKing, stSuddenDeath, stDelay3, stHealth, stSpawn, stDelay4,
+    stNTurn);
     NewTurnTick: LongWord;
     //SDMusic: shortstring;
 
+const delaySDStart = 1600;
+      delaySDWarning = 1000;
+      delayDamageTagFull = 1500;
+      delayDamageTagShort = 500;
+      delayTurnReact = 800;
+      delayFinal = 100;
+
 function CheckNoDamage: boolean; // returns TRUE in case of no damaged hhs
 var Gear: PGear;
     dmg: LongInt;
@@ -86,7 +94,7 @@
             CheckNoDamage:= false;
 
             dmg:= Gear^.Damage;
-            if Gear^.Health < dmg then
+            if (Gear^.Health < dmg) then
                 begin
                 Gear^.Active:= true;
                 Gear^.Health:= 0
@@ -105,7 +113,15 @@
             RenderHealth(Gear^.Hedgehog^);
             RecountTeamHealth(Gear^.Hedgehog^.Team);
 
+            end
+        else if ((GameFlags and gfKing) <> 0) and (not Gear^.Hedgehog^.Team^.hasKing) then
+            begin
+            Gear^.Active:= true;
+            Gear^.Health:= 0;
+            RenderHealth(Gear^.Hedgehog^);
+            RecountTeamHealth(Gear^.Hedgehog^.Team);
             end;
+
         if (not isInMultiShoot) then
             Gear^.Karma:= 0;
         Gear^.Damage:= 0
@@ -114,6 +130,34 @@
     end;
 end;
 
+function DoDelay: boolean;
+begin
+if delay <= 0 then
+    delay:= 1
+else
+    dec(delay);
+DoDelay:= delay = 0;
+end;
+
+function CheckMinionsDie: boolean;
+var Gear: PGear;
+begin
+    CheckMinionsDie:= false;
+    if (GameFlags and gfKing) = 0 then
+        exit;
+
+    Gear:= GearsList;
+    while Gear <> nil do
+        begin
+        if (Gear^.Kind = gtHedgehog) and (not Gear^.Hedgehog^.King) and (not Gear^.Hedgehog^.Team^.hasKing) then
+            begin
+            CheckMinionsDie:= true;
+            exit;
+            end;
+        Gear:= Gear^.NextGear;
+        end;
+end;
+
 procedure HealthMachine;
 var Gear: PGear;
     team: PTeam;
@@ -269,22 +313,20 @@
             ScriptCall('onEndTurn');
         inc(step)
         end;
-    stDelay:
-        begin
-        if delay = 0 then
-            delay:= cInactDelay
-        else
-            dec(delay);
-
-        if delay = 0 then
-            inc(step)
-        end;
-
+    stDelay1:
+        if DoDelay() then
+            inc(step);
     stChDmg:
     if CheckNoDamage then
         inc(step)
     else
-        step:= stDelay;
+        begin
+        if (not bBetweenTurns) and (not isInMultiShoot) then
+            delay:= delayDamageTagShort
+        else
+            delay:= delayDamageTagFull;
+        step:= stDelay1;
+        end;
 
     stSweep:
     if SweepDirty then
@@ -314,22 +356,16 @@
             begin
             uStats.TurnReaction;
             uStats.TurnStatsReset;
+            delay:= delayTurnReact;
             inc(step)
             end
         else
             inc(step, 2);
         end;
 
-    stAfterDelay:
-        begin
-        if delay = 0 then
-            delay:= cInactDelay
-        else
-            dec(delay);
-
-        if delay = 0 then
-            inc(step)
-            end;
+    stDelay2:
+        if DoDelay() then
+            inc(step);
     stChWin2:
         begin
         CheckForWin();
@@ -357,39 +393,79 @@
         inc(step)
         end;
 
-    stHealth:
+    stChKing:
+        begin
+        if (not isInMultiShoot) and (CheckMinionsDie) then
+            step:= stChDmg
+        else
+            inc(step);
+        end;
+    stSuddenDeath:
         begin
-        if (cWaterRise <> 0) or (cHealthDecrease <> 0) then
-             begin
-            if (TotalRoundsPre = cSuddenDTurns) and (not SuddenDeath) and (not isInMultiShoot) then
-                StartSuddenDeath()
-            else if (TotalRoundsPre < cSuddenDTurns) and (not isInMultiShoot) then
+        if ((cWaterRise <> 0) or (cHealthDecrease <> 0)) and (not (isInMultiShoot or bBetweenTurns)) then
+            begin
+            // Start Sudden Death
+            if (TotalRoundsPre = cSuddenDTurns) and (not SuddenDeath) then
+                begin
+                StartSuddenDeath();
+                delay:= delaySDStart;
+                inc(step);
+                end
+            // Show Sudden Death warning message
+            else if (TotalRoundsPre < cSuddenDTurns) and ((LastSuddenDWarn = -2) or (LastSuddenDWarn <> TotalRoundsPre)) then
                 begin
                 i:= cSuddenDTurns - TotalRoundsPre;
                 s:= ansistring(inttostr(i));
-                if i = 1 then
-                    AddCaption(trmsg[sidRoundSD], capcolDefault, capgrpGameState)
-                else if (i = 2) or ((i > 0) and ((i mod 50 = 0) or ((i <= 25) and (i mod 5 = 0)))) then
-                    AddCaption(FormatA(trmsg[sidRoundsSD], s), capcolDefault, capgrpGameState);
-                end;
+                // X rounds before SD. X = 1, 2, 3, 5, 7, 10, 15, 20, 25, 50, 100, ...
+                if (i > 0) and ((i <= 3) or (i = 7) or ((i mod 50 = 0) or ((i <= 25) and (i mod 5 = 0)))) then
+                    begin
+                    if i = 1 then
+                        AddCaption(trmsg[sidRoundSD], capcolDefault, capgrpGameState)
+                    else
+                        AddCaption(FormatA(trmsg[sidRoundsSD], s), capcolDefault, capgrpGameState);
+                    delay:= delaySDWarning;
+                    inc(step);
+                    LastSuddenDWarn:= TotalRoundsPre;
+                    end
+                else
+                    inc(step, 2);
+                end
+            else
+                inc(step, 2);
+            end
+        else
+            inc(step, 2);
+        end;
+    stDelay3:
+        if DoDelay() then
+            inc(step);
+    stHealth:
+        begin
+        if bBetweenTurns
+        or isInMultiShoot
+        or (TotalRoundsReal = -1) then
+            inc(step)
+        else
+            begin
+            bBetweenTurns:= true;
+            HealthMachine;
+            step:= stChDmg
             end;
-            if bBetweenTurns
-            or isInMultiShoot
-            or (TotalRoundsPre = -1) then
-                inc(step)
-            else
-                begin
-                bBetweenTurns:= true;
-                HealthMachine;
-                step:= stChDmg
-                end
-            end;
+        end;
     stSpawn:
         begin
-        if not isInMultiShoot then
+        if (not isInMultiShoot) then
+            begin
             SpawnBoxOfSmth;
-        inc(step)
+            delay:= delayFinal;
+            inc(step);
+            end
+        else
+            inc(step, 2)
         end;
+    stDelay4:
+        if DoDelay() then
+            inc(step);
     stNTurn:
         begin
         if isInMultiShoot then
@@ -466,11 +542,11 @@
     if IsClockRunning() then
         //(CurrentHedgehog^.CurAmmoType in [amShotgun, amDEagle, amSniperRifle])
         begin
-        if (cHedgehogTurnTime >= 10000)
+        if (cHedgehogTurnTime > TurnTimeLeft)
         and (CurrentHedgehog^.Gear <> nil)
         and ((CurrentHedgehog^.Gear^.State and gstAttacked) = 0)
         and (not isGetAwayTime) and (ReadyTimeLeft = 0) then
-            if TurnTimeLeft = 5000 then
+            if (TurnTimeLeft = 5000) and (cHedgehogTurnTime >= 10000) then
                 PlaySoundV(sndHurry, CurrentTeam^.voicepack)
             else if TurnTimeLeft = 4000 then
                 PlaySound(sndCountdown4)
@@ -1147,7 +1223,8 @@
     or (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerWhileAttacking <> 0)
     or ((GameFlags and gfInfAttack) <> 0) and (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerWhileAttackingInInfAttackMode <> 0)
     or (CurrentHedgehog^.CurAmmoType = amSniperRifle))
-    and (not(isInMultiShoot and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerInMultiShoot) <> 0)));
+    and (not(isInMultiShoot and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerInMultiShoot) <> 0)))
+    and (not LuaClockPaused);
 end;
 
 
@@ -1332,7 +1409,7 @@
     //typed const
     delay:= 0;
     delay2:= 0;
-    step:= stDelay;
+    step:= stDelay1;
     upd:= 0;
 
     //SDMusic:= 'hell.ogg';
--- a/hedgewars/uGearsHandlersMess.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uGearsHandlersMess.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -6023,6 +6023,8 @@
                 RenderHealth(resgear^.Hedgehog^);
                 RecountTeamHealth(resgear^.Hedgehog^.Team);
                 resgear^.Hedgehog^.Effects[heResurrected]:= 1;
+                if resgear^.Hedgehog^.King then
+                    resgear^.Hedgehog^.Team^.hasKing:= true;
                 { Reviving a hog implies its clan is now alive, too. }
                 resgear^.Hedgehog^.Team^.Clan^.DeathLogged:= false;
                 s:= ansistring(resgear^.Hedgehog^.Name);
--- a/hedgewars/uGearsHedgehog.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uGearsHedgehog.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -1307,7 +1307,7 @@
 else if not isInMultiShoot then
     AllInactive:= false;
 
-if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (LuaEndTurnRequested = true) then
+if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (((GameFlags and gfKing) <> 0) and (not Hedgehog^.Team^.hasKing)) or (LuaEndTurnRequested = true) then
     begin
     if (Hedgehog^.CurAmmoType = amKnife) then
        LoadHedgehogHat(Hedgehog^, Hedgehog^.Hat);
--- a/hedgewars/uGearsList.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uGearsList.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -771,7 +771,6 @@
 procedure DeleteGear(Gear: PGear);
 var team: PTeam;
     t,i: Longword;
-    k: boolean;
     cakeData: PCakeData;
     iterator: PGear;
 begin
@@ -857,19 +856,14 @@
 
         if Gear^.Hedgehog^.King then
             begin
-            // are there any other kings left? Just doing nil check.  Presumably a mortally wounded king will get reaped soon enough
-            k:= false;
+            Gear^.Hedgehog^.Team^.hasKing:= false;
             for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
-                if (team^.Clan^.Teams[i]^.Hedgehogs[0].Gear <> nil) then
-                    k:= true;
-            if not k then
-                for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
-                    with team^.Clan^.Teams[i]^ do
-                        for t:= 0 to cMaxHHIndex do
-                            if Hedgehogs[t].Gear <> nil then
-                                Hedgehogs[t].Gear^.Health:= 0
-                            else if (Hedgehogs[t].GearHidden <> nil) then
-                                Hedgehogs[t].GearHidden^.Health:= 0  // hog is still hidden. if tardis should return though, lua, eh...
+                with team^.Clan^.Teams[i]^ do
+                    for t:= 0 to cMaxHHIndex do
+                        if Hedgehogs[t].Gear <> nil then
+                            Hedgehogs[t].Gear^.Health:= 0
+                        else if (Hedgehogs[t].GearHidden <> nil) then
+                            Hedgehogs[t].GearHidden^.Health:= 0  // hog is still hidden. if tardis should return though, lua, eh...
             end;
 
         // should be not CurrentHedgehog, but hedgehog of the last gear which caused damage to this hog
--- a/hedgewars/uGearsRender.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uGearsRender.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -1189,7 +1189,9 @@
 
                 dAngle := DxDy2Angle(int2hwfloat(ty - oy), int2hwfloat(tx - ox)) + 90;
 
+                Tint(Team^.Clan^.Color shl 8 or $FF);
                 DrawSpriteRotatedF(sprFinger, tx, ty, RealTicks div 32 mod 16, 1, dAngle);
+                untint;
                 end;
 
 
@@ -1264,7 +1266,13 @@
         //DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1, 0, 0, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8, 1, 22, 22, (RealTicks shr 3) mod 360)
         DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1/(1+(RealTicks shr 8) mod 5), 0, 0, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8, 1, 22, 22, (RealTicks shr 3) mod 360)
     else
+        begin
+        if CurrentHedgehog <> nil then
+            Tint(CurrentHedgehog^.Team^.Clan^.Color shl 8 or $FF);
         DrawSpriteRotatedF(sprTargetP, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+        if CurrentHedgehog <> nil then
+            untint;
+        end;
 
     case Gear^.Kind of
           gtGrenade: DrawSpriteRotated(sprBomb, x, y, 0, Gear^.DirAngle);
@@ -1467,7 +1475,13 @@
                         DrawSpriteRotatedF(sprTeleport, hwRound(HHGear^.X) + 1 + WorldDx, hwRound(HHGear^.Y) - 3 + WorldDy, 11 - Gear^.Pos, hwSign(HHGear^.dX), 0)
                         end
                     end;
-        gtSwitcher: DrawSprite(sprSwitch, x - 16, y - 56, (RealTicks shr 6) mod 12);
+        gtSwitcher: begin
+                    setTintAdd(true);
+                    Tint(Gear^.Hedgehog^.Team^.Clan^.Color shl 8 or $FF);
+                    DrawSprite(sprSwitch, x - 16, y - 56, (RealTicks shr 6) mod 12);
+                    untint;
+                    setTintAdd(false);
+                    end;
           gtTarget: begin
                     Tint($FF, $FF, $FF, round($FF * Gear^.Timer / 1000));
                     DrawSprite(sprTarget, x - 16, y - 16, 0);
--- a/hedgewars/uInputHandler.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uInputHandler.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -38,6 +38,10 @@
 procedure ProcessKey(event: TSDL_KeyboardEvent); inline;
 procedure ProcessKey(code: LongInt; KeyDown: boolean);
 
+{$IFDEF USE_AM_NUMCOLUMN}
+function CheckDefaultSlotKeys: boolean;
+{$ENDIF}
+
 procedure ResetKbd;
 procedure ResetMouseWheel;
 procedure FreezeEnterKey;
@@ -488,6 +492,27 @@
 end;
 
 
+{$IFDEF USE_AM_NUMCOLUMN}
+function CheckDefaultSlotKeys: boolean;
+{$IFDEF USE_TOUCH_INTERFACE}
+begin
+    CheckDefaultSlotKeys:= false;
+{$ELSE}
+var i, code: LongInt;
+begin
+    for i:=1 to cMaxSlotIndex do
+        begin
+        code:= KeyNameToCode('f'+IntToStr(i));
+        if CurrentBinds.binds[CurrentBinds.indices[code]] <> 'slot '+char(i+48) then
+            begin
+            CheckDefaultSlotKeys:= false;
+            exit;
+            end;
+        end;
+    CheckDefaultSlotKeys:= true;
+{$ENDIF}
+end;
+{$ENDIF}
 
 {$IFNDEF MOBILE}
 procedure SetBinds(var binds: TBinds);
--- a/hedgewars/uLand.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uLand.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -285,13 +285,13 @@
 
 procedure GenDrawnMap;
 begin
-    ResizeLand(4096, 2048);
+    ResizeLand((4096 * max(min(cFeatureSize,24),3)) div 12, (2048 * max(min(cFeatureSize,24),3)) div 12);
     uLandPainted.Draw;
 
-    MaxHedgehogs:= 48;
+    MaxHedgehogs:= 64;
     hasGirders:= true;
-    playHeight:= 2048;
-    playWidth:= 4096;
+    playHeight:= LAND_HEIGHT;
+    playWidth:= LAND_WIDTH;
     leftX:= ((LAND_WIDTH - playWidth) div 2);
     rightX:= (playWidth + ((LAND_WIDTH - playWidth) div 2)) - 1;
     topY:= LAND_HEIGHT - playHeight;
@@ -300,11 +300,11 @@
 function SelectTemplate: LongInt;
 var l: LongInt;
 begin
-	SelectTemplate:= 0;
+    SelectTemplate:= 0;
     if (cReducedQuality and rqLowRes) <> 0 then
         SelectTemplate:= SmallTemplates[getrandom(Succ(High(SmallTemplates)))]
     else
-		begin
+        begin
         if cTemplateFilter = 0 then
             begin
             l:= getRandom(GroupedTemplatesCount);
@@ -313,22 +313,22 @@
                 dec(l, TemplateCounts[cTemplateFilter]);
             until l < 0;
             end
-			else getRandom(1);
+            else getRandom(1);
 
-			case cTemplateFilter of
-			0: OutError('Error selecting TemplateFilter. Ask unC0Rr about what you did wrong', true);
-			1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
-			2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
-			3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
-			4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
-			5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
-	// For lua only!
-			6: begin
-			   SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
-			   GetRandom(2) // burn 1
-			   end
-			end
-		end;
+            case cTemplateFilter of
+            0: OutError('Error selecting TemplateFilter. Ask unC0Rr about what you did wrong', true);
+            1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+            2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+            3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+            4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+            5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+    // For lua only!
+            6: begin
+               SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
+               GetRandom(2) // burn 1
+               end
+            end
+        end;
 
     WriteLnToConsole('Selected template #'+inttostr(SelectTemplate)+' using filter #'+inttostr(cTemplateFilter));
 end;
@@ -886,7 +886,7 @@
         mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
         mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
         mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
-        mgDrawn: GenDrawnMap;
+        mgDrawn: begin cFeatureSize:= 3;GenDrawnMap; end;
         mgForts: MakeFortsPreview();
     else
         OutError('Unknown mapgen', true);
@@ -895,8 +895,16 @@
     ScriptSetMapGlobals;
 
     // strict scaling needed here since preview assumes a rectangle
-    rh:= max(LAND_HEIGHT,2048);
-    rw:= max(LAND_WIDTH,4096);
+    if (cMapGen <> mgDrawn) then
+        begin
+        rh:= max(LAND_HEIGHT, 2048);
+        rw:= max(LAND_WIDTH, 4096);
+        end
+    else
+        begin
+        rh:= LAND_HEIGHT;
+        rw:= LAND_WIDTH
+        end;
     ox:= 0;
     if rw < rh*2 then
         begin
@@ -937,7 +945,7 @@
         mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
         mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
         mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
-        mgDrawn: GenDrawnMap;
+        mgDrawn: begin cFeatureSize:= 3;GenDrawnMap; end;
         mgForts: MakeFortsPreview;
     else
         OutError('Unknown mapgen', true);
@@ -945,9 +953,19 @@
 
     ScriptSetMapGlobals;
 
+
     // strict scaling needed here since preview assumes a rectangle
-    rh:= max(LAND_HEIGHT, 2048);
-    rw:= max(LAND_WIDTH, 4096);
+    if (cMapGen <> mgDrawn) then
+        begin
+        rh:= max(LAND_HEIGHT, 2048);
+        rw:= max(LAND_WIDTH, 4096);
+        end
+    else
+        begin
+        rh:= LAND_HEIGHT;
+        rw:= LAND_WIDTH
+        end;
+
     ox:= 0;
     if rw < rh*2 then
         begin
@@ -986,9 +1004,9 @@
 
 procedure chSendLandDigest(var s: shortstring);
 var i: LongInt;
-	landPixelDigest  : LongInt;	
+    landPixelDigest  : LongInt;
 begin
-	landPixelDigest:= 1;
+    landPixelDigest:= 1;
     for i:= 0 to LAND_HEIGHT-1 do
         landPixelDigest:= Adler32Update(landPixelDigest, @Land[i,0], LAND_WIDTH*2);
     s:= 'M' + IntToStr(syncedPixelDigest)+'|'+IntToStr(landPixelDigest);
--- a/hedgewars/uLandPainted.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uLandPainted.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -58,6 +58,7 @@
         rec:= prec^;
         rec.X:= SDLNet_Read16(@rec.X);
         rec.Y:= SDLNet_Read16(@rec.Y);
+		
         if rec.X < -318 then rec.X:= -318;
         if rec.X > 4096+318 then rec.X:= 4096+318;
         if rec.Y < -318 then rec.Y:= -318;
@@ -81,7 +82,7 @@
 var pe: PPointEntry;
     prevPoint: PointRec;
     radius: LongInt;
-    color: Longword;
+    color, Xoffset, Yoffset: Longword;
     lineNumber, linePoints: Longword;
 begin
     // shutup compiler
@@ -89,6 +90,8 @@
     prevPoint.Y:= 0;
     radius:= 0;
     linePoints:= 0;
+ 	Xoffset:= (LAND_WIDTH-(4096*max(min(cFeatureSize,24),3) div 12)) div 2;
+ 	Yoffset:= (LAND_HEIGHT-(2048*max(min(cFeatureSize,24),3) div 12));
 
     pe:= pointsListHead;
     while (pe <> nil) and (pe^.point.flags and $80 = 0) do
@@ -101,6 +104,8 @@
 
     while(pe <> nil) do
         begin
+		pe^.point.X:= (LongInt(pe^.point.X) * max(min(cFeatureSize,24),3)) div 12 + Xoffset;
+		pe^.point.Y:= (LongInt(pe^.point.Y) * max(min(cFeatureSize,24),3)) div 12 + Yoffset;
         if (pe^.point.flags and $80 <> 0) then
             begin
             if (lineNumber > 0) and (linePoints = 0) and cAdvancedMapGenMode then
@@ -113,9 +118,10 @@
                 else
                 color:= lfBasic;
             radius:= (pe^.point.flags and $3F) * 5 + 3;
+			radius:= (radius * max(min(cFeatureSize,24),3)) div 12;
             linePoints:= FillRoundInLand(pe^.point.X, pe^.point.Y, radius, color);
             end
-            else
+		else
             begin
             inc(linePoints, DrawThickLine(prevPoint.X, prevPoint.Y, pe^.point.X, pe^.point.Y, radius, color));
             end;
--- a/hedgewars/uScript.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uScript.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -686,11 +686,20 @@
 
 function lc_spawnfakehealthcrate(L: Plua_State) : LongInt; Cdecl;
 var gear: PGear;
-begin
-    if CheckLuaParamCount(L, 4,'SpawnFakeHealthCrate', 'x, y, explode, poison') then
+    explode, poison: boolean;
+    n: LongInt;
+begin
+    if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeHealthCrate', 'x, y [, explode [, poison]]', n) then
         begin
+        explode:= false;
+        poison:= false;
+        if (n >= 3) and (not lua_isnil(L, 3)) then
+            explode:= lua_toboolean(L, 3);
+        if (n = 4) and (not lua_isnil(L, 4)) then
+            poison:= lua_toboolean(L, 4);
+
         gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
-        HealthCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+        HealthCrate, explode, poison);
         if gear <> nil then
              lua_pushnumber(L, gear^.uid)
         else lua_pushnil(L)
@@ -702,11 +711,20 @@
 
 function lc_spawnfakeammocrate(L: PLua_State): LongInt; Cdecl;
 var gear: PGear;
-begin
-    if CheckLuaParamCount(L, 4,'SpawnFakeAmmoCrate', 'x, y, explode, poison') then
+    explode, poison: boolean;
+    n: LongInt;
+begin
+    if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeAmmoCrate', 'x, y [, explode [, poison]]', n) then
         begin
+        explode:= false;
+        poison:= false;
+        if (n >= 3) and (not lua_isnil(L, 3)) then
+            explode:= lua_toboolean(L, 3);
+        if (n = 4) and (not lua_isnil(L, 4)) then
+            poison:= lua_toboolean(L, 4);
+
         gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
-        AmmoCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+        AmmoCrate, explode, poison);
         if gear <> nil then
              lua_pushnumber(L, gear^.uid)
         else lua_pushnil(L)
@@ -718,11 +736,20 @@
 
 function lc_spawnfakeutilitycrate(L: PLua_State): LongInt; Cdecl;
 var gear: PGear;
-begin
-    if CheckLuaParamCount(L, 4,'SpawnFakeUtilityCrate', 'x, y, explode, poison') then
+    explode, poison: boolean;
+    n: LongInt;
+begin
+    if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeUtilityCrate', 'x, y [, explode [, poison]]', n) then
         begin
+        explode:= false;
+        poison:= false;
+        if (n >= 3) and (not lua_isnil(L, 3)) then
+            explode:= lua_toboolean(L, 3);
+        if (n = 4) and (not lua_isnil(L, 4)) then
+            poison:= lua_toboolean(L, 4);
+
         gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
-        UtilityCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+        UtilityCrate, explode, poison);
         if gear <> nil then
              lua_pushnumber(L, gear^.uid)
         else lua_pushnil(L)
@@ -3276,6 +3303,22 @@
     lc_setreadytimeleft:= 0;
 end;
 
+function lc_setturntimepaused(L : Plua_State) : LongInt; Cdecl;
+begin
+    if CheckLuaParamCount(L, 1, 'SetTurnTimePaused', 'isPaused') then
+        LuaClockPaused:= lua_toboolean(L, 1);
+    lc_setturntimepaused:= 0;
+end;
+
+function lc_getturntimepaused(L : Plua_State) : LongInt; Cdecl;
+begin
+    if CheckLuaParamCount(L, 0, 'GetTurnTimePaused', '') then
+        lua_pushboolean(L, LuaClockPaused)
+    else
+        lua_pushnil(L);
+    lc_getturntimepaused:= 1;
+end;
+
 function lc_startghostpoints(L : Plua_State) : LongInt; Cdecl;
 begin
     if CheckLuaParamCount(L, 1, 'StartGhostPoints', 'count') then
@@ -4310,6 +4353,8 @@
 lua_register(luaState, _P'Explode', @lc_explode);
 lua_register(luaState, _P'SetTurnTimeLeft', @lc_setturntimeleft);
 lua_register(luaState, _P'SetReadyTimeLeft', @lc_setreadytimeleft);
+lua_register(luaState, _P'SetTurnTimePaused', @lc_setturntimepaused);
+lua_register(luaState, _P'GetTurnTimePaused', @lc_getturntimepaused);
 // drawn map functions
 lua_register(luaState, _P'AddPoint', @lc_addPoint);
 lua_register(luaState, _P'FlushPoints', @lc_flushPoints);
--- a/hedgewars/uSound.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uSound.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -490,11 +490,11 @@
             GetFallbackV := sndUhOh
     else if (snd in [sndDrat, sndBugger]) then
         GetFallbackV := sndStupid
-    else if (snd in [sndGonnaGetYou, sndCutItOut, sndLeaveMeAlone]) then
+    else if (snd in [sndGonnaGetYou, sndIllGetYou, sndJustYouWait, sndCutItOut, sndLeaveMeAlone]) then
         GetFallbackV := sndRegret
     else if (snd in [sndOhDear, sndSoLong]) then
         GetFallbackV := sndByeBye
-    else if (snd = sndWhatThe) then
+    else if (snd in [sndWhatThe, sndUhOh]) then
         GetFallbackV := sndNooo
     else if (snd = sndRunAway) then
         GetFallbackV := sndOops
@@ -502,14 +502,19 @@
         GetFallbackV := sndReinforce
     else if (snd in [sndAmazing, sndBrilliant, sndExcellent]) then
         GetFallbackV := sndEnemyDown
-    // Hmm is for enemy turn start
-    else if snd = sndHmm then
-        // these are not ideal fallbacks, but those were the voices which were used in older versions
-        // for enemy turn start
-        if random(2) = 0 then
-            GetFallbackV := sndIllGetYou
-        else
-            GetFallbackV := sndJustYouWait
+    else if (snd = sndPoisonCough) then
+        GetFallbackV := sndPoisonMoan
+    else if (snd = sndPoisonMoan) then
+        GetFallbackV := sndPoisonCough
+    else if (snd = sndFlawless) then
+        GetFallbackV := sndVictory
+    else if (snd = sndSameTeam) then
+        GetFallbackV := sndTraitor
+    else if (snd = sndMelon) then
+        GetFallbackV := sndCover
+    // sndHmm is used for enemy turn start, so sndHello is an "okay" replacement
+    else if (snd = sndHmm) then
+        GetFallbackV := sndHello
     else
         GetFallbackV := sndNone;
 end;
--- a/hedgewars/uStats.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uStats.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -67,6 +67,7 @@
     HitTargets  : LongWord = 0; // Target (gtTarget) hits per turn
     AmmoUsedCount : Longword = 0;
     AmmoDamagingUsed : boolean = false;
+    FirstBlood  : boolean = false;
     LeaveMeAlone : boolean = false;
     SkippedTurns: LongWord = 0;
     isTurnSkipped: boolean = false;
@@ -254,8 +255,11 @@
         killsCheck:= 0;
 
     // First blood (first damage, poison or kill)
-    if ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) and ((CurrentHedgehog^.stats.DamageGiven = DamageTotal) and (CurrentHedgehog^.stats.StepKills = KillsTotal) and (PoisonTotal = PoisonTurn + PoisonClan)) then
-        AddVoice(sndFirstBlood, CurrentTeam^.voicepack)
+    if (not FirstBlood) and ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) and ((CurrentHedgehog^.stats.DamageGiven = DamageTotal) and (CurrentHedgehog^.stats.StepKills = KillsTotal) and (PoisonTotal = PoisonTurn + PoisonClan)) then
+        begin
+        FirstBlood:= true;
+        AddVoice(sndFirstBlood, CurrentTeam^.voicepack);
+        end
 
     // Hog hurts, poisons or kills itself (except sacrifice)
     else if (CurrentHedgehog^.stats.Sacrificed = false) and ((CurrentHedgehog^.stats.StepDamageRecv > 0) or (CurrentHedgehog^.stats.StepPoisoned) or (CurrentHedgehog^.stats.StepDied)) then
@@ -598,6 +602,7 @@
     HitTargets  := 0;
     AmmoUsedCount := 0;
     AmmoDamagingUsed := false;
+    FirstBlood:= false;
     LeaveMeAlone := false;
     SkippedTurns:= 0;
     isTurnSkipped:= false;
--- a/hedgewars/uTeams.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uTeams.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -560,6 +560,7 @@
         // Some initial King buffs
         if (GameFlags and gfKing) <> 0 then
             begin
+            hasKing:= true;
             Hedgehogs[0].King:= true;
             Hedgehogs[0].Hat:= 'crown';
             Hedgehogs[0].Effects[hePoisoned] := 0;
--- a/hedgewars/uTypes.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uTypes.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -444,6 +444,7 @@
             voicepack: PVoicepack;
             PlayerHash: shortstring;   // md5 hash of player name. For temporary enabling of hats as thank you. Hashed for privacy of players
             stats: TTeamStats;
+            hasKing: boolean; // true if team has a living king
             hasGone: boolean;
             skippedTurns: Longword;
             isGoneFlagPendingToBeSet, isGoneFlagPendingToBeUnset: boolean;
@@ -511,7 +512,8 @@
             sidCmdHeaderTaunts, sidCmdSpeech, sidCmdThink, sidCmdYell,
             sidCmdSpeechNumberHint, sidCmdHsa, sidCmdHta, sidCmdHya,
             sidCmdHurrah, sidCmdIlovelotsoflemonade, sidCmdJuggle,
-            sidCmdRollup, sidCmdShrug, sidCmdWave, sidCmdUnknown);
+            sidCmdRollup, sidCmdShrug, sidCmdWave, sidCmdUnknown,
+            sidCmdHelpRoom, sidCmdHelpRoomFail);
 
     // Events that are important for the course of the game or at least interesting for other reasons
     TEventId = (eidDied, eidDrowned, eidRoundStart, eidRoundWin, eidRoundDraw,
--- a/hedgewars/uVariables.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uVariables.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -104,6 +104,7 @@
     IsGetAwayTime   : boolean;
     GameOver        : boolean;
     cSuddenDTurns   : LongInt;
+    LastSuddenDWarn : LongInt; // last round in which the last SD warning appeared. -2 = no warning so far
     cDamagePercent  : LongInt;
     cMineDudPercent : LongWord;
     cTemplateFilter : LongInt;
@@ -261,6 +262,9 @@
     LuaEndTurnRequested: boolean;
     LuaNoEndTurnTaunts: boolean;
 
+    // whether Lua requested to pause the clock
+    LuaClockPaused: boolean;
+
     MaskedSounds : array[TSound] of boolean;
 
     LastVoice : TVoice;
@@ -2798,12 +2802,13 @@
     TurnClockActive     := true;
     TagTurnTimeLeft     := 0;
     cSuddenDTurns       := 15;
+    LastSuddenDWarn     := -2;
     cDamagePercent      := 100;
     cRopePercent        := 100;
     cGetAwayTime        := 100;
     cMineDudPercent     := 0;
     cTemplateFilter     := 0;
-    cFeatureSize        := 50;
+    cFeatureSize        := 12;
     cMapGen             := mgRandom;
     cHedgehogTurnTime   := 45000;
     cMinesTime          := 3000;
--- a/hedgewars/uWorld.pas	Thu Dec 13 10:49:30 2018 -0500
+++ b/hedgewars/uWorld.pas	Thu Dec 13 10:51:07 2018 -0500
@@ -63,6 +63,7 @@
     , uCommands
     , uTeams
     , uDebug
+    , uInputHandler
 {$IFDEF USE_VIDEO_RECORDING}
     , uVideoRec
 {$ENDIF}
@@ -415,7 +416,10 @@
     STurns: LongInt;
     amSurface: PSDL_Surface;
     AMRect: TSDL_Rect;
-{$IFDEF USE_AM_NUMCOLUMN}tmpsurf: PSDL_Surface;{$ENDIF}
+{$IFDEF USE_AM_NUMCOLUMN}
+    tmpsurf: PSDL_Surface;
+    usesDefaultSlotKeys: boolean;
+{$ENDIF}
 begin
     if cOnlyStats then exit(nil);
 
@@ -451,6 +455,9 @@
 
     x:= AMRect.x;
     y:= AMRect.y;
+{$IFDEF USE_AM_NUMCOLUMN}
+    usesDefaultSlotKeys:= CheckDefaultSlotKeys;
+{$ENDIF USE_AM_NUMCOLUMN}
     for i:= 0 to cMaxSlotIndex do
         if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
             begin
@@ -460,7 +467,13 @@
             x:= AMRect.x;
 {$ENDIF}
 {$IFDEF USE_AM_NUMCOLUMN}
-            tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F' + IntToStr(i+1)), cWhiteColorChannels);
+            // Ammo slot number column
+            if usesDefaultSlotKeys then
+                // F1, F2, F3, F4, ...
+                tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F'+IntToStr(i+1)), cWhiteColorChannels)
+            else
+                // 1, 2, 3, 4, ...
+                tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i+1)), cWhiteColorChannels);
             copyToXY(tmpsurf, amSurface,
                      x + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.w shr 1),
                      y + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.h shr 1));
@@ -600,12 +613,14 @@
 
 if AMState = AMShowingUp then // show ammo menu
     begin
-    if (cReducedQuality and rqSlowMenu) <> 0 then
+    // No "appear" animation in low quality or playing with very short turn time.
+    if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
         begin
         AMShiftX:= 0;
         AMShiftY:= 0;
         AMState:= AMShowing;
         end
+    // "Appear" animation
     else
         if AMAnimState < 1 then
             begin
@@ -625,12 +640,14 @@
     end;
 if AMState = AMHiding then // hide ammo menu
     begin
-    if (cReducedQuality and rqSlowMenu) <> 0 then
+    // No "disappear" animation (see above)
+    if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
         begin
         AMShiftX:= AMShiftTargetX;
         AMShiftY:= AMShiftTargetY;
         AMState:= AMHidden;
         end
+    // "Disappear" animation
     else
         if AMAnimState < 1 then
             begin
@@ -1146,7 +1163,9 @@
             h:= -NameTagTex^.w - 24;
             if OwnerTex <> nil then
                 h:= h - OwnerTex^.w - 4;
+            Tint(TeamsArray[t]^.Clan^.Color shl 8 or $FF);
             DrawSpriteRotatedF(sprFinger, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFinger].Width div 4, 0, 1, -90);
+            untint;
             end;
         end;
       end;
@@ -1198,7 +1217,7 @@
     r: TSDL_Rect;
     s: shortstring;
     offsetX, offsetY, screenBottom: LongInt;
-    replicateToLeft, replicateToRight, tmp: boolean;
+    replicateToLeft, replicateToRight, tmp, isNotHiddenByCinematic: boolean;
 {$IFDEF USE_VIDEO_RECORDING}
     a: Byte;
 {$ENDIF}
@@ -1400,7 +1419,10 @@
         if CurAmmoType = amBee then
             spr:= sprTargetBee
         else
+            begin
             spr:= sprTargetP;
+            Tint(Team^.Clan^.Color shl 8 or $FF);
+            end;
         if replicateToLeft then
             begin
             ShiftWorld(-1);
@@ -1416,6 +1438,8 @@
             end;
 
         DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+        if spr = sprTargetP then
+            untint;
         end;
     end;
 
@@ -1442,7 +1466,8 @@
 // This scale is used to keep the various widgets at the same dimension at all zoom levels
 SetScale(cDefaultZoomLevel);
 
-// Cinematic Mode: Effects
+isNotHiddenByCinematic:= true;
+// Cinematic Mode: Determine effects and state
 if CinematicScript or (InCinematicMode and autoCameraOn
     and ((CurrentHedgehog = nil) or CurrentHedgehog^.Team^.ExtDriven
     or (CurrentHedgehog^.BotLevel <> 0) or (GameType = gmtDemo))) then
@@ -1451,7 +1476,10 @@
         begin
         inc(CinematicSteps, Lag);
         if CinematicSteps > 300 then
-        CinematicSteps:= 300;
+            begin
+            CinematicSteps:= 300;
+            isNotHiddenByCinematic:= false;
+            end;
         end;
     end
 else if CinematicSteps > 0 then
@@ -1461,22 +1489,8 @@
         CinematicSteps:= 0;
     end;
 
-// Cinematic Mode: Render black bars
-if CinematicSteps > 0 then
-    begin
-    r.x:= ViewLeftX;
-    r.w:= ViewWidth;
-    r.y:= ViewTopY;
-    CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
-    r.h:= CinematicBarH;
-    DrawRect(r, 0, 0, 0, $FF, true);
-    r.y:= ViewBottomY - r.h;
-    DrawRect(r, 0, 0, 0, $FF, true);
-    end;
-
-
 // Turn time
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
     begin
 {$IFDEF USE_TOUCH_INTERFACE}
     offsetX:= cScreenHeight - 13;
@@ -1515,34 +1529,14 @@
         DrawSprite(sprFrame, -(cScreenWidth shr 1) + t - 4 + offsetY, cScreenHeight - offsetX, 0);
         end;
 
-// Captions
-    DrawCaptions
     end;
 
-{$IFDEF USE_TOUCH_INTERFACE}
-// Draw buttons Related to the Touch interface
-DrawScreenWidget(@arrowLeft);
-DrawScreenWidget(@arrowRight);
-DrawScreenWidget(@arrowUp);
-DrawScreenWidget(@arrowDown);
-
-DrawScreenWidget(@fireButton);
-DrawScreenWidget(@jumpWidget);
-DrawScreenWidget(@AMWidget);
-DrawScreenWidget(@pauseButton);
-DrawScreenWidget(@utilityWidget);
-{$ENDIF}
-
 // Team bars
-if UIDisplay = uiAll then
+if (UIDisplay = uiAll) and (isNotHiddenByCinematic) then
     RenderTeamsHealth;
 
-// Lag alert
-if isInLag then
-    DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
-
 // Wind bar
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
     begin
 {$IFDEF USE_TOUCH_INTERFACE}
     offsetX:= cScreenHeight - 13;
@@ -1576,7 +1570,7 @@
     end;
 
 // Indicators for global effects (extra damage, low gravity)
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
     begin
 {$IFDEF USE_TOUCH_INTERFACE}
     offsetX:= (cScreenWidth shr 1) - 95;
@@ -1603,6 +1597,41 @@
         end;
     end;
 
+// Cinematic Mode: Render black bars
+if CinematicSteps > 0 then
+    begin
+    r.x:= ViewLeftX;
+    r.w:= ViewWidth;
+    r.y:= ViewTopY;
+    CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
+    r.h:= CinematicBarH;
+    DrawRect(r, 0, 0, 0, $FF, true);
+    r.y:= ViewBottomY - r.h;
+    DrawRect(r, 0, 0, 0, $FF, true);
+    end;
+
+// Touchscreen interface widgets
+{$IFDEF USE_TOUCH_INTERFACE}
+DrawScreenWidget(@arrowLeft);
+DrawScreenWidget(@arrowRight);
+DrawScreenWidget(@arrowUp);
+DrawScreenWidget(@arrowDown);
+
+DrawScreenWidget(@fireButton);
+DrawScreenWidget(@jumpWidget);
+DrawScreenWidget(@AMWidget);
+DrawScreenWidget(@utilityWidget);
+DrawScreenWidget(@pauseButton);
+{$ENDIF}
+
+// Captions
+if UIDisplay <> uiNone then
+    DrawCaptions;
+
+// Lag alert
+if isInLag then
+    DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
+
 // Chat
 DrawChat;
 
--- a/qmlfrontend/CMakeLists.txt	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/CMakeLists.txt	Thu Dec 13 10:51:07 2018 -0500
@@ -15,6 +15,8 @@
     "team.cpp" "team.h"
     "engine_instance.cpp" "engine_instance.h"
     "preview_image_provider.cpp" "preview_image_provider.h"
-    "engine_interface.h")
+    "engine_interface.h"
+    "preview_acceptor.cpp" "preview_acceptor.h"
+    )
 
 target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick)
--- a/qmlfrontend/Page1.qml	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/Page1.qml	Thu Dec 13 10:51:07 2018 -0500
@@ -2,24 +2,31 @@
 import Hedgewars.Engine 1.0
 
 Page1Form {
+  property var hwEngine
+
+  Component {
+    id: hwEngineComponent
+
+    HWEngine {
+      engineLibrary: "./libhedgewars_engine.so"
+      previewAcceptor: PreviewAcceptor
+      onPreviewImageChanged: previewImage.source = "image://preview/image"
+      onPreviewIsRendering: previewImage.source = "qrc:/res/iconTime.png"
+    }
+  }
+
+  Component.onCompleted: {
+    hwEngine = hwEngineComponent.createObject()
+  }
+
   tickButton.onClicked: {
     gameView.tick(100)
   }
   gameButton.onClicked: {
-    var engineInstance = HWEngine.runQuickGame()
+    var engineInstance = hwEngine.runQuickGame()
     gameView.engineInstance = engineInstance
   }
   button1.onClicked: {
-    HWEngine.getPreview()
-  }
-
-  Connections {
-    target: HWEngine
-    onPreviewImageChanged: {
-      previewImage.source = "image://preview/image"
-    }
-    onPreviewIsRendering: {
-      previewImage.source = "qrc:/res/iconTime.png"
-    }
+    hwEngine.getPreview()
   }
 }
--- a/qmlfrontend/engine_instance.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/engine_instance.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -1,6 +1,7 @@
 #include "engine_instance.h"
 
 #include <QDebug>
+#include <QLibrary>
 #include <QOpenGLFunctions>
 #include <QSurface>
 
@@ -12,37 +13,92 @@
     return currentOpenglContext->getProcAddress(fn);
 }
 
-EngineInstance::EngineInstance(QObject* parent)
-    : QObject(parent), m_instance(Engine::start_engine()) {}
+EngineInstance::EngineInstance(const QString& libraryPath, QObject* parent)
+    : QObject(parent) {
+  QLibrary hwlib(libraryPath);
+
+  if (!hwlib.load())
+    qWarning() << "Engine library not found" << hwlib.errorString();
+
+  hedgewars_engine_protocol_version =
+      reinterpret_cast<Engine::hedgewars_engine_protocol_version_t*>(
+          hwlib.resolve("hedgewars_engine_protocol_version"));
+  start_engine =
+      reinterpret_cast<Engine::start_engine_t*>(hwlib.resolve("start_engine"));
+  generate_preview = reinterpret_cast<Engine::generate_preview_t*>(
+      hwlib.resolve("generate_preview"));
+  dispose_preview = reinterpret_cast<Engine::dispose_preview_t*>(
+      hwlib.resolve("dispose_preview"));
+  cleanup = reinterpret_cast<Engine::cleanup_t*>(hwlib.resolve("cleanup"));
+
+  send_ipc = reinterpret_cast<Engine::send_ipc_t*>(hwlib.resolve("send_ipc"));
+  read_ipc = reinterpret_cast<Engine::read_ipc_t*>(hwlib.resolve("read_ipc"));
 
-EngineInstance::~EngineInstance() { Engine::cleanup(m_instance); }
+  setup_current_gl_context =
+      reinterpret_cast<Engine::setup_current_gl_context_t*>(
+          hwlib.resolve("setup_current_gl_context"));
+  render_frame =
+      reinterpret_cast<Engine::render_frame_t*>(hwlib.resolve("render_frame"));
+  advance_simulation = reinterpret_cast<Engine::advance_simulation_t*>(
+      hwlib.resolve("advance_simulation"));
+
+  m_isValid = hedgewars_engine_protocol_version && start_engine &&
+              generate_preview && dispose_preview && cleanup && send_ipc &&
+              read_ipc && setup_current_gl_context && render_frame &&
+              advance_simulation;
+  emit isValidChanged(m_isValid);
+
+  if (isValid()) {
+    qDebug() << "Loaded engine library with protocol version"
+             << hedgewars_engine_protocol_version();
+
+    m_instance = start_engine();
+  }
+}
+
+EngineInstance::~EngineInstance() {
+  if (m_isValid) cleanup(m_instance);
+}
 
 void EngineInstance::sendConfig(const GameConfig& config) {
   for (auto b : config.config()) {
-    Engine::send_ipc(m_instance, reinterpret_cast<uint8_t*>(b.data()),
-                     static_cast<size_t>(b.size()));
+    send_ipc(m_instance, reinterpret_cast<uint8_t*>(b.data()),
+             static_cast<size_t>(b.size()));
   }
 }
 
 void EngineInstance::advance(quint32 ticks) {
-  Engine::advance_simulation(m_instance, ticks);
+  advance_simulation(m_instance, ticks);
 }
 
-void EngineInstance::renderFrame() { Engine::render_frame(m_instance); }
+void EngineInstance::renderFrame() { render_frame(m_instance); }
 
 void EngineInstance::setOpenGLContext(QOpenGLContext* context) {
   currentOpenglContext = context;
 
   auto size = context->surface()->size();
-  Engine::setup_current_gl_context(
-      m_instance, static_cast<quint16>(size.width()),
-      static_cast<quint16>(size.height()), &getProcAddress);
+  setup_current_gl_context(m_instance, static_cast<quint16>(size.width()),
+                           static_cast<quint16>(size.height()),
+                           &getProcAddress);
 }
 
-Engine::PreviewInfo EngineInstance::generatePreview() {
+QImage EngineInstance::generatePreview() {
   Engine::PreviewInfo pinfo;
 
-  Engine::generate_preview(m_instance, &pinfo);
+  generate_preview(m_instance, &pinfo);
+
+  QVector<QRgb> colorTable;
+  colorTable.resize(256);
+  for (int i = 0; i < 256; ++i) colorTable[i] = qRgba(255, 255, 0, i);
 
-  return pinfo;
+  QImage previewImage(pinfo.land, static_cast<int>(pinfo.width),
+                      static_cast<int>(pinfo.height), QImage::Format_Indexed8);
+  previewImage.setColorTable(colorTable);
+
+  // Cannot use it here, since QImage refers to original bytes
+  // dispose_preview(m_instance);
+
+  return previewImage;
 }
+
+bool EngineInstance::isValid() const { return m_isValid; }
--- a/qmlfrontend/engine_instance.h	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/engine_instance.h	Thu Dec 13 10:51:07 2018 -0500
@@ -1,31 +1,50 @@
 #ifndef ENGINEINSTANCE_H
 #define ENGINEINSTANCE_H
 
-#include "engine_interface.h"
-
+#include <QImage>
 #include <QObject>
 #include <QOpenGLContext>
 
+#include "engine_interface.h"
 #include "game_config.h"
 
 class EngineInstance : public QObject {
   Q_OBJECT
  public:
-  explicit EngineInstance(QObject* parent = nullptr);
+  explicit EngineInstance(const QString& libraryPath,
+                          QObject* parent = nullptr);
   ~EngineInstance();
 
+  Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged)
+
   void sendConfig(const GameConfig& config);
   void advance(quint32 ticks);
   void renderFrame();
   void setOpenGLContext(QOpenGLContext* context);
-  Engine::PreviewInfo generatePreview();
+  QImage generatePreview();
+
+  bool isValid() const;
 
  signals:
+  void isValidChanged(bool isValid);
 
  public slots:
 
  private:
   Engine::EngineInstance* m_instance;
+
+  Engine::hedgewars_engine_protocol_version_t*
+      hedgewars_engine_protocol_version;
+  Engine::start_engine_t* start_engine;
+  Engine::generate_preview_t* generate_preview;
+  Engine::dispose_preview_t* dispose_preview;
+  Engine::cleanup_t* cleanup;
+  Engine::send_ipc_t* send_ipc;
+  Engine::read_ipc_t* read_ipc;
+  Engine::setup_current_gl_context_t* setup_current_gl_context;
+  Engine::render_frame_t* render_frame;
+  Engine::advance_simulation_t* advance_simulation;
+  bool m_isValid;
 };
 
 #endif  // ENGINEINSTANCE_H
--- a/qmlfrontend/engine_interface.h	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/engine_interface.h	Thu Dec 13 10:51:07 2018 -0500
@@ -18,10 +18,11 @@
   unsigned char* land;
 } PreviewInfo;
 
-typedef uint32_t protocol_version_t();
+typedef uint32_t hedgewars_engine_protocol_version_t();
 typedef EngineInstance* start_engine_t();
 typedef void generate_preview_t(EngineInstance* engine_state,
                                 PreviewInfo* preview);
+typedef void dispose_preview_t(EngineInstance* engine_state);
 typedef void cleanup_t(EngineInstance* engine_state);
 
 typedef void send_ipc_t(EngineInstance* engine_state, uint8_t* buf,
@@ -36,18 +37,6 @@
 
 typedef bool advance_simulation_t(EngineInstance* engine_state, uint32_t ticks);
 
-extern protocol_version_t* protocol_version;
-extern start_engine_t* start_engine;
-extern generate_preview_t* generate_preview;
-extern cleanup_t* cleanup;
-
-extern send_ipc_t* send_ipc;
-extern read_ipc_t* read_ipc;
-
-extern setup_current_gl_context_t* setup_current_gl_context;
-extern render_frame_t* render_frame;
-extern advance_simulation_t* advance_simulation;
-
 #ifdef __cplusplus
 }
 };
--- a/qmlfrontend/hwengine.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/hwengine.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -1,64 +1,33 @@
+#include "hwengine.h"
+
 #include <QDebug>
-#include <QLibrary>
-#include <QQmlEngine>
+#include <QImage>
 #include <QUuid>
 
 #include "engine_instance.h"
 #include "engine_interface.h"
 #include "game_view.h"
-#include "preview_image_provider.h"
-
-#include "hwengine.h"
+#include "preview_acceptor.h"
 
-HWEngine::HWEngine(QQmlEngine* engine, QObject* parent)
-    : QObject(parent),
-      m_engine(engine),
-      m_previewProvider(new PreviewImageProvider()) {
-  m_engine->addImageProvider(QLatin1String("preview"), m_previewProvider);
-}
+HWEngine::HWEngine(QObject* parent) : QObject(parent) {}
 
 HWEngine::~HWEngine() {}
 
-static QObject* hwengine_singletontype_provider(QQmlEngine* engine,
-                                                QJSEngine* scriptEngine) {
-  Q_UNUSED(scriptEngine)
-
-  HWEngine* hwengine = new HWEngine(engine);
-  return hwengine;
-}
-
-void HWEngine::exposeToQML() {
-  qDebug("HWEngine::exposeToQML");
-  qmlRegisterSingletonType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine",
-                                     hwengine_singletontype_provider);
-  qmlRegisterType<GameView>("Hedgewars.Engine", 1, 0, "GameView");
-  qmlRegisterUncreatableType<EngineInstance>("Hedgewars.Engine", 1, 0,
-                                             "EngineInstance",
-                                             "Create by HWEngine run methods");
-}
-
 void HWEngine::getPreview() {
   emit previewIsRendering();
 
   m_gameConfig = GameConfig();
   m_gameConfig.cmdSeed(QUuid::createUuid().toByteArray());
 
-  EngineInstance engine;
+  EngineInstance engine(m_engineLibrary);
+  if (!engine.isValid())  // TODO: error notification
+    return;
+
   engine.sendConfig(m_gameConfig);
 
-  Engine::PreviewInfo preview = engine.generatePreview();
-
-  QVector<QRgb> colorTable;
-  colorTable.resize(256);
-  for (int i = 0; i < 256; ++i) colorTable[i] = qRgba(255, 255, 0, i);
+  QImage previewImage = engine.generatePreview();
 
-  QImage previewImage(preview.land, static_cast<int>(preview.width),
-                      static_cast<int>(preview.height),
-                      QImage::Format_Indexed8);
-  previewImage.setColorTable(colorTable);
-  previewImage.detach();
-
-  m_previewProvider->setImage(previewImage);
+  if (m_previewAcceptor) m_previewAcceptor->setImage(previewImage);
 
   emit previewImageChanged();
   // m_runQueue->queue(m_gameConfig);
@@ -74,9 +43,28 @@
   m_gameConfig.cmdTeam(team1);
   m_gameConfig.cmdTeam(team2);
 
-  EngineInstance* engine = new EngineInstance(this);
+  EngineInstance* engine = new EngineInstance(m_engineLibrary, this);
+
   return engine;
   // m_runQueue->queue(m_gameConfig);
 }
 
 int HWEngine::previewHedgehogsCount() const { return m_previewHedgehogsCount; }
+
+PreviewAcceptor* HWEngine::previewAcceptor() const { return m_previewAcceptor; }
+
+QString HWEngine::engineLibrary() const { return m_engineLibrary; }
+
+void HWEngine::setPreviewAcceptor(PreviewAcceptor* previewAcceptor) {
+  if (m_previewAcceptor == previewAcceptor) return;
+
+  m_previewAcceptor = previewAcceptor;
+  emit previewAcceptorChanged(m_previewAcceptor);
+}
+
+void HWEngine::setEngineLibrary(const QString& engineLibrary) {
+  if (m_engineLibrary == engineLibrary) return;
+
+  m_engineLibrary = engineLibrary;
+  emit engineLibraryChanged(m_engineLibrary);
+}
--- a/qmlfrontend/hwengine.h	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/hwengine.h	Thu Dec 13 10:51:07 2018 -0500
@@ -8,25 +8,33 @@
 #include "game_config.h"
 
 class QQmlEngine;
-class PreviewImageProvider;
 class EngineInstance;
+class PreviewAcceptor;
 
 class HWEngine : public QObject {
   Q_OBJECT
 
   Q_PROPERTY(int previewHedgehogsCount READ previewHedgehogsCount NOTIFY
                  previewHedgehogsCountChanged)
+  Q_PROPERTY(PreviewAcceptor* previewAcceptor READ previewAcceptor WRITE
+                 setPreviewAcceptor NOTIFY previewAcceptorChanged)
+  Q_PROPERTY(QString engineLibrary READ engineLibrary WRITE setEngineLibrary
+                 NOTIFY engineLibraryChanged)
 
  public:
-  explicit HWEngine(QQmlEngine* engine, QObject* parent = nullptr);
+  explicit HWEngine(QObject* parent = nullptr);
   ~HWEngine();
 
-  static void exposeToQML();
-
   Q_INVOKABLE void getPreview();
   Q_INVOKABLE EngineInstance* runQuickGame();
 
   int previewHedgehogsCount() const;
+  PreviewAcceptor* previewAcceptor() const;
+  QString engineLibrary() const;
+
+ public slots:
+  void setPreviewAcceptor(PreviewAcceptor* previewAcceptor);
+  void setEngineLibrary(const QString& engineLibrary);
 
  signals:
   void previewIsRendering();
@@ -34,12 +42,15 @@
   void previewHogCountChanged(int count);
   void gameFinished();
   void previewHedgehogsCountChanged(int previewHedgehogsCount);
+  void previewAcceptorChanged(PreviewAcceptor* previewAcceptor);
+  void engineLibraryChanged(const QString& engineLibrary);
 
  private:
   QQmlEngine* m_engine;
-  PreviewImageProvider* m_previewProvider;
   GameConfig m_gameConfig;
   int m_previewHedgehogsCount;
+  PreviewAcceptor* m_previewAcceptor;
+  QString m_engineLibrary;
 };
 
 #endif  // HWENGINE_H
--- a/qmlfrontend/main.cpp	Thu Dec 13 10:49:30 2018 -0500
+++ b/qmlfrontend/main.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -4,66 +4,34 @@
 #include <QQmlApplicationEngine>
 
 #include "engine_interface.h"
+#include "game_view.h"
 #include "hwengine.h"
+#include "preview_acceptor.h"
 
-namespace Engine {
-protocol_version_t* protocol_version;
-start_engine_t* start_engine;
-generate_preview_t* generate_preview;
-cleanup_t* cleanup;
-send_ipc_t* send_ipc;
-read_ipc_t* read_ipc;
-setup_current_gl_context_t* setup_current_gl_context;
-render_frame_t* render_frame;
-advance_simulation_t* advance_simulation;
-};  // namespace Engine
-
-void loadEngineLibrary() {
-#ifdef Q_OS_WIN
-  QLibrary hwlib("./libhedgewars_engine.dll");
-#else
-  QLibrary hwlib("./libhedgewars_engine.so");
-#endif
-
-  if (!hwlib.load())
-    qWarning() << "Engine library not found" << hwlib.errorString();
+namespace Engine {};  // namespace Engine
 
-  Engine::protocol_version = reinterpret_cast<Engine::protocol_version_t*>(
-      hwlib.resolve("protocol_version"));
-  Engine::start_engine =
-      reinterpret_cast<Engine::start_engine_t*>(hwlib.resolve("start_engine"));
-  Engine::generate_preview = reinterpret_cast<Engine::generate_preview_t*>(
-      hwlib.resolve("generate_preview"));
-  Engine::cleanup =
-      reinterpret_cast<Engine::cleanup_t*>(hwlib.resolve("cleanup"));
+static QObject* previewacceptor_singletontype_provider(
+    QQmlEngine* engine, QJSEngine* scriptEngine) {
+  Q_UNUSED(scriptEngine)
 
-  Engine::send_ipc =
-      reinterpret_cast<Engine::send_ipc_t*>(hwlib.resolve("send_ipc"));
-  Engine::read_ipc =
-      reinterpret_cast<Engine::read_ipc_t*>(hwlib.resolve("read_ipc"));
-
-  Engine::setup_current_gl_context =
-      reinterpret_cast<Engine::setup_current_gl_context_t*>(
-          hwlib.resolve("setup_current_gl_context"));
-  Engine::render_frame =
-      reinterpret_cast<Engine::render_frame_t*>(hwlib.resolve("render_frame"));
-  Engine::advance_simulation = reinterpret_cast<Engine::advance_simulation_t*>(
-      hwlib.resolve("advance_simulation"));
-
-  if (Engine::protocol_version)
-    qDebug() << "Loaded engine library with protocol version"
-             << Engine::protocol_version();
+  PreviewAcceptor* acceptor = new PreviewAcceptor(engine);
+  return acceptor;
 }
 
 int main(int argc, char* argv[]) {
   QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
   QGuiApplication app(argc, argv);
 
-  loadEngineLibrary();
-
   QQmlApplicationEngine engine;
 
-  HWEngine::exposeToQML();
+  qmlRegisterSingletonType<PreviewAcceptor>(
+      "Hedgewars.Engine", 1, 0, "PreviewAcceptor",
+      previewacceptor_singletontype_provider);
+  qmlRegisterType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine");
+  qmlRegisterType<GameView>("Hedgewars.Engine", 1, 0, "GameView");
+  qmlRegisterUncreatableType<EngineInstance>("Hedgewars.Engine", 1, 0,
+                                             "EngineInstance",
+                                             "Create by HWEngine run methods");
 
   engine.load(QUrl(QLatin1String("qrc:/main.qml")));
   if (engine.rootObjects().isEmpty()) return -1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/preview_acceptor.cpp	Thu Dec 13 10:51:07 2018 -0500
@@ -0,0 +1,15 @@
+#include "preview_acceptor.h"
+
+#include <QImage>
+#include <QQmlEngine>
+
+#include "preview_image_provider.h"
+
+PreviewAcceptor::PreviewAcceptor(QQmlEngine *engine, QObject *parent)
+    : QObject(parent), m_previewProvider(new PreviewImageProvider()) {
+  engine->addImageProvider(QLatin1String("preview"), m_previewProvider);
+}
+
+void PreviewAcceptor::setImage(const QImage &preview) {
+  m_previewProvider->setImage(preview);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/preview_acceptor.h	Thu Dec 13 10:51:07 2018 -0500
@@ -0,0 +1,21 @@
+#ifndef PREVIEW_ACCEPTOR_H
+#define PREVIEW_ACCEPTOR_H
+
+#include <QObject>
+
+class QQmlEngine;
+class PreviewImageProvider;
+
+class PreviewAcceptor : public QObject {
+  Q_OBJECT
+ public:
+  explicit PreviewAcceptor(QQmlEngine *engine, QObject *parent = nullptr);
+
+ public slots:
+  void setImage(const QImage &preview);
+
+ private:
+  PreviewImageProvider *m_previewProvider;
+};
+
+#endif  // PREVIEW_ACCEPTOR_H
--- a/rust/hedgewars-checker/src/main.rs	Thu Dec 13 10:49:30 2018 -0500
+++ b/rust/hedgewars-checker/src/main.rs	Thu Dec 13 10:51:07 2018 -0500
@@ -175,7 +175,7 @@
 fn get_protocol_number(executable: &str) -> std::io::Result<u32> {
     let output = Command::new(executable).arg("--protocol").output()?;
 
-    Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().as_str()).unwrap_or(55))
+    Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
 }
 
 fn main() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/Cargo.toml	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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	Thu Dec 13 10:51:07 2018 -0500
@@ -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
--- a/rust/lib-hedgewars-engine/src/lib.rs	Thu Dec 13 10:49:30 2018 -0500
+++ b/rust/lib-hedgewars-engine/src/lib.rs	Thu Dec 13 10:51:07 2018 -0500
@@ -25,8 +25,8 @@
 }
 
 #[no_mangle]
-pub extern "C" fn protocol_version() -> u32 {
-    56
+pub extern "C" fn hedgewars_engine_protocol_version() -> u32 {
+    58
 }
 
 #[no_mangle]
@@ -42,14 +42,19 @@
 
     (*engine_state).world.generate_preview();
 
-    let land_preview = (*engine_state).world.preview();
+    if let Some(land_preview) = (*engine_state).world.preview() {
+        *preview = PreviewInfo {
+            width: land_preview.width() as u32,
+            height: land_preview.height() as u32,
+            hedgehogs_number: 0,
+            land: land_preview.raw_pixels().as_ptr(),
+        };
+    }
+}
 
-    *preview = PreviewInfo {
-        width: land_preview.width() as u32,
-        height: land_preview.height() as u32,
-        hedgehogs_number: 0,
-        land: land_preview.raw_pixels().as_ptr(),
-    };
+#[no_mangle]
+pub extern "C" fn dispose_preview(engine_state: &mut EngineInstance, preview: &mut PreviewInfo) {
+    (*engine_state).world.dispose_preview();
 }
 
 #[no_mangle]
--- a/rust/lib-hedgewars-engine/src/world.rs	Thu Dec 13 10:49:30 2018 -0500
+++ b/rust/lib-hedgewars-engine/src/world.rs	Thu Dec 13 10:51:07 2018 -0500
@@ -26,7 +26,7 @@
 
 pub struct World {
     random_numbers_gen: LaggedFibonacciPRNG,
-    preview: Land2D<u8>,
+    preview: Option<Land2D<u8>>,
     game_state: Option<GameState>,
 }
 
@@ -34,7 +34,7 @@
     pub fn new() -> Self {
         Self {
             random_numbers_gen: LaggedFibonacciPRNG::new(&[]),
-            preview: Land2D::new(Size::new(0, 0), 0),
+            preview: None,
             game_state: None,
         }
     }
@@ -43,7 +43,7 @@
         self.random_numbers_gen = LaggedFibonacciPRNG::new(seed);
     }
 
-    pub fn preview(&self) -> &Land2D<u8> {
+    pub fn preview(&self) -> &Option<Land2D<u8>> {
         &self.preview
     }
 
@@ -63,7 +63,11 @@
 
         let params = LandGenerationParameters::new(0u8, u8::max_value(), 5, false, false);
         let landgen = TemplatedLandGenerator::new(template());
-        self.preview = landgen.generate_land(&params, &mut self.random_numbers_gen);
+        self.preview = Some(landgen.generate_land(&params, &mut self.random_numbers_gen));
+    }
+
+    pub fn dispose_preview(&mut self) {
+        self.preview = None
     }
 
     pub fn init(&mut self, template: OutlineTemplate) {
Binary file share/hedgewars/Data/Graphics/Finger.png has changed
Binary file share/hedgewars/Data/Graphics/Switch.png has changed
Binary file share/hedgewars/Data/Graphics/Targetp.png has changed
Binary file share/hedgewars/Data/Graphics/Targetp@2x.png has changed
--- a/share/hedgewars/Data/Locale/de.txt	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Locale/de.txt	Thu Dec 13 10:51:07 2018 -0500
@@ -1420,3 +1420,5 @@
 06:24=/shrug: Igel mit den Achseln zucken lassen
 06:25=/wave: Igel winken lassen
 06:26=Unbekannter Befehl oder ungültige Parameter. Sag »/help« im Chat für eine Liste an Befehlen.
+06:27=/help room: Raum-Chatbefehle auflisten
+06:28=Du bist nicht online!
--- a/share/hedgewars/Data/Locale/en.txt	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Locale/en.txt	Thu Dec 13 10:51:07 2018 -0500
@@ -1325,3 +1325,5 @@
 06:24=/shrug: Make hedgehog shrug
 06:25=/wave: Make hedgehog wave its hand
 06:26=Unknown command or invalid parameters. Say “/help” in chat for a list of commands.
+06:27=/help room: List room chat commands
+06:28=You're not online!
--- a/share/hedgewars/Data/Maps/Basketball/map.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Maps/Basketball/map.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -10,7 +10,6 @@
 	CaseFreq = 0
 	MinesNum = 0
 	Explosives = 0
-	Delay = 500
     Map = 'BasketballField'
 	-- Disable Sudden Death
 	WaterRise = 0
--- a/share/hedgewars/Data/Maps/FlightJoust/map.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Maps/FlightJoust/map.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -31,7 +31,6 @@
     CaseFreq = 0 
     MinesNum = 0 
     Explosives = 0 
-    Delay = 500 
     SuddenDeathTurns = 99999 -- "disable" sudden death
     Theme = Compost
 end
--- a/share/hedgewars/Data/Maps/Knockball/map.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Maps/Knockball/map.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -10,7 +10,6 @@
 	CaseFreq = 0
 	MinesNum = 0
 	Explosives = 0
-	Delay = 500
 	-- Disable Sudden Death
 	WaterRise = 0
 	HealthDecrease = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/backstab.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/backstab.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -1016,7 +1016,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10 
 	Map = "Cave"
 	Theme = "Nature"
 	WaterRise = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/enemy.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/enemy.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -598,7 +598,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10 
   Map = "Islands"
 	Theme = "EarthRise"
   SuddenDeathTurns = 20
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/epil.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/epil.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -395,7 +395,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10 
   Map = "Hogville"
 	Theme = "Nature"
   -- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/family.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/family.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -580,7 +580,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10 
   MapGen = mgDrawn
 	Theme = "Hell"
   SuddenDeathTurns = 35
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -150,20 +150,32 @@
 princessFace = "Left"
 elderFace = "Left"
 
+local ctrlJump, ctrlMissionPanel, ctrlAttack
+if INTERFACE == "touch" then
+    ctrlJump = loc("Long Jump: Tap the [Curvy Arrow] button for long")
+    ctrlMissionPanel = loc("Hint: Pause the game to review the mission texts.")
+    ctrlAttack = loc("Attack: Tap the [Bomb]")
+else
+    ctrlJump = loc("Long Jump: [Enter]")
+    ctrlMissionPanel = loc("Hint: Hold down [M] to review the mission texts.")
+    ctrlAttack = loc("Attack: [Space]")
+end
+
 goals = {
-  [startDialogue] = {loc("First Blood"), loc("First Steps"), loc("Press [Left] or [Right] to move around, [Enter] to jump"), 1, 4000},
-  [onShroomAnim] = {loc("First Blood"), loc("A leap in a leap"), loc("Go on top of the flower") .. "|" .. loc("Hint: Hold down [M] to review the mission texts."), 1, 7000},
-  [onFlowerAnim] = {loc("First Blood"), loc("Hightime"), loc("Collect the crate on the right.|Hint: Select the rope, [Up] or [Down] to aim, [Space] to fire, directional keys to move.|Ropes can be fired again in the air!"), 1, 7000},
-  [tookParaAnim] = {loc("First Blood"), loc("Omnivore"), loc("Get on the head of the mole"), 1, 4000},
-  [onMoleHeadAnim] = {loc("First Blood"), loc("The Leap of Faith"), loc("Use the parachute ([Space] while in air) to get the next crate"), 1, 4000},
+  [startDialogue] = {loc("First Blood"), loc("First Steps"), loc("Press [Left] or [Right] to move around, [Long Jump] to jump forwards.") .. "| |" .. ctrlJump, 1, 4000},
+  [onShroomAnim] = {loc("First Blood"), loc("A leap in a leap"), loc("Go on top of the flower.") .. "|" .. ctrlMissionPanel, 1, 7000},
+  [onFlowerAnim] = {loc("First Blood"), loc("Hightime"), loc("Collect the crate on the right.") .. "|" .. loc("Hint: Select the rope, [Up] or [Down] to aim, [Attack] to fire, directional keys to move.") .. "|" .. loc("Ropes can be fired again in the air!") .. "| |" .. ctrlAttack, 1, 7000},
+  [tookParaAnim] = {loc("First Blood"), loc("Omnivore"), loc("Get on the head of the mole."), 1, 4000},
+  [onMoleHeadAnim] = {loc("First Blood"), loc("The Leap of Faith"), loc("Use the parachute to get the next crate.") .. "|" .. loc("Hint: Just select the parachute, it opens automatically when you fall."), 1, 4000},
   [tookRope2Anim] = {loc("First Blood"), loc("The Rising"), loc("Get that crate!"), 1, 4000},
-  [tookPunchAnim] = {loc("First Blood"), loc("The Slaughter"), loc("Destroy the targets!|Hint: Select the Shoryuken and hit [Space]|P.S. You can use it mid-air."), 1, 5000},
+  [tookPunchAnim] = {loc("First Blood"), loc("The Slaughter"), loc("Destroy the targets!") .. "|" .. loc("Hint: Select the Shoryuken and hit [Attack].|P.S.: You can use it mid-air.") .. "| |" .. ctrlAttack, 1, 5000},
   [challengeAnim] = {loc("First Blood"), loc("The Crate Frenzy"), loc("Collect the crates within the time limit!|If you fail, you'll have to try again."), 1, 5000},
   [challengeFailedAnim] = {loc("First Blood"), loc("The Crate Frenzy"), loc("Collect the crates within the time limit!|If you fail, you'll have to try again."), 1, 5000},
   [challengeCompletedAnim] = {loc("First Blood"), loc("The Ultimate Weapon"), loc("Get that crate!"), 1, 5000},
   [beforeKillAnim] = {loc("First Blood"), loc("The First Blood"), loc("Kill the cannibal!"), 1, 5000},
-  [closeCannim] = {loc("First Blood"), loc("The First Blood"), loc("KILL IT!"), 1, 5000}
+  [closeCannim] = {loc("First Blood"), loc("The First Blood"), loc("KILL IT!"), 1, 5000},
 }
+
 -----------------------------Animations--------------------------------
 function Skipanim(anim)
   AnimSwitchHog(youngh)
@@ -214,7 +226,7 @@
   table.insert(startDialogue, {func = AnimJump, args = {youngh, "long"}})
   table.insert(startDialogue, {func = AnimTurn, args = {princess, "Right"}})
   table.insert(startDialogue, {func = AnimSwitchHog, args = {youngh}})
-  table.insert(startDialogue, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("First Steps"), loc("Press [Left] or [Right] to move around, [Enter] to jump"), 1, 4000}})
+  table.insert(startDialogue, {func = AnimShowMission, args = {youngh, unpack(goals[startDialogue])}})
 
   AddSkipFunction(onShroomAnim, SkipOnShroom, {onShroomAnim})
   table.insert(onShroomAnim, {func = AnimSay, args = {elderh, loc("I can see you have been training diligently."), SAY_SAY, 4000}, skipFunc = Skipanim, skipArgs = onShroomAnim})
@@ -224,14 +236,14 @@
   table.insert(onShroomAnim, {func = AnimTurn, args = {elderh, "Left"}})
   table.insert(onShroomAnim, {func = AnimSay, args = {princess, loc("He moves like an eagle in the sky."), SAY_THINK, 4000}})
   table.insert(onShroomAnim, {func = AnimSwitchHog, args = {youngh}})
-  table.insert(onShroomAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("A leap in a leap"), loc("Go on top of the flower") .. "|" .. loc("Hint: Press [Esc] to review the mission texts."), 1, 7000}})
+  table.insert(onShroomAnim, {func = AnimShowMission, args = {youngh, unpack(goals[onShroomAnim])}})
 
   AddSkipFunction(onFlowerAnim, Skipanim, {onFlowerAnim})
   table.insert(onFlowerAnim, {func = AnimSay, args = {elderh, loc("See that crate farther on the right?"), SAY_SAY, 4000}})
   table.insert(onFlowerAnim, {func = AnimSay, args = {elderh, loc("Swing, Leaks A Lot, on the wings of the wind!"), SAY_SAY, 6000}})
   table.insert(onFlowerAnim, {func = AnimSay, args = {princess, loc("His arms are so strong!"), SAY_THINK, 4000}})
   table.insert(onFlowerAnim, {func = AnimSwitchHog, args = {youngh}})
-  table.insert(onFlowerAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("Hightime"), loc("Collect the crate on the right.|Hint: Select the rope, [Up] or [Down] to aim, [Space] to fire, directional keys to move.|Ropes can be fired again in the air!"), 1, 7000}})
+  table.insert(onFlowerAnim, {func = AnimShowMission, args = {youngh, unpack(goals[onFlowerAnim])}})
 
   AddSkipFunction(tookParaAnim, Skipanim, {tookParaAnim})
   table.insert(tookParaAnim, {func = AnimGearWait, args = {youngh, 1000}, skipFunc = Skipanim, skipArgs = tookParaAnim})
@@ -239,14 +251,14 @@
   table.insert(tookParaAnim, {func = AnimSay, args = {elderh, loc("Worry not, for it is a peaceful animal! There is no reason to be afraid..."), SAY_SHOUT, 5000}})
   table.insert(tookParaAnim, {func = AnimSay, args = {elderh, loc("We all know what happens when you get frightened..."), SAY_SAY, 4000}})
   table.insert(tookParaAnim, {func = AnimSay, args = {youngh, loc("So humiliating..."), SAY_SAY, 4000}})
-  table.insert(tookParaAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("Omnivore"), loc("Get on the head of the mole"), 1, 4000}})
+  table.insert(tookParaAnim, {func = AnimShowMission, args = {youngh, unpack(goals[tookParaAnim])}})
   table.insert(tookParaAnim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(onMoleHeadAnim, Skipanim, {onMoleHeadAnim})
   table.insert(onMoleHeadAnim, {func = AnimSay, args = {elderh, loc("Perfect! Now try to get the next crate without hurting yourself!"), SAY_SAY, 4000}, skipFunc = Skipanim, skipArgs = onMoleHeadAnim})
   table.insert(onMoleHeadAnim, {func = AnimSay, args = {elderh, loc("The giant umbrella from the last crate should help break the fall."), SAY_SAY, 4000}})
   table.insert(onMoleHeadAnim, {func = AnimSay, args = {princess, loc("He's so brave..."), SAY_THINK, 4000}})
-  table.insert(onMoleHeadAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The Leap of Faith"), loc("Use the parachute ([Space] while in air) to get the next crate"), 1, 4000}})
+  table.insert(onMoleHeadAnim, {func = AnimShowMission, args = {youngh, unpack(goals[onMoleHeadAnim])}})
   table.insert(onMoleHeadAnim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(pastMoleHeadAnim, Skipanim, {pastMoleHeadAnim})
@@ -257,13 +269,13 @@
   AddSkipFunction(tookRope2Anim, Skipanim, {tookRope2Anim})
   table.insert(tookRope2Anim, {func = AnimSay, args = {elderh, loc("Impressive...you are still dry as the corpse of a hawk after a week in the desert..."), SAY_SAY, 5000}, skipFunc = Skipanim, skipArgs = tookRope2Anim})
   table.insert(tookRope2Anim, {func = AnimSay, args = {elderh, loc("You probably know what to do next..."), SAY_SAY, 4000}})
-  table.insert(tookRope2Anim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The Rising"), loc("Get that crate!"), 1, 4000}})
+  table.insert(tookRope2Anim, {func = AnimShowMission, args = {youngh, unpack(goals[tookRope2Anim])}})
   table.insert(tookRope2Anim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(tookPunchAnim, Skipanim, {tookPunchAnim})
   table.insert(tookPunchAnim, {func = AnimSay, args = {elderh, loc("It is time to practice your fighting skills."), SAY_SAY, 4000}})
   table.insert(tookPunchAnim, {func = AnimSay, args = {elderh, loc("Imagine those targets are the wolves that killed your parents! Take your anger out on them!"), SAY_SAY, 5000}})
-  table.insert(tookPunchAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The Slaughter"), loc("Destroy the targets!|Hint: Select the Shoryuken and hit [Space]|P.S. You can use it mid-air."), 1, 5000}})
+  table.insert(tookPunchAnim, {func = AnimShowMission, args = {youngh, unpack(goals[tookPunchAnim])}})
   table.insert(tookPunchAnim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(challengeAnim, Skipanim, {challengeAnim})
@@ -276,7 +288,7 @@
 
   AddSkipFunction(challengeFailedAnim, Skipanim, {challengeFailedAnim})
   table.insert(challengeFailedAnim, {func = AnimSay, args = {elderh, loc("Hmmm...perhaps a little more time will help."), SAY_SAY, 4000}, skipFunc = Skipanim, skipArgs = challengeFailedAnim})
-  table.insert(challengeFailedAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The Crate Frenzy"), loc("Collect the crates within the time limit!|If you fail, you'll have to try again."), 1, 5000}})
+  table.insert(challengeFailedAnim, {func = AnimShowMission, args = {youngh, unpack(goals[challengeFailedAnim])}})
   table.insert(challengeFailedAnim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(challengeCompletedAnim, Skipanim, {challengeCompletedAnim})
@@ -284,7 +296,7 @@
   table.insert(challengeCompletedAnim, {func = AnimSay, args = {elderh, loc("You have proven yourself worthy to see our most ancient secret!"), SAY_SAY, 4000}})
   table.insert(challengeCompletedAnim, {func = AnimSay, args = {elderh, loc("The weapon in that last crate was bestowed upon us by the ancients!"), SAY_SAY, 4000}})
   table.insert(challengeCompletedAnim, {func = AnimSay, args = {elderh, loc("Use it with precaution!"), SAY_SAY, 4000}})
-  table.insert(challengeCompletedAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The Ultimate Weapon"), loc("Get that crate!"), 1, 5000}})
+  table.insert(challengeCompletedAnim, {func = AnimShowMission, args = {youngh, unpack(goals[challengeCompletedAnim])}})
   table.insert(challengeCompletedAnim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(beforeKillAnim, Skipanim, {beforeKillAnim})
@@ -294,7 +306,7 @@
   table.insert(beforeKillAnim, {func = AnimWait, args = {cannibal, 1000}})
   table.insert(beforeKillAnim, {func = AnimSay, args = {elderh, loc("Destroy him, Leaks A Lot! He is responsible for the deaths of many of us!"), SAY_SHOUT, 4000}})
   table.insert(beforeKillAnim, {func = AnimSay, args = {cannibal, loc("Oh, my!"), SAY_THINK, 4000}})
-  table.insert(beforeKillAnim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The First Blood"), loc("Kill the cannibal!"), 1, 5000}})
+  table.insert(beforeKillAnim, {func = AnimShowMission, args = {youngh, unpack(goals[beforeKillAnim])}})
   table.insert(beforeKillAnim, {func = AnimSwitchHog, args = {youngh}})
 
   AddSkipFunction(closeCannim, Skipanim, {closeCannim})
@@ -303,7 +315,7 @@
   table.insert(closeCannim, {func = AnimSay, args = {cannibal, loc("If only I were given a chance to explain my being here..."), SAY_SAY, 4000}})
   table.insert(closeCannim, {func = AnimSay, args = {elderh, loc("Do not let his words fool you, young one! He will stab you in the back as soon as you turn away!"), SAY_SAY, 6000}})
   table.insert(closeCannim, {func = AnimSay, args = {elderh, loc("Here...pick your weapon!"), SAY_SAY, 5000}})
-  table.insert(closeCannim, {func = AnimShowMission, args = {youngh, loc("First Blood"), loc("The First Blood"), loc("KILL IT!"), 1, 5000}})
+  table.insert(closeCannim, {func = AnimShowMission, args = {youngh, unpack(goals[closeCannim])}})
   table.insert(closeCannim, {func = AnimSwitchHog, args = {youngh}})
 
   table.insert(cannKilledAnim, {func = AnimSay, args = {elderh, loc("Yes, yeees! You are now ready to enter the real world!"), SAY_SHOUT, 6000}})
@@ -410,7 +422,13 @@
 end
 
 function DoMovedUntilJump()
-  ShowMission(loc("First Blood"), loc("Step By Step"), loc("Hint: Double Jump - Press [Backspace] twice"), -amSkip, 0)
+  local msg
+  if INTERFACE == "touch" then
+     msg = loc("Hint: Double Jump - Tap the [Curvy Arrow] twice")
+  else
+     msg = loc("Hint: Double Jump - Press [Backspace] twice")
+  end
+  ShowMission(loc("First Blood"), loc("Step By Step"), msg, -amSkip, 0)
   AddEvent(CheckOnShroom, {}, DoOnShroom, {}, 0)
 end
 
@@ -601,7 +619,7 @@
   PutTargets(1)
   AddEvent(CheckTargetsKilled, {}, DoTargetsKilled, {}, 1)
   AddEvent(CheckCannibalKilled, {}, DoCannibalKilledEarly, {}, 0)
-  ShowMission(loc("First Blood"), loc("The Bull's Eye"), loc("Destroy the targets!|Hint: [Up], [Down] to aim, [Space] to shoot"), 1, 5000)
+  ShowMission(loc("First Blood"), loc("The Bull's Eye"), loc("Destroy the targets!") .. "| |" .. ctrlAttack, 1, 5000)
 end
 
 function CheckTargetsKilled()
@@ -725,7 +743,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10
 	Map = "A_Classic_Fairytale_first_blood"
 	Theme = "Nature"
 
@@ -754,7 +771,14 @@
   progress = tonumber(GetCampaignVar("Progress"))
   SetTurnTimeLeft(MAX_TURN_TIME)
   FollowGear(youngh)
-	ShowMission(loc("A Classic Fairytale"), loc("First Blood"), loc("Finish your training|Hint: Animations can be skipped with the [Precise] key."), -amSkip, 0)
+  local msgSkip
+  if INTERFACE == "touch" then
+    -- FIXME: Precise key is not available in Touch
+    msgSkip = ""
+  else
+    msgSkip = "|" .. loc("Hint: Cinematics can be skipped with the [Precise] key.")
+  end
+  ShowMission(loc("A Classic Fairytale"), loc("First Blood"), loc("Finish your training.") .. msgSkip, -amSkip, 0)
   HideHog(cannibal)
 
   AddAnim(startDialogue)
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/journey.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/journey.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -1080,7 +1080,6 @@
 		MinesTime = 5000
 	end
 	Explosives = 0
-	Delay = 5
     Map = "A_Classic_Fairytale_journey"
     Theme = "Nature"
 
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/queen.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/queen.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -784,7 +784,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10 
   MapGen = mgDrawn
 	Theme = "Hell"
   SuddenDeathTurns = 20
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/shadow.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/shadow.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -144,6 +144,17 @@
 cannibalDead = {}
 isHidden = {}
 
+local grenadeHint = loc("Grenade hint: Set timer with the [Timer] controls, aim with [Up]/[Down].") .. "|" ..
+                    loc("Hold [Attack] pressed to throw with more power.")
+if INTERFACE == "touch" then
+  grenadeHint = grenadeHint .. "|" ..
+                loc("Change detonation timer: Tap the [Clock]") .. "|" ..
+                loc("Attack: Tap the [Bomb]")
+else
+  grenadeHint = grenadeHint .. "|" ..
+                loc("Set detonation timer: [1]-[5]") .. "|" ..
+                loc("Attack: [Space]")
+end
 
 --------------------------Anim skip functions--------------------------
 function AfterRefusedAnim()
@@ -316,7 +327,7 @@
     return
   end
   stage = aloneStage
-  ShowMission(loc("The Shadow Falls"), loc("The Individualist"), loc("Defeat the cannibals!|Grenade hint: Set the timer with [1-5], aim with [Up]/[Down] and hold [Space] to set power"), 1, 8000)
+  ShowMission(loc("The Shadow Falls"), loc("The Individualist"), loc("Defeat the cannibals!") .. "|" .. grenadeHint, 1, 12000)
   AddAmmo(cannibals[6], amGrenade, 1)
   AddAmmo(cannibals[6], amFirePunch, 0)
   AddAmmo(cannibals[6], amBaseballBat, 0)
@@ -844,7 +855,13 @@
   if stage == loseStage then
     return
   end
-  ShowMission(loc("The Shadow Falls"), loc("Under Construction"), loc("Return to Leaks A Lot!") .. "|" .. loc("To place a girder, select it, use [Left] and [Right] to select angle and length, place with [Left Click]"), 1, 6000)
+  local ctrl = loc("Hint: To place a girder, select it,|then use [Left] and [Right] to select angle and length,|then choose a location for the girder.")
+  if INTERFACE == "touch" then
+    ctrl = ctrl .. "|" .. loc("Choose location: Tap the [Target] button, then tap on the spot you want to choose")
+  else
+    ctrl = ctrl .. "|" .. loc("Choose location: Left click")
+  end
+  ShowMission(loc("The Shadow Falls"), loc("Under Construction"), loc("Return to Leaks A Lot!") .. "|" .. ctrl, 1, 6000)
 end
 
 function CheckNeedWeapons()
@@ -874,7 +891,8 @@
   if stage == loseStage then
     return
   end
-  ShowMission(loc("The Shadow Falls"), loc("The guardian"), loc("Protect yourselves!|Grenade hint: Set the timer with [1-5], aim with [Up]/[Down] and hold [Space] to set power").."|"..loc("Leaks A Lot must survive!"), 1, 8000)
+
+  ShowMission(loc("The Shadow Falls"), loc("The guardian"), loc("Defeat the cannibals!") .."|".. loc("Leaks A Lot must survive!") .. "|" .. grenadeHint, 1, 12000)
   AddAmmo(dense, amSkip, 100)
   AddAmmo(dense, amSwitch, 100)
   AddAmmo(leaks, amSkip, 100)
@@ -1002,7 +1020,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 0
-	Delay = 10 
 	Map = "A_Classic_Fairytale_shadow"
 	Theme = "Nature"
 	-- Disable Sudden Death
@@ -1027,7 +1044,14 @@
   AddAnim(startDialogue)
   AddFunction({func = AfterStartDialogue, args = {}})
   AddEvent(CheckBrainiacDead, {}, DoBrainiacDead, {}, 0)
-  ShowMission(loc("The Shadow Falls"), loc("The First Encounter"), loc("Survive!|Hint: Cinematics can be skipped with the [Precise] key."), 1, 0)
+  local hint
+  if INTERFACE == "touch" then
+     -- FIXME: No precise key available in Touch yet.
+     hint = ""
+  else
+     hint = "|" .. loc("Hint: Cinematics can be skipped with the [Precise] key.")
+  end
+  ShowMission(loc("The Shadow Falls"), loc("The First Encounter"), loc("Survive!") .. hint, 1, 0)
 end
 
 function onGameTick()
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/united.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/united.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -432,7 +432,6 @@
 	MinesNum = 0
 	MinesTime = 3000
 	Explosives = 2
-	Delay = 10 
   Map = "Hogville"
 	Theme = "Nature"
 	-- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -107,7 +107,6 @@
 	CaseFreq = 0
 	MinesNum = 0
 	Explosives = 0
-	Delay = 5
 	-- Disable Sudden Death
 	WaterRise = 0
 	HealthDecrease = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death01.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death01.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -94,7 +94,6 @@
 	MinesNum = 3
 	MinesTime = 1500
 	Explosives = 2
-	Delay = 3
 	HealthCaseAmount = 50
 	-- gfTagTeam makes it easier to skip the PAotH team
 	GameFlags = gfTagTeam
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -66,7 +66,6 @@
 	Explosives = 0
 	Map = "death02_map"
 	Theme = "Hell"
-	Delay = 600 -- this makes the messages between turns more readable
 	-- Disable Sudden Death
 	WaterRise = 0
 	HealthDecrease = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert01.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert01.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -91,7 +91,6 @@
 	MinesNum = 0
 	MinesTime = 1
 	Explosives = 0
-	Delay = 3
 	HealthCaseAmount = 30
 	-- Disable Sudden Death
 	HealthDecrease = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert02.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert02.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -63,7 +63,6 @@
 	GameFlags = gfOneClanMode
 	Seed = 1
 	TurnTime = 8000
-	Delay = 2
 	CaseFreq = 0
 	HealthCaseAmount = 50
 	MinesNum = 500
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/fruit01.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/fruit01.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -112,7 +112,6 @@
 	MinesNum = 0
 	MinesTime = 1
 	Explosives = 0
-	Delay = 3
 	-- Disable Sudden Death
 	HealthDecrease = 0
 	WaterRise = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/fruit02.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/fruit02.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -87,7 +87,6 @@
 	MinesNum = 0
 	MinesTime = 1
 	Explosives = 0
-	Delay = 3
 	-- Disable Sudden Death
 	HealthDecrease = 0
 	WaterRise = 0
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/ice01.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/ice01.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -88,7 +88,6 @@
 	MinesNum = 0
 	MinesTime = 1
 	Explosives = 0
-	Delay = 3
 	Map = "ice01_map"
 	Theme = "Snow"
 	-- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon01.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon01.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -109,7 +109,6 @@
 	Explosives = 0
 	HealthDecrease = 0
 	WaterRise = 0
-	Delay = 5
 	Map = "moon01_map"
 	Theme = "Cheese" -- Because ofc moon is made of cheese :)
 	-- Hog Solo
--- a/share/hedgewars/Data/Missions/Challenge/Basic_Training_-_Sniper_Rifle.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Challenge/Basic_Training_-_Sniper_Rifle.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -123,8 +123,6 @@
 	MinesNum = 0
 	-- The number of explosives being placed
 	Explosives = 0
-	-- The delay between each round
-	Delay = 0
 	-- The map to be played
 	Map = "Ropes"
 	-- The theme to be used
--- a/share/hedgewars/Data/Missions/Challenge/User_Mission_-_Rope_Knock_Challenge.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Challenge/User_Mission_-_Rope_Knock_Challenge.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -154,7 +154,6 @@
 	GameFlags = gfBorder + gfSolidLand
 
 	TurnTime = 180 * 1000
-	Delay = 500
 	Map = "Ropes"
 	Theme = "Eyes"
 
--- a/share/hedgewars/Data/Missions/Challenge/User_Mission_-_That_Sinking_Feeling.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Challenge/User_Mission_-_That_Sinking_Feeling.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -36,10 +36,10 @@
 	MinesNum = 0
 	MinesTime  = 3000
 	Explosives = 0
-	Delay = 10
 	Map = "Islands"
 	Theme = "City"
-	SuddenDeathTurns = 1
+	HealthDecrease = 0
+	WaterRise = 0
 
 	AddTeam(loc("Hapless Hogs"), -1, "Simple", "Island", "Default")
 	hh[0] = AddHog(loc("Sinky"), 1, 100, "fr_lemon")
--- a/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Dangerous_Ducklings.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Dangerous_Ducklings.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -24,7 +24,6 @@
 	CaseFreq = 0 -- The frequency of crate drops
 	MinesNum = 0 -- The number of mines being placed
 	Explosives = 0 -- The number of explosives being placed
-	Delay = 0 -- The delay between each round
 	Map = "Bath" -- The map to be played
 	Theme = "Bath" -- The theme to be used
 	-- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Diver.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Diver.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -19,7 +19,6 @@
 	MinesNum = 0 -- The number of mines being placed
 	MinesTime  = 1000
 	Explosives = 0 -- The number of explosives being placed
-	Delay = 10 -- The delay between each round
 	Map = "Hydrant" -- The map to be played
 	Theme = "City" -- The theme to be used
 
--- a/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Spooky_Tree.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Spooky_Tree.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -23,7 +23,6 @@
 	MinesNum = 0 -- The number of mines being placed
 	MinesTime  = 1
 	Explosives = 0 -- The number of explosives being placed
-	Delay = 10 -- The delay between each round
 	Map = "Tree" -- The map to be played
 	Theme = "Halloween" -- The theme to be used
 	-- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Teamwork.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Teamwork.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -17,7 +17,6 @@
 	MinesNum = 0 -- The number of mines being placed
 	MinesTime  = 1
 	Explosives = 0 -- The number of explosives being placed
-	Delay = 10 -- The delay between each round
 	Map = "Mushrooms" -- The map to be played
 	Theme = "Nature" -- The theme to be used
 	-- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Teamwork_2.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Scenario/User_Mission_-_Teamwork_2.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -22,7 +22,6 @@
 	WaterRise = 0
 
 	Explosives = 0
-	Delay = 10
 	Map = "CrazyMission"
 	Theme = "CrazyMission"
 
--- a/share/hedgewars/Data/Missions/Scenario/portal.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Scenario/portal.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -13,7 +13,6 @@
 	CaseFreq = 0 -- The frequency of crate drops
 	MinesNum = 0 -- The number of mines being placed
 	Explosives = 0 -- The number of explosives being placed
-	Delay = 10 -- The delay between each round
 	Map = "portal" -- The map to be played
 	Theme = "Hell" -- The theme to be used
 	-- Disable Sudden Death
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Bazooka.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Bazooka.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -130,23 +130,42 @@
 end
 
 function newGamePhase()
+	local ctrl = ""
 	-- Spawn targets, update wind and ammo, show instructions
 	if gamePhase == 0 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Open ammo menu: [Right click]").."|"..
+			loc("Select weapon: [Left click]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Open ammo menu: Tap the [Suitcase]")
+		end
 		ShowMission(loc("Basic Bazooka Training"), loc("Select Weapon"), loc("To begin with the training, select the bazooka from the ammo menu!").."|"..
-		loc("Open ammo menu: [Right click]").."|"..
-		loc("Select weapon: [Left click]"), 2, 5000)
+		ctrl, 2, 5000)
 	elseif gamePhase == 1 then
-		ShowMission(loc("Basic Bazooka Training"), loc("My First Bazooka"), loc("Let's get started!").."|"..
+		if INTERFACE == "desktop" then
+			ctrl = loc("Attack: [Space]").."|"..
+			loc("Aim: [Up]/[Down]").."|"..
+			loc("Walk: [Left]/[Right]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Attack: Tap the [Bomb]").."|"..
+			loc("Aim: [Up]/[Down]").."|"..
+			loc("Walk: [Left]/[Right]")
+		end
+		ShowMission(loc("Basic Bazooka Training"), loc("My First Bazooka"),
+		loc("Let's get started!").."|"..
 		loc("Launch some bazookas to destroy the targets!").."|"..
 		loc("Hold the Attack key pressed for more power.").."|"..
 		loc("Don't hit yourself!").."|"..
-		loc("Attack: [Space]").."|"..
-		loc("Aim: [Up]/[Down]").."|"..
-		loc("Walk: [Left]/[Right]"), 2, 10000)
+		ctrl, 2, 10000)
 		spawnTargets()
 	elseif gamePhase == 2 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("You see the wind strength at the bottom right corner.")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("You see the wind strength at the top.")
+		end
 		ShowMission(loc("Basic Bazooka Training"), loc("Wind"), loc("Bazookas are influenced by wind.").."|"..
-		loc("You see the wind strength at the bottom right corner.").."|"..
+		ctrl.."|"..
 		loc("Destroy the targets!"), 2, 5000)
 		SetWind(50)
 		spawnTargets()
@@ -181,9 +200,12 @@
 		SetWind(-33)
 		spawnTargets()
 	elseif gamePhase == 6 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Precise Aim: [Left Shift] + [Up]/[Down]").."|"
+		end
 		ShowMission(loc("Basic Bazooka Training"), loc("Final Targets"),
 		loc("The final targets are quite tricky. You need to aim well.").."|"..
-		loc("Precise Aim: [Left Shift] + [Up]/[Down]").."|"..
+		ctrl..
 		loc("Hint: It might be easier if you vary the angle only slightly."),
 		2, 12000)
 		SetWind(75)
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Grenade.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Grenade.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -96,10 +96,14 @@
 		AddGear(945, 498, gtTarget, 0, 0, 0, 0)
 	-- Bounciness
 	elseif gamePhase == 4 then
-		AddGear(323, 960, gtTarget, 0, 0, 0, 0)
 		AddGear(1318, 208, gtTarget, 0, 0, 0, 0)
 		AddGear(1697, 250, gtTarget, 0, 0, 0, 0)
-		AddGear(1852, 100, gtTarget, 0, 0, 0, 0)
+		if INTERFACE ~= "touch" then
+			-- These targets may be too hard in touch interface because you cannot set bounciness yet
+			-- FIXME: Allow these targets in touch when bounciness can be set
+			AddGear(323, 960, gtTarget, 0, 0, 0, 0)
+			AddGear(1852, 100, gtTarget, 0, 0, 0, 0)
+		end
 	-- Grand Final
 	elseif gamePhase == 5 then
 		AddGear(186, 473, gtTarget, 0, 0, 0, 0)
@@ -117,24 +121,42 @@
 
 function newGamePhase()
 	-- Spawn targets, update wind and ammo, show instructions
+	local ctrl = ""
 	if gamePhase == 0 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Open ammo menu: [Right click]").."|"..
+			loc("Select weapon: [Left click]")
+		else
+			ctrl = loc("Open ammo menu: Tap the [Suitcase]")
+		end
 		ShowMission(loc("Basic Grenade Training"), loc("Select Weapon"), loc("To begin with the training, select the grenade from the ammo menu!").."|"..
-		loc("Open ammo menu: [Right click]").."|"..
-		loc("Select weapon: [Left click]"), 2, 5000)
+		ctrl, 2, 5000)
 	elseif gamePhase == 1 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Attack: [Space]").."|"..
+			loc("Aim: [Up]/[Down]").."|"..
+			loc("Change direction: [Left]/[Right]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Attack: Tap the [Bomb]").."|"..
+			loc("Aim: [Up]/[Down]").."|"..
+			loc("Change direction: [Left]/[Right]")
+		end
 		ShowMission(loc("Basic Grenade Training"), loc("Warming Up"),
 		loc("Throw a grenade to destroy the target!").."|"..
 		loc("Hold the Attack key pressed for more power.").."|"..
-		loc("Attack: [Space]").."|"..
-		loc("Aim: [Up]/[Down]").."|"..
-		loc("Change direction: [Left]/[Right]").."|"..
+		ctrl.."|"..
 		loc("Note: Walking is disabled in this mission."), 2, 20000)
 		spawnTargets()
 	elseif gamePhase == 2 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Set detonation timer: [1]-[5]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Change detonation timer: Tap the [Clock]")
+		end
 		ShowMission(loc("Basic Grenade Training"), loc("Timer"),
 		loc("You can change the detonation timer of grenades.").."|"..
 		loc("Grenades explode after 1 to 5 seconds (you decide).").."|"..
-		loc("Set detonation timer: [1]-[5]"), 2, 15000)
+		ctrl, 2, 15000)
 		spawnTargets()
 	elseif gamePhase == 3 then
 		ShowMission(loc("Basic Grenade Training"), loc("No Wind Influence"), loc("Unlike bazookas, grenades are not influenced by wind.").."|"..
@@ -142,17 +164,28 @@
 		SetWind(50)
 		spawnTargets()
 	elseif gamePhase == 4 then
-		ShowMission(loc("Basic Grenade Training"), loc("Bounciness"),
-		loc("You can set the bounciness of grenades (and grenade-like weapons).").."|"..
-		loc("Grenades with high bounciness bounce a lot and behave chaotic.").."|"..
-		loc("With low bounciness, it barely bounces at all, but it is much more predictable.").."|"..
-		loc("Try out different bounciness levels to reach difficult targets.").."|"..
-		loc("Set bounciness: [Left Shift] + [1]-[5]"),
-		2, 20000)
+		local caption = loc("Bounciness")
+		if INTERFACE == "desktop" then
+			ctrl = loc("You can set the bounciness of grenades (and grenade-like weapons).").."|"..
+			loc("Grenades with high bounciness bounce a lot and behave chaotic.").."|"..
+			loc("With low bounciness, it barely bounces at all, but it is much more predictable.").."|"..
+			loc("Try out different bounciness levels to reach difficult targets.").."|"..
+			loc("Set bounciness: [Left Shift] + [1]-[5]")
+		elseif INTERFACE == "touch" then
+			-- FIXME: Bounciness can't be set in touch yet. :(
+			caption = loc("Well done.")
+			ctrl = loc("You're doing well! Here are more targets for you.")
+		end
+
+		ShowMission(loc("Basic Grenade Training"), caption, ctrl, 2, 20000)
 		spawnTargets()
 	elseif gamePhase == 5 then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Precise Aim: [Left Shift] + [Up]/[Down]")
+			-- FIXME: No precise aim in touch interface yet :(
+		end
 		ShowMission(loc("Basic Grenade Training"), loc("Final Targets"), loc("Good job! Now destroy the final targets to finish the training.").."|"..
-		loc("Precise Aim: [Left Shift] + [Up]/[Down]"),
+		ctrl,
 		2, 7000)
 		spawnTargets()
 	elseif gamePhase == 6 then
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Movement.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Movement.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -241,7 +241,10 @@
 	crates[7] = SpawnHealthCrate(1198, 1750)		-- Back Jumping 2
 	crates[8] = SpawnSupplyCrate(1851, 1402, amSwitch, 100)	-- Switch Hedgehog
 	crates[9] = SpawnHealthCrate(564, 1772)			-- Health
-	crates[10] = SpawnHealthCrate(2290, 1622)		-- Turning Around
+	-- FIXME: Not available in touch because no “precise” button
+	if INTERFACE ~= "touch" then
+		crates[10] = SpawnHealthCrate(2290, 1622)		-- Turning Around
+	end
 end
 
 local function victory()
@@ -265,11 +268,17 @@
 		loc("To finish hedgehog selection, just do anything|with him, like walking."),
 		2, 20000)
 	else
+		local ctrl = ""
+		if INTERFACE == "desktop" then
+			ctrl = loc("Hit the “Switch Hedgehog” key until you have|selected Cappy, the hedgehog with the cap!").."|"..
+			loc("Switch hedgehog: [Tabulator]")
+		else
+			ctrl = loc("Tap the “rotating arrow” button on the left|until you have selected Cappy, the hedgehog with the cap!")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Switch Hedgehog (2/3)"),
 		loc("You have activated Switch Hedgehog!").."|"..
 		loc("The spinning arrows above your hedgehog show|which hedgehog is selected right now.").."|"..
-		loc("Hit the “Switch Hedgehog” key until you have|selected Cappy, the hedgehog with the cap!").."|"..
-		loc("Switch hedgehog: [Tabulator]"), 2, 20000)
+		ctrl, 2, 20000)
 	end
 end
 
@@ -281,6 +290,7 @@
 end
 
 function onGearDelete(gear)
+	local ctrl = ""
 	-- Switching done
 	if GetGearType(gear) == gtSwitcher then
 		switcherGear = nil
@@ -290,57 +300,99 @@
 			loc("Collect the remaining crates to complete the training."),
 			2, 0)
 		else
+			if INTERFACE == "desktop" then
+				ctrl = loc("Open ammo menu: [Right click]").."|"..
+				loc("Attack: [Space]")
+			elseif INTERFACE == "touch" then
+				ctrl = loc("Open ammo menu: Tap the [Suitcase]").."|"..
+				loc("Attack: Tap the [Bomb]")
+			end
 			ShowMission(loc("Basic Movement Training"), loc("Switch Hedgehog (Failed!)"),
 			loc("Oops! You have selected the wrong hedgehog! Just try again.").."|"..
 			loc("Select “Switch Hedgehog” from the ammo menu and|hit the “Attack” key to proceed.").."|"..
-			loc("Open ammo menu: [Right click]").."|"..
-			loc("Attack: [Space]"), 2, 0)
+			ctrl, 2, 0)
 		end
 
 	-- Crate collected (or destroyed, but this should not be possible)
 	elseif gear == crates[1] then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Long Jump: [Enter]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Long Jump: Tap the [Curvy Arrow] button for long")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Jumping"),
 		loc("Get the next crate by jumping over the abyss.").."|"..
 		loc("Careful, hedgehogs can't swim!").."|"..
-		loc("Long Jump: [Enter]"), 2, 5000)
+		ctrl, 2, 5000)
 	elseif gear == crates[2] then
 		victory()
 	elseif gear == crates[4] then
+		if INTERFACE == "desktop" then
+			ctrl = loc("High Jump: [Backspace]").."|"..loc("Back Jump: [Backspace] ×2")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("High Jump: Tap the [Curvy Arrow] shortly").."|"..loc("Back Jump: Double-tap the [Curvy Arrow]")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Back Jumping (1/2)"),
 		loc("For the next crate, you have to do back jumps.") .. "|" ..
 		loc("To reach higher ground, walk to a ledge, look to the left, then do a back jump.") .. "|" ..
-		loc("High Jump: [Backspace]").."|"..loc("Back Jump: [Backspace] ×2"), 2, 6600)
+		ctrl, 2, 6600)
 	elseif gear == crates[7] then
+		if INTERFACE == "desktop" then
+			ctrl = loc("High Jump: [Backspace]").."|"..loc("Back Jump: [Backspace] ×2")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("High Jump: Tap the [Curvy Arrow] shortly").."|"..loc("Back Jump: Double-tap the [Curvy Arrow]")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Back Jumping (2/2)"),
 		loc("To get over the next obstacles, keep some distance from the wall before you back jump.").."|"..
 		loc("Hint: To jump higher, wait a bit before you hit “High Jump” a second time.").."|"..
-		loc("High Jump: [Backspace]").."|"..loc("Back Jump: [Backspace] ×2"), 2, 15000)
+		ctrl, 2, 15000)
 	elseif gear == crates[5] then
+		-- FIXME: Touch doesn't have precise aim yet :(
+		if INTERFACE == "desktop" then
+			ctrl = "|" ..
+			loc("You can also hold down the key for “Precise Aim” to prevent slipping.") .. "|" ..
+			loc("Precise Aim: [Left Shift]")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Walking on Ice"),
 		loc("These girders are slippery, like ice.").."|"..
 		loc("And you need to move to the top!").."|"..
-		loc("If you don't want to slip away, you have to keep moving!").."|"..
-		loc("You can also hold down the key for “Precise Aim” to prevent slipping.").."|"..
-		loc("Precise Aim: [Left Shift]"), 2, 9000)
+		loc("If you don't want to slip away, you have to keep moving!")..
+		ctrl, 2, 9000)
 	elseif gear == crates[6] then
+		-- FIXME: Touch doesn't have precise aim yet :(
+		if INTERFACE == "desktop" then
+			ctrl = "|" .. loc("Remember: Hold down [Left Shift] to prevent slipping")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("A mysterious Box"),
-		loc("The next crate is an utility crate.").."|"..loc("What's in the box, you ask? Let's find out!").."|"..
-		loc("Remember: Hold down [Left Shift] to prevent slipping"), 2, 6000)
+		loc("The next crate is an utility crate.").."|"..loc("What's in the box, you ask? Let's find out!")..
+		ctrl, 2, 6000)
 	elseif gear == crates[8] then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Open ammo menu: [Right click]").."|"..
+			loc("Attack: [Space]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Open ammo menu: Tap the [Suitcase]").."|"..
+			loc("Attack: Tap the [Bomb]")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Switch Hedgehog (1/3)"),
 		loc("You have collected the “Switch Hedgehog” utility!").."|"..
 		loc("This allows to select any hedgehog in your team!").."|"..
 		loc("Select “Switch Hedgehog” from the ammo menu and|hit the “Attack” key.").."|"..
-		loc("Open ammo menu: [Right click]").."|"..
-		loc("Attack: [Space]"), 2, 30000)
+		ctrl, 2, 30000)
 	elseif gear == crates[3] then
 		ShowMission(loc("Basic Movement Training"), loc("Rubber"), loc("As you probably noticed, these rubber bands|are VERY elastic. Hedgehogs and many other|things will bounce off without taking any damage.").."|"..
 		loc("Now try to get out of this bounce house|and take the next crate."), 2, 8000)
 	elseif gear == crates[9] then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Look around: [Mouse movement]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Look around: [Tap or swipe on the screen]")
+		end
 		ShowMission(loc("Basic Movement Training"), loc("Health"), loc("You just got yourself some extra health.|The more health your hedgehogs have, the better!").."|"..
 		loc("Now go to the next crate.").."|"..
-		loc("Look around: [Mouse movement]"), 2, 10000)
+		ctrl, 2, 10000)
 	elseif gear == crates[10] then
+		-- FIXME: This crate is unused in touch atm
 		ShowMission(loc("Basic Movement Training"), loc("Turning Around"),
 		loc("By the way, you can turn around without walking|by holding down Precise when you hit a walk control.").."|"..
 		loc("Get the final crate to the right to complete the training.").."|"..
@@ -369,17 +421,26 @@
 	-- This part is CRITICALLY important for all future missions.
 	-- Because the player must know how to show the current mission texts again.
 	-- We force the player to hit Attack before the actual training begins.
+	local ctrl = ""
+	if INTERFACE == "desktop" then
+		ctrl = loc("IMPORTANT: To see the mission panel again, hold the mission panel key.").."| |"..
+		loc("Note: This basic training assumes default controls.").."|"..
+		loc("Mission panel: [M]").."|"..
+		loc("Quit: [Esc]").."|"..
+		loc("Pause: [P]").."| |"..
+		loc("To begin with the training, hit the attack key!").."|"..
+		loc("Attack: [Space]")
+	elseif INTERFACE == "touch" then
+		ctrl = loc("IMPORTANT: To see the mission panel again, pause the game.").."| |"..
+		loc("Pause: Tap the [Pause] button").."| |"..
+		loc("To begin with the training, tap the attack button!").."|"..
+		loc("Attack: Tap the [Bomb]")
+	end
 	ShowMission(loc("Basic Movement Training"), loc("Mission Panel"),
 	loc("This is the mission panel.").."|"..
 	loc("Here you will find the current mission instructions.").."|"..
 	loc("Normally, the mission panel disappears after a few seconds.").."|"..
-	loc("IMPORTANT: To see the mission panel again, hold the mission panel key.").."| |"..
-	loc("Note: This basic training assumes default controls.").."|"..
-        loc("Mission panel: [M]").."|"..
-	loc("Quit: [Esc]").."|"..
-	loc("Pause: [P]").."| |"..
-	loc("To begin with the training, hit the attack key!").."|"..
-	loc("Attack: [Space]"), 2, 900000, true)
+	ctrl, 2, 900000, true)
 
 	-- TODO: This and other training missions are currently hardcoding control names.
 	-- This should be fixed eventually.
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -194,10 +194,16 @@
 end
 
 function onNewTurn()
+	local ctrl = ""
 	if not wasFirstTurn then
+		if INTERFACE == "desktop" then
+			ctrl = loc("Open ammo menu: [Right click]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Open ammo menu: Tap the [Suitcase]")
+		end
 		ShowMission(loc("Basic Rope Training"), loc("Select Rope"),
 		loc("Select the rope to begin!").."|"..
-		loc("Open ammo menu: [Right click]"), 2, 7500)
+		ctrl, 2, 7500)
 		wasFirstTurn = true
 	end
 	if isInMineChallenge then
@@ -212,11 +218,18 @@
 
 	-- First rope selection
 	if not ropeSelected and GetCurAmmoType() == amRope then
+		local ctrl = ""
+		if INTERFACE == "desktop" then
+			ctrl = loc("Aim: [Up]/[Down]").."|"..
+			loc("Attack: [Space]")
+		elseif INTERFACE == "touch" then
+			ctrl = loc("Aim: [Up]/[Down]").."|"..
+			loc("Attack: Tap the [Bomb]")
+		end
 		ShowMission(loc("Basic Rope Training"), loc("Getting Started"),
 		loc("You can use the rope to reach new places.").."|"..
 		loc("Aim at the ceiling and hold [Attack] pressed until the rope attaches.").."|"..
-		loc("Aim: [Up]/[Down]").."|"..
-		loc("Attack: [Space]"), 2, 15000)
+		ctrl, 2, 15000)
 		ropeSelected = true
 	-- Rope attach
 	elseif ropeGear and band(GetState(ropeGear), gstCollision) ~= 0 then
@@ -367,11 +380,18 @@
 	elseif GetGearType(gear) == gtRope then
 		ropeGear = nil
 		if ropeAttached and not target1Reached then
+			local ctrl = ""
+			if INTERFACE == "desktop" then
+				ctrl = loc("Aim: [Up]/[Down]").."|"..
+				loc("Attack: [Space]")
+			elseif INTERFACE == "touch" then
+				ctrl = loc("Aim: [Up]/[Down]").."|"..
+				loc("Attack: Tap the [Bomb]")
+			end
 			ShowMission(loc("Basic Rope Training"), loc("How to Rope"),
 			loc("Go to the target.").."|"..
 			loc("Hold [Attack] to attach the rope.").."|"..
-			loc("Aim: [Up]/[Down]").."|"..
-			loc("Attack: [Space]"), 2, 13000)
+			ctrl, 2, 13000)
 			ropeAttached = false
 		end
 	elseif GetGearType(gear) == gtMine then
--- a/share/hedgewars/Data/Scripts/Multiplayer/Frenzy.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Frenzy.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -26,10 +26,13 @@
 	ruleSet = "" ..
 	loc("RULES:") .. " |" ..
 	loc("Each turn is only ONE SECOND!") .. "|" ..
-	loc("Use your ready time to think.") .. "|" ..
-	loc("Slot keys save time! (F1-F10 by default)") .. "| |"
-	for i=1, #frenzyAmmos do
-		ruleSet = ruleSet .. string.format(loc("Slot %d: %s"), i, GetAmmoName(frenzyAmmos[i])) .. "|"
+	loc("Use your ready time to think.")
+	if INTERFACE ~= "touch" then
+		ruleSet = ruleSet .. "|" ..
+		loc("Slot keys save time! (F1-F10 by default)") .. "| |"
+		for i=1, #frenzyAmmos do
+			ruleSet = ruleSet .. string.format(loc("Slot %d: %s"), i, GetAmmoName(frenzyAmmos[i])) .. "|"
+		end
 	end
 
 	ShowMission(loc("FRENZY"),
@@ -40,7 +43,7 @@
 
 function onGameInit()
 
-	if TurnTime > 10001 then
+	if TurnTime > 8000 then
 		Ready = 8000
 	else
 		Ready = TurnTime
--- a/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -1091,7 +1091,6 @@
 	end
 	CaseFreq = 0
 	HealthCaseProb = 0
-	Delay = 1000
 	SuddenDeathTurns = 50
 	WaterRise = 0
 	HealthDecrease = 0
--- a/share/hedgewars/Data/Scripts/Multiplayer/The_Specialists.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Scripts/Multiplayer/The_Specialists.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -175,7 +175,6 @@
 function onGameInit()
 	ClearGameFlags()
 	EnableGameFlags(gfRandomOrder, gfResetWeps, gfInfAttack, gfPlaceHog, gfPerHogAmmo, gfSwitchHog)
-	Delay = 10
 	HealthCaseProb = 100
 end
 
--- a/share/hedgewars/Data/Scripts/SpeedShoppa.lua	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Scripts/SpeedShoppa.lua	Thu Dec 13 10:51:07 2018 -0500
@@ -94,7 +94,6 @@
 		CaseFreq = 0 
 		MinesNum = 0 
 		Explosives = 0 
-		Delay = 10 
 		Theme = params.theme
 		Map = params.map
 		-- Disable Sudden Death
--- a/share/hedgewars/Data/Sounds/voices/Default_ru/CMakeLists.txt	Thu Dec 13 10:49:30 2018 -0500
+++ b/share/hedgewars/Data/Sounds/voices/Default_ru/CMakeLists.txt	Thu Dec 13 10:51:07 2018 -0500
@@ -8,6 +8,7 @@
 Firepunch*.ogg
 Flawless.ogg
 Hello.ogg
+Hmm.ogg
 Hurry.ogg
 Illgetyou.ogg
 Incoming.ogg
Binary file share/hedgewars/Data/Sounds/voices/Default_ru/Hmm.ogg has changed