Merge alfadur's server commits with timer cycle key commits
authorWuzzy <Wuzzy2@mail.ru>
Sat, 21 Jul 2018 02:06:13 +0200
changeset 13534 395a4c92e523
parent 13533 0c8001e43fd3 (current diff)
parent 13531 282218ab1b28 (diff)
child 13535 4dd9711b0206
Merge alfadur's server commits with timer cycle key commits
--- a/gameServer2/Cargo.toml	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/Cargo.toml	Sat Jul 21 02:06:13 2018 +0200
@@ -13,3 +13,7 @@
 log = "0.4"
 proptest = "0.8"
 base64 = "0.9"
+bitflags = "1.0"
+serde = "1.0"
+serde_yaml = "0.7"
+serde_derive = "1.0"
--- a/gameServer2/src/main.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/main.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -12,6 +12,10 @@
 extern crate log;
 extern crate env_logger;
 #[macro_use] extern crate proptest;
+#[macro_use] extern crate bitflags;
+extern crate serde;
+extern crate serde_yaml;
+#[macro_use] extern crate serde_derive;
 
 //use std::io::*;
 //use rand::Rng;
--- a/gameServer2/src/protocol/messages.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/protocol/messages.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -18,7 +18,7 @@
     Info(String),
     // not entered state
     Nick(String),
-    Proto(u32),
+    Proto(u16),
     Password(String, String),
     Checker(u32, String, String),
     // lobby
@@ -41,7 +41,7 @@
     // in room
     Part(Option<String>),
     Cfg(GameCfg),
-    AddTeam(TeamInfo),
+    AddTeam(Box<TeamInfo>),
     RemoveTeam(String),
     SetHedgehogsNumber(String, u8),
     SetTeamColor(String, u8),
@@ -76,7 +76,7 @@
     Pong,
     Bye(String),
     Nick(String),
-    Proto(u32),
+    Proto(u16),
     LobbyLeft(String, String),
     LobbyJoined(Vec<String>),
     ChatMsg {nick: String, msg: String},
@@ -105,8 +105,8 @@
     Unreachable,
 }
 
-pub fn server_chat(msg: &str) -> HWServerMessage  {
-    HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg: msg.to_string() }
+pub fn server_chat(msg: String) -> HWServerMessage  {
+    HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg }
 }
 
 impl GameCfg {
@@ -234,10 +234,10 @@
             //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(String, String), ??
-            Delete(room) => msg!["CMD", format!("DELETE {}", room)],
-            SaveRoom(room) => msg!["CMD", format!("SAVEROOM {}", room)],
-            LoadRoom(room) => msg!["CMD", format!("LOADROOM {}", room)],
+            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")
@@ -245,7 +245,7 @@
     }
 }
 
-fn construct_message(header: &[&str], msg: &Vec<String>) -> String {
+fn construct_message(header: &[&str], msg: &[String]) -> String {
     let mut v: Vec<_> = header.iter().map(|s| *s).collect();
     v.extend(msg.iter().map(|s| &s[..]));
     v.push("\n");
--- a/gameServer2/src/protocol/parser.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/protocol/parser.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -24,7 +24,10 @@
 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))
@@ -87,7 +90,7 @@
     | 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: u32_line >> (Proto(d)))
+    | do_parse!(tag!("PROTO")   >> eol >> d: u16_line >> (Proto(d)))
 
     | do_parse!(tag!("QUIT")   >> msg: opt_param >> (Quit(msg)))
 ));
@@ -103,9 +106,10 @@
     | 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!("DELETE")   >> spaces >> r: a_line  >> (Delete(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)))
@@ -148,11 +152,11 @@
                     flag: a_line    >> eol >>
                     difficulty: u8_line >> eol >>
                     hedgehogs: _8_hogs >>
