Merge gameServer2 changes
authorWuzzy <Wuzzy2@mail.ru>
Thu, 12 Jul 2018 14:46:16 +0200
changeset 13485 c85b324c4c2d
parent 13484 480ea997036b (current diff)
parent 13483 e1543ad2fac9 (diff)
child 13486 4ef83bcb850b
child 13520 1ee192f13456
Merge gameServer2 changes
--- a/gameServer2/src/protocol/messages.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/protocol/messages.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,4 +1,7 @@
-use server::coretypes::{ServerVar, GameCfg, TeamInfo, HedgehogInfo};
+use server::coretypes::{
+    ServerVar, GameCfg, TeamInfo,
+    HedgehogInfo, VoteType
+};
 use std::{ops, convert::From, iter::once};
 
 #[derive(PartialEq, Eq, Clone, Debug)]
@@ -56,9 +59,9 @@
     Fix,
     Unfix,
     Greeting(String),
-    CallVote(Option<(String, Option<String>)>),
-    Vote(String),
-    ForceVote(String),
+    CallVote(Option<VoteType>),
+    Vote(bool),
+    ForceVote(bool),
     Save(String, String),
     Delete(String),
     SaveRoom(String),
@@ -90,6 +93,7 @@
     TeamColor(String, u8),
     HedgehogsNumber(String, u8),
     ConfigEntry(String, Vec<String>),
+    Kicked,
     RunGame,
     ForwardEngineMessage(Vec<String>),
     RoundFinished,
@@ -101,6 +105,10 @@
     Unreachable,
 }
 
+pub fn server_chat(msg: &str) -> HWServerMessage  {
+    HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg: msg.to_string() }
+}
+
 impl GameCfg {
     pub fn to_protocol(&self) -> (String, Vec<String>) {
         use server::coretypes::GameCfg::*;
@@ -224,8 +232,8 @@
             Unfix => msg!["CMD", "UNFIX"],
             Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)],
             //CallVote(Option<(String, Option<String>)>) =>, ??
-            Vote(msg) => msg!["CMD", format!("VOTE {}", msg)],
-            ForceVote(msg) => msg!["CMD", format!("FORCE {}", msg)],
+            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)],
@@ -280,6 +288,7 @@
             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),
--- a/gameServer2/src/protocol/parser.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/protocol/parser.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -18,7 +18,7 @@
     test::gen_proto_msg
 };
 use server::coretypes::{
-    HedgehogInfo, TeamInfo, GameCfg
+    HedgehogInfo, TeamInfo, GameCfg, VoteType
 };
 
 named!(end_of_message, tag!("\n\n"));
@@ -26,6 +26,9 @@
 named!(  a_line<&[u8], String>, map!(str_line, String::from));
 named!( u8_line<&[u8],     u8>, 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())))));
@@ -42,6 +45,18 @@
               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!(
@@ -94,10 +109,12 @@
     | 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: a_line  >> (Vote(m)))
-    | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: a_line  >> (ForceVote(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 >>
--- a/gameServer2/src/protocol/test.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/protocol/test.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,8 +1,7 @@
 use proptest::{
     test_runner::{TestRunner, Reason},
     arbitrary::{any, any_with, Arbitrary, StrategyFor},
-    strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree},
-    string::RegexGeneratorValueTree,
+    strategy::{Strategy, BoxedStrategy, Just, Map},
 };
 
 use server::coretypes::{GameCfg, TeamInfo, HedgehogInfo};
@@ -55,14 +54,7 @@
     type Parameters = <String as Arbitrary>::Parameters;
 
     fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
-        any_with::<String>(args)
-            .prop_filter("not ascii", |s| {
-                s.len() > 0 && s.is_ascii() &&
-                    s.find(|c| {
-                        ['\0', '\n', '\x20'].contains(&c)
-                    }).is_none()})
-            .prop_map(Ascii)
-            .boxed()
+        "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
     }
 
     type Strategy = BoxedStrategy<Ascii>;
@@ -166,8 +158,8 @@
         47 => Unfix(),
         48 => Greeting(Ascii),
         //49 => CallVote(Option<(String, Option<String>)>),
-        50 => Vote(Ascii),
-        51 => ForceVote(Ascii),
+        50 => Vote(bool),
+        51 => ForceVote(bool),
         //52 => Save(String, String),
         53 => Delete(Ascii),
         54 => SaveRoom(Ascii),
--- a/gameServer2/src/server/actions.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/actions.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,21 +1,24 @@
 use std::{
     io, io::Write,
     iter::once,
-    mem::swap
+    mem::replace
 };
 use super::{
     server::HWServer,
-    room::{RoomId, GameInfo},
-    client::{ClientId, HWClient},
+    room::{GameInfo},
+    client::HWClient,
+    coretypes::{ClientId, RoomId, GameCfg, VoteType},
     room::HWRoom,
     handlers
 };
 use protocol::messages::{
     HWProtocolMessage,
     HWServerMessage,
-    HWServerMessage::*
+    HWServerMessage::*,
+    server_chat
 };
 use utils::to_engine_msg;