-                    (AddTeam(TeamInfo{
+                    (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    >>
--- a/gameServer2/src/protocol/test.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/protocol/test.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -118,7 +118,7 @@
         7 => SuperPower(),
         8 => Info(Ascii),
         9 => Nick(Ascii),
-        10 => Proto(u32),
+        10 => Proto(u16),
         11 => Password(Ascii, Ascii),
         12 => Checker(u32, Ascii, Ascii),
         13 => List(),
@@ -139,7 +139,7 @@
         28 => Stats(),
         29 => Part(Option<Ascii>),
         30 => Cfg(GameCfg),
-        31 => AddTeam(TeamInfo),
+        31 => AddTeam(Box<TeamInfo>),
         32 => RemoveTeam(Ascii),
         33 => SetHedgehogsNumber(Ascii, u8),
         34 => SetTeamColor(Ascii, u8),
@@ -160,7 +160,7 @@
         //49 => CallVote(Option<(String, Option<String>)>),
         50 => Vote(bool),
         51 => ForceVote(bool),
-        //52 => Save(String, String),
+        52 => Save(Ascii, Ascii),
         53 => Delete(Ascii),
         54 => SaveRoom(Ascii),
         55 => LoadRoom(Ascii),
--- a/gameServer2/src/server/actions.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/actions.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -5,7 +5,7 @@
 };
 use super::{
     server::HWServer,
-    room::{GameInfo},
+    room::{GameInfo, RoomFlags},
     client::HWClient,
     coretypes::{ClientId, RoomId, GameCfg, VoteType},
     room::HWRoom,
@@ -25,7 +25,7 @@
     ToSelf,
     ToAll {
         room_id: Option<RoomId>,
-        protocol: Option<u32>,
+        protocol: Option<u16>,
         skip_self: bool
     }
 }
@@ -60,7 +60,7 @@
         self
     }
 
-    pub fn with_protocol(mut self, protocol_number: u32) -> PendingMessage {
+    pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage {
         if let Destination::ToAll {ref mut protocol, ..} = self.destination {
             *protocol = Some(protocol_number)
         }
@@ -116,7 +116,7 @@
 
 pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) {
     match action {
-        Send(msg) => server.send(client_id, msg.destination, msg.message),
+        Send(msg) => server.send(client_id, &msg.destination, msg.message),
         ByeClient(msg) => {
             let room_id;
             let nick;
@@ -126,12 +126,12 @@
                 nick = c.nick.clone();
             }
 
-            room_id.map (|id| {
+            if let Some(id) = 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(),
@@ -218,14 +218,14 @@
                 let c = &mut server.clients[client_id];
                 r.players_number += 1;
                 c.room_id = Some(room_id);
-                c.is_joined_mid_game = false;
-                if r.master_id == Some(c.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;
-                    c.is_master = true;
-                    c.is_ready = true;
-                } else {
-                    c.is_ready = false;
-                    c.is_master = false;
                 }
 
                 let mut v = vec![
@@ -236,11 +236,11 @@
                     v.push(ChatMsg {nick: "[greeting]".to_string(), msg: r.greeting.clone()}
                         .send_self().action());
                 }
-                if !c.is_master {
+                if !c.is_master() {
                     let team_names: Vec<_>;
                     if let Some(ref mut info) = r.game_info {
-                        c.is_in_game = true;
-                        c.is_joined_mid_game = true;
+                        c.set_is_in_game(true);
+                        c.set_is_joined_mid_game(true);
 
                         {
                             let teams = info.client_teams(c.id);
@@ -273,7 +273,7 @@
                         v.push(ForwardEngineMessage(info.msg_log.clone())
                             .send_self().action());
 
-                        for name in team_names.iter() {
+                        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());
@@ -295,7 +295,7 @@
                 if config {
                     actions.push(ConfigEntry("FULLMAPCONFIG".to_string(), r.map_config())
                         .send(to).action());
-                    for cfg in r.game_config().into_iter() {
+                    for cfg in r.game_config() {
                         actions.push(cfg.to_server_msg().send(to).action());
                     }
                 }
@@ -319,7 +319,7 @@
                             .send(to).action());
                     }
                     let nicks: Vec<_> = server.clients.iter()
-                        .filter(|(_, c)| c.room_id == Some(r.id) && c.is_ready)
+                        .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)
@@ -331,11 +331,12 @@
         }
         AddVote{vote, is_forced} => {
             let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
+            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().find(|(id, _)| client_id == *id).is_none() {
-                        actions.push(server_chat("Your vote has been counted.").send_self().action());
+                        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();
@@ -347,14 +348,16 @@
                             result = Some(false);
                         }
                     } else {
-                        actions.push(server_chat("You already have voted.").send_self().action());
+                        actions.push(server_chat("You already have voted.".to_string())
+                            .send_self().action());
                     }
                 } else {
-                    actions.push(server_chat("There's no voting going on.").send_self().action());
+                    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.")
+                    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 {
@@ -378,13 +381,25 @@
                         }
                     }
                 },
-                VoteType::Map(_) => {
-                    unimplemented!();
+                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.")
+                        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());
@@ -411,10 +426,10 @@
             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 {
+                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) {
+                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()])
@@ -425,7 +440,7 @@
 
             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 {
+                if r.players_number == 0 && !r.is_fixed() {
                     actions.push(RemoveRoom(r.id));
                 } else {
                     actions.push(RemoveClientTeams);
@@ -439,18 +454,18 @@
         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) {
+            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).map(|id| *id))
+                    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.is_master = false;
+                        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());
@@ -459,12 +474,16 @@
                     None => {}
                 }
                 r.master_id = new_id;
+                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());
                 }
             }
-            new_id.map(|id| server.clients[id].is_master = true);
+            if let Some(id) = new_id { server.clients[id].set_is_master(true) }
             server.react(client_id, actions);
         }
         RemoveTeam(name) => {
@@ -476,7 +495,7 @@
                 }
                 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 {
+                if r.game_info.is_some() && c.is_in_game() {
                     actions.push(SendTeamRemovalMessage(name));
                 }
             }
@@ -514,7 +533,7 @@
                     room.start_round();
                     for id in room_clients {
                         let c = &mut server.clients[id];
-                        c.is_in_game = true;
+                        c.set_is_in_game(false);
                         c.team_indices = room.client_team_indices(c.id);
                     }
                     vec![RunGame.send_all().in_room(room.id).action(),
@@ -527,7 +546,7 @@
         }
         SendTeamRemovalMessage(team_name) => {
             let mut actions = Vec::new();
-            if let (c, Some(r)) = server.client_and_room(client_id) {
+            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)]).
@@ -564,11 +583,11 @@
 
             if let Some(info) = old_info {
                 for (_, c) in server.clients.iter() {
-                    if c.room_id == Some(room_id) && c.is_joined_mid_game {
+                    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.iter() {
+                        for name in &info.left_teams {
                             actions.push(TeamRemove(name.clone())
                                 .send(c.id).action());
                         }
@@ -579,10 +598,11 @@
             let nicks: Vec<_> = server.clients.iter_mut()
                 .filter(|(_, c)| c.room_id == Some(room_id))
                 .map(|(_, c)| {
-                    c.is_ready = c.is_master;
-                    c.is_joined_mid_game = false;
+                    let is_master = c.is_master();
+                    c.set_is_ready(is_master);
+                    c.set_is_joined_mid_game(false);
                     c
-                }).filter_map(|c| if !c.is_master {
+                }).filter_map(|c| if !c.is_master() {
                     Some(c.nick.clone())
                 } else {
                     None
--- a/gameServer2/src/server/client.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/client.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -1,18 +1,27 @@
 use super::coretypes::ClientId;
 
+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 NONE = 0b0000_0000;
+        const DEFAULT = Self::NONE.bits;
+    }
+}
+
 pub struct HWClient {
     pub id: ClientId,
     pub room_id: Option<usize>,
     pub nick: String,
-    pub protocol_number: u32,
-    pub is_admin: bool,
-    pub is_master: bool,
-    pub is_ready: bool,
-    pub is_in_game: bool,
+    pub protocol_number: u16,
+    pub flags: ClientFlags,
     pub teams_in_game: u8,
     pub team_indices: Vec<u8>,
-    pub clan: Option<u8>,
-    pub is_joined_mid_game: bool,
+    pub clan: Option<u8>
 }
 
 impl HWClient {
@@ -22,14 +31,30 @@
             room_id: None,
             nick: String::new(),
             protocol_number: 0,
-            is_admin: false,
-            is_master: false,
-            is_ready: false,
-            is_in_game: false,
+            flags: ClientFlags::DEFAULT,
             teams_in_game: 0,
             team_indices: Vec::new(),
             clan: None,
-            is_joined_mid_game: false,
         }
     }
+
+    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 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) }
 }