+use rand::{thread_rng, Rng, distributions::Uniform};
 
 pub enum Destination {
     ToId(ClientId),
@@ -103,6 +106,8 @@
     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)
 }
@@ -227,6 +232,10 @@
                     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 {
@@ -320,6 +329,83 @@
             }
             server.react(client_id, actions);
         }
+        AddVote{vote, is_forced} => {
+            let mut actions = Vec::new();
+            if let (c, Some(r)) = server.client_and_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());
+                        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.").send_self().action());
+                    }
+                } else {
+                    actions.push(server_chat("There's no voting going on.").send_self().action());
+                }
+
+                if let Some(res) = result {
+                    actions.push(server_chat("Voting closed.")
+                        .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(_) => {
+                    unimplemented!();
+                },
+                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.")
+                            .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;
@@ -328,23 +414,24 @@
                 if c.is_ready && r.ready_players_number > 0 {
                     r.ready_players_number -= 1;
                 }
-                if r.players_number > 0 && c.is_master {
+                if c.is_master && (r.players_number > 0 || r.is_fixed) {
                     actions.push(ChangeMaster(r.id, None));
                 }
-                actions.push(RemoveClientTeams);
-                actions.push(RoomLeft(c.nick.clone(), msg)
-                    .send_all().in_room(r.id).but_self().action());
                 actions.push(ClientFlags("-i".to_string(), vec![c.nick.clone()])
                     .send_all().action());
-                actions.push(SendRoomUpdate(Some(r.name.clone())));
             }
             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 {
+                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)
@@ -352,8 +439,12 @@
         ChangeMaster(room_id, new_id) => {
             let mut actions = Vec::new();
             let room_client_ids = server.room_clients(room_id);
-            let new_id = new_id.or_else(||
-                room_client_ids.iter().find(|id| **id != client_id).map(|id| *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).map(|id| *id))
+            };
             let new_nick = new_id.map(|id| server.clients[id].nick.clone());
 
             if let (c, Some(r)) = server.client_and_room(client_id) {
@@ -461,10 +552,10 @@
         }
         FinishRoomGame(room_id) => {
             let mut actions = Vec::new();
-            let mut old_info = None;
+            let old_info;
             {
                 let r = &mut server.rooms[room_id];
-                swap(&mut old_info, &mut r.game_info);
+                old_info = replace(&mut r.game_info, None);
                 r.game_info = None;
                 r.ready_players_number = 1;
                 actions.push(SendRoomUpdate(None));
--- a/gameServer2/src/server/client.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/client.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,10 +1,11 @@
-pub type ClientId = usize;
+use super::coretypes::ClientId;
 
 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,
@@ -21,6 +22,7 @@
             room_id: None,
             nick: String::new(),
             protocol_number: 0,
+            is_admin: false,
             is_master: false,
             is_ready: false,
             is_in_game: false,
--- a/gameServer2/src/server/coretypes.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/coretypes.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,3 +1,6 @@
+pub type ClientId = usize;
+pub type RoomId = usize;
+
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum ServerVar {
     MOTDNew(String),
@@ -39,3 +42,29 @@
     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/inroom.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/handlers/inroom.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -2,11 +2,12 @@
 
 use protocol::messages::{
     HWProtocolMessage,
-    HWServerMessage::*
+    HWServerMessage::*,
+    server_chat
 };
 use server::{
+    coretypes::{ClientId, Voting, VoteType},
     server::HWServer,
-    client::ClientId,
     room::HWRoom,
     actions::{Action, Action::*}
 };
@@ -70,6 +71,16 @@
     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)
+    })
+}
+
 pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     use protocol::messages::HWProtocolMessage::*;
     match message {
@@ -89,10 +100,29 @@
             };
             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 }