\ No newline at end of file
--- a/gameServer2/src/server/handlers/common.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/handlers/common.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -1,30 +1,21 @@
 use protocol::messages::{
     HWProtocolMessage::{self, Rnd}, HWServerMessage::{self, ChatMsg},
 };
-use rand::{self, Rng};
-use server::{actions::Action, room::HWRoom, server::HWServer};
+use rand::{self, Rng, thread_rng};
+use server::{actions::Action, server::HWServer};
 
-pub fn rnd_action(options: Vec<String>, room: Option<&mut HWRoom>) -> Vec<Action> {
-    if let Some(room) = room {
-        let msg = rnd_reply(options);
-        vec![msg.send_all().in_room(room.id).action()]
+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 {
-        Vec::new()
-    }
-}
+        rng.choose(&options).unwrap().clone()
+    };
 
-fn rnd_reply(options: Vec<String>) -> HWServerMessage {
-    let options = if options.is_empty() {
-        vec!["heads".to_owned(), "tails".to_owned()]
-    } else {
-        options
-    };
-    let reply = rand::thread_rng().choose(&options).unwrap();
-    let msg = ChatMsg {
+    ChatMsg {
         nick: "[random]".to_owned(),
         msg: reply.clone(),
-    };
-    msg
+    }
 }
 
 #[cfg(test)]
@@ -45,7 +36,7 @@
     fn run_handle_test(opts: Vec<String>) {
         let opts2 = opts.clone();
         for opt in opts {
-            while reply2string(rnd_reply(opts2.clone())) != opt {}
+            while reply2string(rnd_reply(&opts2)) != opt {}
         }
     }
 
@@ -72,7 +63,7 @@
 
         while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps {
             tries += 1;
-            if reply2string(rnd_reply(opts.clone())) == 1.to_string() {
+            if reply2string(rnd_reply(&opts)) == 1.to_string() {
                 ones += 1;
             }
         }
--- a/gameServer2/src/server/handlers/inroom.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/handlers/inroom.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -6,15 +6,18 @@
     server_chat
 };
 use server::{
-    coretypes::{ClientId, Voting, VoteType},
+    coretypes::{ClientId, RoomId, Voting, VoteType},
     server::HWServer,
-    room::HWRoom,
+    room::{HWRoom, RoomFlags},
     actions::{Action, Action::*}
 };
 use utils::is_name_illegal;
-use std::mem::swap;
+use std::{
+    mem::swap, fs::{File, OpenOptions},
+    io::{Read, Write, Result, Error, ErrorKind}
+};
 use base64::{encode, decode};
-use super::common::rnd_action;
+use super::common::rnd_reply;
 
 #[derive(Clone)]
 struct ByMsg<'a> {
@@ -35,8 +38,8 @@
     }
 }
 
-fn by_msg(source: &Vec<u8>) -> ByMsg {
-    ByMsg {messages: &source[..]}
+fn by_msg(source: &[u8]) -> ByMsg {
+    ByMsg {messages: source}
 }
 
 const VALID_MESSAGES: &[u8] =
@@ -81,7 +84,29 @@
     })
 }
 
-pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
+fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags {
+    use 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 protocol::messages::HWProtocolMessage::*;
     match message {
         Part(None) => server.react(client_id, vec![
@@ -91,28 +116,24 @@
         Chat(msg) => {
             let actions = {
                 let c = &mut server.clients[client_id];
-                let chat_msg = ChatMsg {nick: c.nick.clone(), msg: msg};
-                if let Some(room_id) = c.room_id {
-                    vec![chat_msg.send_all().in_room(room_id).but_self().action()]
-                } else {
-                    Vec::new()
-                }
+                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.is_fixed = true }
+                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.is_fixed = false }
+                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 {
+                if c.is_admin() || c.is_master() && !r.is_fixed() {
                     r.greeting = text
                 }
             }
@@ -121,35 +142,32 @@
             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.room(client_id).map(|r| r.is_fixed).unwrap_or(false) {
+                } 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();
-                    if let (_, Some(r)) = server.client_and_room(client_id) {
-                        swap(&mut r.name, &mut old_name);
-                        vec![SendRoomUpdate(Some(old_name))]
-                    } else {
-                        Vec::new()
-                    }
+                    swap(&mut server.rooms[room_id].name, &mut old_name);
+                    vec![SendRoomUpdate(Some(old_name))]
                 };
             server.react(client_id, actions);
         },
         ToggleReady => {
             let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                let flags = if c.is_ready {
+                let flags = if c.is_ready() {
                     r.ready_players_number -= 1;
                     "-r"
                 } else {
                     r.ready_players_number += 1;
                     "+r"
                 };
-                c.is_ready = !c.is_ready;
+                let is_ready = !c.is_ready();
+                c.set_is_ready(is_ready);
                 let mut v =
                     vec![ClientFlags(flags.to_string(), vec![c.nick.clone()])
                         .send_all().in_room(r.id).action()];
-                if r.is_fixed && r.ready_players_number as u32 == r.players_number {
+                if r.is_fixed() && r.ready_players_number == r.players_number {
                     v.push(StartRoomGame(r.id))
                 }
                 v
@@ -161,7 +179,6 @@
         AddTeam(info) => {
             let mut actions = Vec::new();
             if let (c, Some(r)) = server.client_and_room(client_id) {
-                let room_id = r.id;
                 if r.teams.len() >= r.team_limit as usize {
                     actions.push(Warn("Too many teams!".to_string()))
                 } else if r.addable_hedgehogs() == 0 {
@@ -170,8 +187,10 @@
                     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);
+                    let team = r.add_team(c.id, *info);
                     c.teams_in_game += 1;
                     c.clan = Some(team.color);
                     actions.push(TeamAccepted(team.name.clone())
@@ -206,10 +225,9 @@
         },
         SetHedgehogsNumber(team_name, number) => {
             let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                let room_id = r.id;
                 let addable_hedgehogs = r.addable_hedgehogs();
                 if let Some((_, mut team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
-                    if !c.is_master {
+                    if !c.is_master() {
                         vec![ProtocolError("You're not the room master!".to_string())]
                     } else if number < 1 || number > 8
                            || number > addable_hedgehogs + team.hedgehogs_number {
@@ -231,9 +249,8 @@
         SetTeamColor(team_name, color) => {
             let mut owner_id = None;
             let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                let room_id = r.id;
                 if let Some((owner, mut team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
-                    if !c.is_master {
+                    if !c.is_master() {
                         vec![ProtocolError("You're not the room master!".to_string())]
                     } else if false  {
                         Vec::new()
@@ -258,9 +275,9 @@
         },
         Cfg(cfg) => {
             let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                if r.is_fixed {
+                if r.is_fixed() {
                     vec![Warn("Access denied.".to_string())]
-                } else if !c.is_master {
+                } else if !c.is_master() {
                     vec![ProtocolError("You're not the room master!".to_string())]
                 } else {
                     let v = vec![cfg.to_server_msg()
@@ -273,33 +290,98 @@
             };
             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) => {
+            let actions = if server.clients[client_id].is_admin() {
+                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())]
+                    }
+                }
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
+        }
+        LoadRoom(filename) => {
+            let actions = if server.clients[client_id].is_admin() {
+                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())]
+                    }
+                }
+            } else {
+                Vec::new()
+            };
+            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>")
+                server_chat("Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs <number>".to_string())
                     .send_self().action()])
         }
         CallVote(Some(kind)) => {
-            let (room_id, is_in_game) = server.room(client_id)
-                .map(|r| (r.id, r.game_info.is_some())).unwrap();
+            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!")
+                        Some("/callvote kick: No such user!".to_string())
                     }
                 },
                 VoteType::Map(None) => {
-                    Some("/callvote map: Not implemented")
+                    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)) => {
-                    Some("/callvote map: Not implemented")
+                    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!")
+                        Some("/callvote pause: No game in progress!".to_string())
                     }
                 },
                 VoteType::NewSeed => {
@@ -308,7 +390,7 @@
                 VoteType::HedgehogsPerTeam(number) => {
                     match number {
                         1...8 => None,
-                        _ => Some("/callvote hedgehogs: Specify number from 1 to 8.")
+                        _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string())
                     }
                 },
             };
@@ -316,9 +398,9 @@
                 None => {
                     let msg = voting_description(&kind);
                     let voting = Voting::new(kind, server.room_clients(client_id));
-                    server.room(client_id).unwrap().voting = Some(voting);
+                    server.rooms[room_id].voting = Some(voting);
                     server.react(client_id, vec![
-                        server_chat(&msg).send_all().in_room(room_id).action(),
+                        server_chat(msg).send_all().in_room(room_id).action(),
                         AddVote{ vote: true, is_forced: false}]);
                 }
                 Some(msg) => {
@@ -328,28 +410,20 @@
             }
         }
         Vote(vote) => {
-            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                vec![AddVote{ vote, is_forced: false }]
-            } else {
-                Vec::new()
-            };
-            server.react(client_id, actions);
+            server.react(client_id, vec![AddVote{ vote, is_forced: false }]);
         }
         ForceVote(vote) => {
-            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                vec![AddVote{ vote, is_forced: c.is_admin} ]
-            } else {
-                Vec::new()
-            };
-            server.react(client_id, actions);
+            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 => {
-            let actions = if let (_, Some(r)) = server.client_and_room(client_id) {
-                vec![StartRoomGame(r.id)]
-            } else {
-                Vec::new()
-            };
-            server.react(client_id, actions);
+            server.react(client_id, vec![StartRoomGame(room_id)]);
         }
         EngineMessage(em) => {
             let mut actions = Vec::new();
@@ -383,8 +457,8 @@
         RoundFinished => {
             let mut actions = Vec::new();
             if let (c, Some(r)) = server.client_and_room(client_id) {
-                if c.is_in_game {
-                    c.is_in_game = false;
+                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() {
@@ -397,8 +471,16 @@
             server.react(client_id, actions)
         },
         Rnd(v) => {
-            let actions = rnd_action(v, server.room(client_id));
-            server.react(client_id, actions)
+            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	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/handlers/lobby.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -10,7 +10,7 @@
     HWServerMessage::*
 };
 use utils::is_name_illegal;
-use super::common::rnd_action;
+use super::common::rnd_reply;
 
 pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     use protocol::messages::HWProtocolMessage::*;
@@ -31,8 +31,9 @@
             server.react(client_id, actions);
         },
         Chat(msg) => {
-            let chat_msg = ChatMsg {nick: server.clients[client_id].nick.clone(), msg: msg};
-            server.react(client_id, vec![chat_msg.send_all().but_self().action()]);
+            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 actions;
@@ -48,6 +49,10 @@
                 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()]
@@ -59,8 +64,7 @@
             server.react(client_id, actions);
         },
         Rnd(v) => {
-            let actions = rnd_action(v, server.room(client_id));
-            server.react(client_id, actions)
+            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/mod.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/handlers/mod.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -1,36 +1,38 @@
 use mio;
-use std::io::Write;
-use std::io;
+use std::{io, io::Write};
 
-use super::server::HWServer;
-use super::actions::Action;
-use super::actions::Action::*;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage::*;
-
+use super::{
+    server::HWServer,
+    actions::{Action, Action::*},
+    coretypes::ClientId
+};
+use protocol::messages::{
+    HWProtocolMessage,
+    HWServerMessage::*
+};
 mod common;
 mod loggingin;
 mod lobby;
 mod inroom;
 
-pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
+pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     match message {
         HWProtocolMessage::Ping =>
-            server.react(token, vec![Pong.send_self().action()]),
+            server.react(client_id, vec![Pong.send_self().action()]),
         HWProtocolMessage::Quit(Some(msg)) =>
-            server.react(token, vec![ByeClient("User quit: ".to_string() + &msg)]),
+            server.react(client_id, vec![ByeClient("User quit: ".to_string() + &msg)]),
         HWProtocolMessage::Quit(None) =>
-            server.react(token, vec![ByeClient("User quit".to_string())]),
+            server.react(client_id, vec![ByeClient("User quit".to_string())]),
         HWProtocolMessage::Malformed => warn!("Malformed/unknown message"),
         HWProtocolMessage::Empty => warn!("Empty message"),
         _ => {
-            match server.clients[token].room_id {
+            match server.clients[client_id].room_id {
                 None =>
-                    loggingin::handle(server, token, message),
+                    loggingin::handle(server, client_id, message),
                 Some(id) if id == server.lobby_id =>
-                    lobby::handle(server, token, message),
-                _ =>
-                    inroom::handle(server, token, message)
+                    lobby::handle(server, client_id, message),
+                Some(id) =>
+                    inroom::handle(server, client_id, id, message)
             }
         },
     }
--- a/gameServer2/src/server/network.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/network.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -105,14 +105,14 @@
     }
 
     pub fn send_raw_msg(&mut self, msg: &[u8]) {
-        self.buf_out.write(msg).unwrap();
+        self.buf_out.write_all(msg).unwrap();
     }
 
-    pub fn send_string(&mut self, msg: &String) {
+    pub fn send_string(&mut self, msg: &str) {
         self.send_raw_msg(&msg.as_bytes());
     }
 
-    pub fn send_msg(&mut self, msg: HWServerMessage) {
+    pub fn send_msg(&mut self, msg: &HWServerMessage) {
         self.send_string(&msg.to_raw_protocol());
     }
 }
@@ -143,7 +143,7 @@
         let mut client_exists = false;
         if let Some(ref client) = self.clients.get(id) {
             poll.deregister(&client.socket)
-                .ok().expect("could not deregister socket");
+                .expect("could not deregister socket");
             info!("client {} ({}) removed", client.id, client.peer_addr);
             client_exists = true;
         }
@@ -156,7 +156,7 @@
         poll.register(&client_socket, Token(id),
                       Ready::readable() | Ready::writable(),
                       PollOpt::edge())
-            .ok().expect("could not register socket with event loop");
+            .expect("could not register socket with event loop");
 
         let entry = self.clients.vacant_entry();
         let client = NetworkClient::new(id, client_socket, addr);
@@ -189,7 +189,7 @@
         Ok(())
     }
 
-    fn operation_failed(&mut self, poll: &Poll, client_id: ClientId, error: Error, msg: &str) -> io::Result<()> {
+    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 {
@@ -224,7 +224,7 @@
                 };
             }
             Err(e) => self.operation_failed(
-                poll, client_id, e,
+                poll, client_id, &e,
                 "Error while reading from client socket")?
         }
 
@@ -256,7 +256,7 @@
             },
             Ok(_) => {}
             Err(e) => self.operation_failed(
-                poll, client_id, e,
+                poll, client_id, &e,
                 "Error while writing to client socket")?
         }
 
--- a/gameServer2/src/server/room.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/room.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -1,24 +1,29 @@
-use std::{iter};
+use std::{
+    iter, collections::HashMap
+};
 use server::{
     coretypes::{ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting},
     client::{HWClient}
 };
+use serde::{Serialize, Deserialize};
+use serde_yaml;
 
-const MAX_HEDGEHOGS_IN_ROOM: u8 = 48;
+const MAX_HEDGEHOGS_IN_ROOM: u8 = 64;
+const MAX_TEAMS_IN_ROOM: u8 = 8;
 
-#[derive(Clone)]
+#[derive(Clone, Serialize, Deserialize)]
 struct Ammo {
     name: String,
     settings: Option<String>
 }
 
-#[derive(Clone)]
+#[derive(Clone, Serialize, Deserialize)]
 struct Scheme {
     name: String,
     settings: Option<Vec<String>>
 }
 
-#[derive(Clone)]
+#[derive(Clone, Serialize, Deserialize)]
 struct RoomConfig {
     feature_size: u32,
     map_type: String,
@@ -53,7 +58,7 @@
     }
 }
 
-fn client_teams_impl(teams: &Vec<(ClientId, TeamInfo)>, client_id: ClientId)
+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)
@@ -106,22 +111,38 @@
     }
 }
 
+#[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 protocol_number: u32,
     pub greeting: String,
-    pub is_fixed: bool,
+    pub protocol_number: u16,
+    pub flags: RoomFlags,
 
-    pub players_number: u32,
+    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>
 }
 
@@ -133,15 +154,16 @@
             name: String::new(),
             password: None,
             greeting: "".to_string(),
-            is_fixed: false,
+            flags: RoomFlags::empty(),
             protocol_number: 0,
             players_number: 0,
             default_hedgehog_number: 4,
-            team_limit: 8,
+            team_limit: MAX_TEAMS_IN_ROOM,
             ready_players_number: 0,
             teams: Vec::new(),
             config: RoomConfig::new(),
             voting: None,
+            saves: HashMap::new(),
             game_info: None
         }
     }
@@ -250,11 +272,47 @@
         }
     }
 
+    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 flags = "-".to_string();
         let c = &self.config;
         vec![
-            flags,
+            self.flags_string(),
             self.name.clone(),
             self.players_number.to_string(),
             self.teams.len().to_string(),
@@ -280,6 +338,34 @@
         }
     }
 
+    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(),
--- a/gameServer2/src/server/server.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/server/server.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -40,7 +40,7 @@
             let client = HWClient::new(entry.key());
             entry.insert(client);
         }