+            }
+        }
+        Unfix => {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
+                if c.is_admin { r.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.room(client_id).map(|r| r.is_fixed).unwrap_or(false) {
+                    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 {
@@ -116,8 +146,13 @@
                     "+r"
                 };
                 c.is_ready = !c.is_ready;
-                vec![ClientFlags(flags.to_string(), vec![c.nick.clone()])
-                    .send_all().in_room(r.id).action()]
+                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 {
+                    v.push(StartRoomGame(r.id))
+                }
+                v
             } else {
                 Vec::new()
             };
@@ -223,7 +258,9 @@
         },
         Cfg(cfg) => {
             let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
-                if !c.is_master {
+                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 v = vec![cfg.to_server_msg()
@@ -236,6 +273,76 @@
             };
             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>")
+                    .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 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!")
+                    }
+                },
+                VoteType::Map(None) => {
+                    Some("/callvote map: Not implemented")
+                },
+                VoteType::Map(Some(name)) => {
+                    Some("/callvote map: Not implemented")
+                },
+                VoteType::Pause => {
+                    if is_in_game {
+                        None
+                    } else {
+                        Some("/callvote pause: No game in progress!")
+                    }
+                },
+                VoteType::NewSeed => {
+                    None
+                },
+                VoteType::HedgehogsPerTeam(number) => {
+                    match number {
+                        1...8 => None,
+                        _ => Some("/callvote hedgehogs: Specify number from 1 to 8.")
+                    }
+                },
+            };
+            match error {
+                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.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) => {
+            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);
+        }
+        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);
+        }
         StartGame => {
             let actions = if let (_, Some(r)) = server.client_and_room(client_id) {
                 vec![StartRoomGame(r.id)]
--- a/gameServer2/src/server/handlers/lobby.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/handlers/lobby.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -2,7 +2,7 @@
 
 use server::{
     server::HWServer,
-    client::ClientId,
+    coretypes::ClientId,
     actions::{Action, Action::*}
 };
 use protocol::messages::{
--- a/gameServer2/src/server/handlers/loggingin.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/handlers/loggingin.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -2,7 +2,7 @@
 
 use server::{
     server::HWServer,
-    client::ClientId,
+    coretypes::ClientId,
     actions::{Action, Action::*}
 };
 use protocol::messages::{
--- a/gameServer2/src/server/network.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/network.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -4,7 +4,7 @@
     io, io::{Error, ErrorKind, Write},
     net::{SocketAddr, IpAddr, Ipv4Addr},
     collections::HashSet,
-    mem::swap
+    mem::{swap, replace}
 };
 
 use mio::{
@@ -18,7 +18,7 @@
 use protocol::{ProtocolDecoder, messages::*};
 use super::{
     server::{HWServer},
-    client::ClientId
+    coretypes::ClientId
 };
 
 const MAX_BYTES_PER_READ: usize = 2048;
@@ -277,8 +277,7 @@
 
     pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> {
         if self.has_pending_operations() {
-            let mut cache = Vec::new();
-            swap(&mut cache, &mut self.pending_cache);
+            let mut cache = replace(&mut self.pending_cache, Vec::new());
             cache.extend(self.pending.drain());
             for (id, state) in cache.drain(..) {
                 match state {
--- a/gameServer2/src/server/room.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/room.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,11 +1,10 @@
 use std::{iter};
 use server::{
-    coretypes::{TeamInfo, GameCfg, GameCfg::*},
-    client::{ClientId, HWClient}
+    coretypes::{ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting},
+    client::{HWClient}
 };
 
 const MAX_HEDGEHOGS_IN_ROOM: u8 = 48;
-pub type RoomId = usize;
 
 #[derive(Clone)]
 struct Ammo {
@@ -113,6 +112,8 @@
     pub name: String,
     pub password: Option<String>,
     pub protocol_number: u32,
+    pub greeting: String,
+    pub is_fixed: bool,
 
     pub players_number: u32,
     pub default_hedgehog_number: u8,
@@ -120,6 +121,7 @@
     pub ready_players_number: u8,
     pub teams: Vec<(ClientId, TeamInfo)>,
     config: RoomConfig,
+    pub voting: Option<Voting>,
     pub game_info: Option<GameInfo>
 }
 
@@ -130,6 +132,8 @@
             master_id: None,
             name: String::new(),
             password: None,
+            greeting: "".to_string(),
+            is_fixed: false,
             protocol_number: 0,
             players_number: 0,
             default_hedgehog_number: 4,
@@ -137,6 +141,7 @@
             ready_players_number: 0,
             teams: Vec::new(),
             config: RoomConfig::new(),
+            voting: None,
             game_info: None
         }
     }
@@ -169,6 +174,23 @@
         }
     }
 
+    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))
--- a/gameServer2/src/server/server.rs	Thu Jul 12 14:41:24 2018 +0200
+++ b/gameServer2/src/server/server.rs	Thu Jul 12 14:46:16 2018 +0200
@@ -1,7 +1,8 @@
 use slab;
 use utils;
 use super::{
-    client::*, room::*, actions, handlers,
+    client::HWClient, room::HWRoom, actions, handlers,
+    coretypes::{ClientId, RoomId},
     actions::{Destination, PendingMessage}
 };
 use protocol::messages::*;
@@ -105,6 +106,14 @@
         self.rooms.iter_mut().find(|(_, r)| r.name == name).map(|(_, r)| r)
     }
 
+    pub fn find_client(&self, nick: &str) -> Option<&HWClient> {
+        self.clients.iter().find(|(_, c)| c.nick == nick).map(|(_, c)| c)
+    }
+
+    pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> {
+        self.clients.iter_mut().find(|(_, c)| c.nick == nick).map(|(_, c)| c)
+    }
+
     pub fn select_clients<F>(&self, f: F) -> Vec<ClientId>
         where F: Fn(&(usize, &HWClient)) -> bool {
         self.clients.iter().filter(f)