-        self.send(key, Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
+        self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
         key
     }
 
@@ -64,8 +64,8 @@
         }
     }
 
-    fn get_recipients(&self, client_id: ClientId, destination: Destination) -> Vec<ClientId> {
-        let mut ids = match destination {
+    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), ..} =>
@@ -83,8 +83,8 @@
         ids
     }
 
-    pub fn send(&mut self, client_id: ClientId, destination: Destination, message: HWServerMessage) {
-        let ids = self.get_recipients(client_id, destination);
+    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));
     }
 
@@ -94,6 +94,8 @@
         }
     }
 
+    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)
     }
@@ -124,7 +126,7 @@
         self.select_clients(|(_, c)| c.room_id == Some(room_id))
     }
 
-    pub fn protocol_clients(&self, protocol: u32) -> Vec<ClientId> {
+    pub fn protocol_clients(&self, protocol: u16) -> Vec<ClientId> {
         self.select_clients(|(_, c)| c.protocol_number == protocol)
     }
 
--- a/gameServer2/src/utils.rs	Sat Jul 21 00:34:32 2018 +0200
+++ b/gameServer2/src/utils.rs	Sat Jul 21 02:06:13 2018 +0200
@@ -3,7 +3,7 @@
 use base64::{encode};
 
 pub const PROTOCOL_VERSION : u32 = 3;
-pub const SERVER: mio::Token = mio::Token(1000000000 + 0);
+pub const SERVER: mio::Token = mio::Token(1_000_000_000);
 
 pub fn is_name_illegal(name: &str ) -> bool{
     name.len() > 40 ||