implementation of team related messages
authoralfadur
Thu, 21 Jun 2018 17:23:10 -0400
changeset 13419 81e0ed105f5d
parent 13418 bb24c3414b0d
child 13420 cf189adc07fd
implementation of team related messages
gameServer2/src/protocol/messages.rs
gameServer2/src/protocol/parser.rs
gameServer2/src/protocol/test.rs
gameServer2/src/server/actions.rs
gameServer2/src/server/client.rs
gameServer2/src/server/coretypes.rs
gameServer2/src/server/handlers/inroom.rs
gameServer2/src/server/handlers/lobby.rs
gameServer2/src/server/handlers/loggingin.rs
gameServer2/src/server/handlers/mod.rs
gameServer2/src/server/network.rs
gameServer2/src/server/room.rs
gameServer2/src/server/server.rs
--- a/gameServer2/src/protocol/messages.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/protocol/messages.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -86,8 +86,13 @@
     RoomLeft(String, String),
     RoomRemove(String),
     RoomUpdated(String, Vec<String>),
+    TeamAdd(Vec<String>),
+    TeamRemove(String),
+    TeamAccepted(String),
+    TeamColor(String, u8),
+    HedgehogsNumber(String, u8),
+
     ServerMessage(String),
-
     Warning(String),
     Error(String),
     Connected(u32),
@@ -183,9 +188,11 @@
     };
 }
 
-fn construct_message(mut msg: Vec<&str>) -> String {
-    msg.push("\n");
-    msg.join("\n")
+fn construct_message(header: &[&str], msg: &Vec<String>) -> String {
+    let mut v: Vec<_> = header.iter().map(|s| *s).collect();
+    v.extend(msg.iter().map(|s| &s[..]));
+    v.push("\n");
+    v.join("\n")
 }
 
 impl HWServerMessage {
@@ -202,40 +209,26 @@
             Nick(nick) => msg!["NICK", nick],
             Proto(proto) => msg!["PROTO", proto],
             LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg],
-            LobbyJoined(nicks) => {
-                let mut v = vec!["LOBBY:JOINED"];
-                v.extend(nicks.iter().map(|n| { &n[..] }));
-                construct_message(v)
-            },
-            ClientFlags(flags, nicks)
-                => {
-                let mut v = vec!["CLIENT_FLAGS"];
-                v.push(&flags[..]);
-                v.extend(nicks.iter().map(|n| { &n[..] }));
-                construct_message(v)
-            },
-            Rooms(info) => {
-                let mut v = vec!["ROOMS"];
-                v.extend(info.iter().map(|n| { &n[..] }));
-                construct_message(v)
-            },
-            RoomAdd(info) => {
-                let mut v = vec!["ROOM", "ADD"];
-                v.extend(info.iter().map(|n| { &n[..] }));
-                construct_message(v)
-            },
-            RoomJoined(nicks) => {
-                let mut v = vec!["JOINED"];
-                v.extend(nicks.iter().map(|n| { &n[..] }));
-                construct_message(v)
-            },
+            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) => {
-                let mut v = vec!["ROOM", "UPD", name];
-                v.extend(info.iter().map(|n| { &n[..] }));
-                construct_message(v)
-            }
+            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],
             ChatMsg(nick, msg) => msg!["CHAT", nick, msg],
             ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
             Warning(msg) => msg!["WARNING", msg],
--- a/gameServer2/src/protocol/parser.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/protocol/parser.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -8,6 +8,9 @@
     messages::{HWProtocolMessage, HWProtocolMessage::*},
     test::gen_proto_msg
 };
+use server::coretypes::{
+    HedgehogInfo, TeamInfo
+};
 
 named!(end_of_message, tag!("\n\n"));
 named!(str_line<&[u8],   &str>, map_res!(not_line_ending, str::from_utf8));
@@ -15,6 +18,15 @@
 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!(opt_param<&[u8], Option<String> >, opt!(map!(flat_map!(preceded!(eol, str_line), non_empty), String::from)));
+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; 8]>,
+    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!(basic_message<&[u8], HWProtocolMessage>, alt!(
       do_parse!(tag!("PING") >> (Ping))
@@ -88,6 +100,28 @@
                     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(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 >>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gameServer2/src/protocol/test.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -0,0 +1,134 @@
+use proptest::{
+    test_runner::{TestRunner, Reason},
+    arbitrary::{any, any_with, Arbitrary, StrategyFor},
+    strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree},
+    string::RegexGeneratorValueTree
+};
+
+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<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: ident, $($num: expr => $constr: ident $res: tt),*) => (
+        match $var {
+            $($num => (proto_msg_case!($constr $res)).boxed()),*,
+            _ => Just($default).boxed()
+        }
+    )
+}
+
+#[derive(Debug)]
+struct Ascii(String);
+
+struct AsciiValueTree(RegexGeneratorValueTree<String>);
+
+impl ValueTree for AsciiValueTree {
+    type Value = Ascii;
+
+    fn current(&self) -> Self::Value { Ascii(self.0.current()) }
+    fn simplify(&mut self) -> bool { self.0.simplify() }
+    fn complicate(&mut self) -> bool { self.0.complicate() }
+}
+
+impl Arbitrary for Ascii {
+    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()
+    }
+
+    type Strategy = BoxedStrategy<Ascii>;
+    type ValueTree = Box<ValueTree<Value = Ascii>>;
+}
+
+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(u32),
+        11 => Password(Ascii, Ascii),
+        12 => Checker(u32, Ascii, Ascii),
+        13 => List(),
+        14 => Chat(Ascii),
+        15 => CreateRoom(Ascii, Option<Ascii>),
+        16 => JoinRoom(Ascii, Option<Ascii>),
+        17 => Follow(Ascii),
+        //18 => Rnd(Vec<String>),
+        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(TeamInfo),
+        32 => RemoveTeam(Ascii),
+        //33 => SetHedgehogsNumber(String, u8),
+        //34 => SetTeamColor(String, 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(String),
+        51 => ForceVote(Ascii),
+        //52 => Save(String, String),
+        53 => Delete(Ascii),
+        54 => SaveRoom(Ascii),
+        55 => LoadRoom(Ascii),
+        56 => Malformed(),
+        57 => Empty()
+    )});
+    res.boxed()
+}
\ No newline at end of file
--- a/gameServer2/src/server/actions.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/actions.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -3,7 +3,8 @@
 };
 use super::{
     server::HWServer,
-    client::ClientId,
+    room::RoomId,
+    client::{ClientId, HWClient},
     room::HWRoom,
     handlers
 };
@@ -13,11 +14,69 @@
     HWServerMessage::*
 };
 
+pub enum Destination {
+    ToSelf,
+        ToAll {
+        room_id: Option<RoomId>,
+        protocol: Option<u32>,
+        skip_self: bool
+    }
+}
+
+pub struct PendingMessage {
+    pub destination: Destination,
+    pub message: HWServerMessage
+}
+
+impl PendingMessage {
+    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: u32) -> 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(self) -> PendingMessage { PendingMessage::send_self(self) }
+    pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) }
+}
+
 pub enum Action {
-    SendAll(HWServerMessage),
-    SendMe(HWServerMessage),
-    SendAllButMe(HWServerMessage),
-    SendToSelected(Vec<ClientId>, HWServerMessage),
+    Send(PendingMessage),
     RemoveClient,
     ByeClient(String),
     ReactProtocolMessage(HWProtocolMessage),
@@ -28,65 +87,55 @@
     MoveToRoom(RoomId),
     MoveToLobby(String),
     ChangeMaster(RoomId, Option<ClientId>),
+    RemoveTeam(String),
+    RemoveClientTeams,
     SendRoomUpdate(Option<String>),
     Warn(String),
     ProtocolError(String)
 }
 
 use self::Action::*;
-use server::room::RoomId;
 
-pub fn run_action(server: &mut HWServer, token: usize, action: Action) {
+pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) {
     match action {
-        SendAll(msg) =>
-            server.send_all(msg),
-        SendMe(msg) =>
-            server.send_self(token, msg),
-        SendAllButMe(msg) =>
-            server.send_others(token, msg),
-        SendToSelected(client_ids, msg) =>
-            server.send_to_selected(client_ids, msg),
+        Send(msg) => server.send(client_id, msg.destination, msg.message),
         ByeClient(msg) => {
             let room_id;
             let nick;
             {
-                let c = &server.clients[token];
+                let c = &server.clients[client_id];
                 room_id = c.room_id;
                 nick = c.nick.clone();
             }
 
-            let action = room_id.map (|id| {
-                if id == server.lobby_id {
-                    SendAll(LobbyLeft(nick, msg.clone()))
-                } else {
-                    MoveToLobby(format!("quit: {}", msg.clone()))
+            room_id.map (|id| {
+                if id != server.lobby_id {
+                    server.react(client_id, vec![
+                        MoveToLobby(format!("quit: {}", msg.clone()))]);
                 }
             });
 
-            if let Some(action) = action {
-                server.react(token, vec![action]);
-            }
-
-            server.react(token, vec![
-                SendMe(Bye(msg)),
+            server.react(client_id, vec![
+                LobbyLeft(nick, msg.clone()).send_all().action(),
+                Bye(msg).send_self().action(),
                 RemoveClient]);
         },
         RemoveClient => {
-            server.removed_clients.push(token);
-            if server.clients.contains(token) {
-                server.clients.remove(token);
+            server.removed_clients.push(client_id);
+            if server.clients.contains(client_id) {
+                server.clients.remove(client_id);
             }
         },
         ReactProtocolMessage(msg) =>
-            handlers::handle(server, token, msg),
+            handlers::handle(server, client_id, msg),
         CheckRegistered =>
-            if server.clients[token].protocol_number > 0 && server.clients[token].nick != "" {
-                server.react(token, vec![
+            if server.clients[client_id].protocol_number > 0 && server.clients[client_id].nick != "" {
+                server.react(client_id, vec![
                     JoinLobby,
                     ]);
             },
         JoinLobby => {
-            server.clients[token].room_id = Some(server.lobby_id);
+            server.clients[client_id].room_id = Some(server.lobby_id);
 
             let joined_msg;
             {
@@ -98,7 +147,7 @@
                 }
                 joined_msg = LobbyJoined(lobby_nicks);
             }
-            let everyone_msg = LobbyJoined(vec![server.clients[token].nick.clone()]);
+            let everyone_msg = LobbyJoined(vec![server.clients[client_id].nick.clone()]);
             let flags_msg = ClientFlags(
                 "+i".to_string(),
                 server.clients.iter()
@@ -111,53 +160,44 @@
                 .flat_map(|(_, r)|
                     r.info(r.master_id.map(|id| &server.clients[id])))
                 .collect());
-            server.react(token, vec![
-                SendAllButMe(everyone_msg),
-                SendMe(joined_msg),
-                SendMe(flags_msg),
-                SendMe(server_msg),
-                SendMe(rooms_msg),
+            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_protocol;
-            let room_info;
             let room_id = server.add_room();;
-            {
+            let actions = {
                 let r = &mut server.rooms[room_id];
-                let c = &mut server.clients[token];
+                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;
 
-                room_protocol = r.protocol_number;
-                room_info = r.info(Some(&c));
-            }
-            let protocol_client_ids = server.protocol_clients(room_protocol);
-            server.react(token, vec![
-                SendToSelected(protocol_client_ids, RoomAdd(room_info)),
-                MoveToRoom(room_id)]);
+                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 room_protocol;
-            let room_name;
-            {
+            let actions = {
                 let r = &mut server.rooms[room_id];
-                room_protocol = r.protocol_number;
-                room_name = r.name.clone();
-            }
+                vec![RoomRemove(r.name.clone()).send_all()
+                        .with_protocol(r.protocol_number).action()]
+            };
             server.rooms.remove(room_id);
-            let protocol_client_ids = server.protocol_clients(room_protocol);
-            server.react(token, vec![
-                SendToSelected(protocol_client_ids, RoomRemove(room_name))]);
+            server.react(client_id, actions);
         }
         MoveToRoom(room_id) => {
-            let flags_msg;
-            let nick;
-            {
+            let actions = {
                 let r = &mut server.rooms[room_id];
-                let c = &mut server.clients[token];
+                let c = &mut server.clients[client_id];
                 r.players_number += 1;
                 c.room_id = Some(room_id);
                 c.is_joined_mid_game = false;
@@ -169,20 +209,18 @@
                     c.is_ready = false;
                     c.is_master = false;
                 }
-                flags_msg = ClientFlags("+i".to_string(), vec![c.nick.clone()]);
-                nick = c.nick.clone();
-            }
-            let rooms_client_ids = server.room_clients(room_id);
-            server.react(token, vec![
-                SendToSelected(rooms_client_ids, RoomJoined(vec![nick])),
-                SendAll(flags_msg),
-                SendRoomUpdate(None)]);
+                let flags_msg = ClientFlags("+i".to_string(), vec![c.nick.clone()]);
+
+                vec![RoomJoined(vec![c.nick.clone()]).send_all().in_room(room_id).action(),
+                     flags_msg.send_all().action(),
+                     SendRoomUpdate(None)]
+            };
+            server.react(client_id, actions);
         },
         MoveToLobby(msg) => {
             let mut actions = Vec::new();
-            let other_client_ids = server.other_clients_in_room(token);
             let lobby_id = server.lobby_id;
-            if let (c, Some(r)) = server.client_and_room(token) {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
                 r.players_number -= 1;
                 if c.is_ready {
                     r.ready_players_number -= 1;
@@ -190,63 +228,85 @@
                 if r.players_number > 0 && c.is_master {
                     actions.push(ChangeMaster(r.id, None));
                 }
-                actions.push(SendToSelected(other_client_ids, RoomLeft(c.nick.clone(), msg)));
-                actions.push(SendAll(ClientFlags("-i".to_string(), vec![c.nick.clone()])));
+                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(token, actions);
+            server.react(client_id, actions);
             actions = Vec::new();
 
-            if let (c, Some(r)) = server.client_and_room(token) {
+            if let (c, Some(r)) = server.client_and_room(client_id) {
                 c.room_id = Some(lobby_id);
                 if r.players_number == 0 {
                     actions.push(RemoveRoom(r.id));
                 }
             }
-            server.react(token, actions)
+            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 = new_id.or_else(||
-                room_client_ids.iter().find(|id| **id != token).map(|id| *id));
+                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(token) {
-                if let Some(id) = r.master_id {
-                    c.is_master = false;
-                    r.master_id = None;
-                    actions.push(SendToSelected(room_client_ids.clone(), ClientFlags("-h".to_string(), vec![c.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;
+                        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 let Some(nick) = new_nick {
-                    actions.push(SendToSelected(room_client_ids, ClientFlags("+h".to_string(), vec![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);
-            server.react(token, actions);
+            server.react(client_id, actions);
+        }
+        RemoveTeam(name) => {
+            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
+                r.remove_team(&name);
+                vec![TeamRemove(name).send_all().in_room(r.id).action(),
+                     SendRoomUpdate(None)]
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
+        },
+        RemoveClientTeams => {
+            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
+                r.client_teams(c.id).map(|t| RemoveTeam(t.name.clone())).collect()
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
         }
         SendRoomUpdate(old_name) => {
-            let room_data =
-                if let (c, Some(r)) = server.client_and_room(token) {
-                    let name = old_name.unwrap_or_else(|| r.name.clone());
-                    Some((name, r.protocol_number, r.info(Some(&c))))
-                } else {
-                    None
-                };
-
-            if let Some((room_name, protocol, room_info)) = room_data {
-                let protocol_clients = server.protocol_clients(protocol);
-                server.react(token,
-                             vec![SendToSelected(protocol_clients, RoomUpdated(room_name, room_info))]);
-            }
+            let actions = if let (c, Some(r)) = server.client_and_room(client_id) {
+                let name = old_name.unwrap_or_else(|| r.name.clone());
+                vec![RoomUpdated(name, r.info(Some(&c)))
+                    .send_all().with_protocol(r.protocol_number).action()]
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
         }
 
         Warn(msg) => {
-            run_action(server, token,SendMe(Warning(msg)));
+            run_action(server, client_id, Warning(msg).send_self().action());
         }
         ProtocolError(msg) => {
-            run_action(server, token, SendMe(Error(msg)))
+            run_action(server, client_id, Error(msg).send_self().action())
         }
     }
 }
--- a/gameServer2/src/server/client.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/client.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -7,6 +7,8 @@
     pub protocol_number: u32,
     pub is_master: bool,
     pub is_ready: bool,
+    pub teams_in_game: u8,
+    pub clan: Option<u8>,
     pub is_joined_mid_game: bool,
 }
 
@@ -19,6 +21,8 @@
             protocol_number: 0,
             is_master: false,
             is_ready: false,
+            teams_in_game: 0,
+            clan: None,
             is_joined_mid_game: false,
         }
     }
--- a/gameServer2/src/server/coretypes.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/coretypes.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -12,19 +12,19 @@
 
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub struct TeamInfo {
-    name: String,
-    color: u8,
-    grave: String,
-    fort: String,
-    voice_pack: String,
-    flag: String,
-    difficulty: u8,
-    hedgehogs_number: u8,
-    hedgehogs: [HedgehogInfo; 8],
+    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; 8],
 }
 
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub struct HedgehogInfo {
-    name: String,
-    hat: String,
+    pub name: String,
+    pub hat: String,
 }
--- a/gameServer2/src/server/handlers/inroom.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/handlers/inroom.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -1,34 +1,36 @@
 use mio;
 
-use server::{
-    server::HWServer,
-    actions::{Action, Action::*}
-};
 use protocol::messages::{
     HWProtocolMessage,
     HWServerMessage::*
 };
+use server::{
+    server::HWServer,
+    client::ClientId,
+    room::HWRoom,
+    actions::{Action, Action::*}
+};
 use utils::is_name_illegal;
 use std::mem::swap;
 
-pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
+pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     use protocol::messages::HWProtocolMessage::*;
     match message {
-        Part(None) => server.react(token, vec![
+        Part(None) => server.react(client_id, vec![
             MoveToLobby("part".to_string())]),
-        Part(Some(msg)) => server.react(token, vec![
+        Part(Some(msg)) => server.react(client_id, vec![
             MoveToLobby(format!("part: {}", msg))]),
         Chat(msg) => {
-            let chat_msg;
-            let room_id;
-            {
-                let c = &mut server.clients[token];
-                chat_msg = ChatMsg(c.nick.clone(), msg);
-                room_id = c.room_id;
-            }
-            let client_ids = server.other_clients_in_room(token);
-            server.react(token, vec![
-                SendToSelected(client_ids, chat_msg)]);
+            let actions = {
+                let c = &mut server.clients[client_id];
+                let chat_msg = ChatMsg(c.nick.clone(), msg);
+                if let Some(room_id) = c.room_id {
+                    vec![chat_msg.send_all().in_room(room_id).but_self().action()]
+                } else {
+                    Vec::new()
+                }
+            };
+            server.react(client_id, actions);
         },
         RoomName(new_name) => {
             let actions =
@@ -38,15 +40,130 @@
                     vec![Warn("A room with the same name already exists.".to_string())]
                 } else {
                     let mut old_name = new_name.clone();
-                    if let (c, Some(r)) = server.client_and_room(token) {
+                    if let (c, Some(r)) = server.client_and_room(client_id) {
                         swap(&mut r.name, &mut old_name);
                         vec![SendRoomUpdate(Some(old_name))]
                     } else {
                         Vec::new()
                     }
                 };
-            server.react(token, actions);
+            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 {
+                    r.ready_players_number -= 1;
+                    "-r"
+                } else {
+                    r.ready_players_number += 1;
+                    "+r"
+                };
+                c.is_ready = !c.is_ready;
+                vec![ClientFlags(flags.to_string(), vec![c.nick.clone()])
+                    .send_all().in_room(r.id).action()]
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
         }
-        _ => warn!("Unimplemented!"),
+        AddTeam(mut 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 {
+                    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 != None {
+                    actions.push(Warn("Joining not possible: Round is in progress.".to_string()))
+                } else {
+                    let team = r.add_team(c.id, info);
+                    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) => {
+            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 {
+                        vec![ProtocolError("You're not the room master!".to_string())]
+                    } else if number < 1 || number > 8
+                           || 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()))]
+                }
+            } else {
+                Vec::new()
+            };
+            server.react(client_id, actions);
+        },
+        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 {
+                        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()))]
+                }
+            } else {
+                Vec::new()
+            };
+
+            if let Some(id) = owner_id {
+                server.clients[id].clan = Some(color);
+            }
+
+            server.react(client_id, actions);
+        }
+        _ => warn!("Unimplemented!")
     }
 }
--- a/gameServer2/src/server/handlers/lobby.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/handlers/lobby.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -2,6 +2,7 @@
 
 use server::{
     server::HWServer,
+    client::ClientId,
     actions::{Action, Action::*}
 };
 use protocol::messages::{
@@ -10,7 +11,7 @@
 };
 use utils::is_name_illegal;
 
-pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
+pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     use protocol::messages::HWProtocolMessage::*;
     match message {
         CreateRoom(name, password) => {
@@ -22,15 +23,15 @@
                 } else {
                     let flags_msg = ClientFlags(
                         "+hr".to_string(),
-                        vec![server.clients[token].nick.clone()]);
+                        vec![server.clients[client_id].nick.clone()]);
                     vec![AddRoom(name, password),
-                         SendMe(flags_msg)]
+                         flags_msg.send_self().action()]
                 };
-            server.react(token, actions);
+            server.react(client_id, actions);
         },
         Chat(msg) => {
-            let chat_msg = ChatMsg(server.clients[token].nick.clone(), msg);
-            server.react(token, vec![SendAllButMe(chat_msg)]);
+            let chat_msg = ChatMsg(server.clients[client_id].nick.clone(), msg);
+            server.react(client_id, vec![chat_msg.send_all().but_self().action()]);
         },
         JoinRoom(name, password) => {
             let actions;
@@ -41,7 +42,7 @@
                     .filter(|(_, c)| c.room_id == room_id)
                     .map(|(_, c)| c.nick.clone())
                     .collect();
-                let c = &mut server.clients[token];
+                let c = &mut server.clients[client_id];
                 actions = match room {
                     None => vec![Warn("No such room.".to_string())],
                     Some((_, r)) => {
@@ -49,12 +50,12 @@
                             vec![Warn("Room version incompatible to your Hedgewars version!".to_string())]
                         } else {
                             vec![MoveToRoom(r.id),
-                                 SendMe(RoomJoined(nicks))]
+                                 RoomJoined(nicks).send_self().action()]
                         }
                     }
                 };
             }
-            server.react(token, actions);
+            server.react(client_id, actions);
         },
         List => warn!("Deprecated LIST message received"),
         _ => warn!("Incorrect command in lobby state"),
--- a/gameServer2/src/server/handlers/loggingin.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/handlers/loggingin.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -1,18 +1,21 @@
 use mio;
 
-use server::server::HWServer;
-use server::actions::Action;
-use server::actions::Action::*;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage::*;
+use server::{
+    server::HWServer,
+    client::ClientId,
+    actions::{Action, Action::*}
+};
+use protocol::messages::{
+    HWProtocolMessage, HWServerMessage::*
+};
 use utils::is_name_illegal;
 
-pub fn handle(server: & mut HWServer, token: usize, message: HWProtocolMessage) {
+pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
     match message {
         HWProtocolMessage::Nick(nick) => {
             let actions;
             {
-                let client = &mut server.clients[token];
+                let client = &mut server.clients[client_id];
                 debug!("{} {}", nick, is_name_illegal(&nick));
                 actions = if client.room_id != None {
                     unreachable!()
@@ -25,15 +28,16 @@
                 }
                 else {
                     client.nick = nick.clone();
-                    vec![SendMe(Nick(nick)), CheckRegistered]
+                    vec![Nick(nick).send_self().action(),
+                         CheckRegistered]
                 };
             }
-            server.react(token, actions);
+            server.react(client_id, actions);
         },
         HWProtocolMessage::Proto(proto) => {
             let actions;
             {
-                let client = &mut server.clients[token];
+                let client = &mut server.clients[client_id];
                 actions = if client.protocol_number != 0 {
                     vec![ProtocolError("Protocol already known.".to_string())]
                 }
@@ -42,10 +46,11 @@
                 }
                 else {
                     client.protocol_number = proto;
-                    vec![SendMe(Proto(proto)), CheckRegistered]
+                    vec![Proto(proto).send_self().action(),
+                         CheckRegistered]
                 };
             }
-            server.react(token, actions);
+            server.react(client_id, actions);
         },
         _ => warn!("Incorrect command in logging-in state"),
     }
--- a/gameServer2/src/server/handlers/mod.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/handlers/mod.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -15,7 +15,7 @@
 pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
     match message {
         HWProtocolMessage::Ping =>
-            server.react(token, vec![SendMe(Pong)]),
+            server.react(token, vec![Pong.send_self().action()]),
         HWProtocolMessage::Quit(Some(msg)) =>
             server.react(token, vec![ByeClient("User quit: ".to_string() + &msg)]),
         HWProtocolMessage::Quit(None) =>
--- a/gameServer2/src/server/network.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/network.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -17,7 +17,7 @@
 use utils;
 use protocol::{ProtocolDecoder, messages::*};
 use super::{
-    server::{HWServer, PendingMessage, Destination},
+    server::{HWServer},
     client::ClientId
 };
 
@@ -166,39 +166,13 @@
 
     fn flush_server_messages(&mut self) {
         debug!("{} pending server messages", self.server.output.len());
-        for PendingMessage(destination, msg) in self.server.output.drain(..) {
-            debug!("Message {:?} to {:?}", msg, destination);
-            match destination {
-                Destination::ToAll => {
-                    let msg_string = msg.to_raw_protocol();
-                    for (client_id, client) in self.clients.iter_mut() {
-                        client.send_string(&msg_string);
-                        self.pending.insert((client_id, NetworkClientState::NeedsWrite));
-                    }
-                },
-                Destination::ToSelf(id)  => {
-                    if let Some(client) = self.clients.get_mut(id) {
-                        client.send_msg(msg);
-                        self.pending.insert((id, NetworkClientState::NeedsWrite));
-                    }
-                }
-                Destination::ToOthers(id) => {
-                    let msg_string = msg.to_raw_protocol();
-                    for (client_id, client) in self.clients.iter_mut() {
-                        if client_id != id {
-                            client.send_string(&msg_string);
-                            self.pending.insert((client_id, NetworkClientState::NeedsWrite));
-                        }
-                    }
-                },
-                Destination::ToSelected(client_ids) => {
-                    let msg_string = msg.to_raw_protocol();
-                    for id in client_ids {
-                        if let Some(client) = self.clients.get_mut(id) {
-                            client.send_string(&msg_string);
-                            self.pending.insert((id, NetworkClientState::NeedsWrite));
-                        }
-                    }
+        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));
                 }
             }
         }
--- a/gameServer2/src/server/room.rs	Thu Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/room.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -1,8 +1,10 @@
+use std::iter;
 use server::{
-    coretypes::TeamInfo,
+    coretypes::{TeamInfo, GameCfg},
     client::{ClientId, HWClient}
 };
 
+const MAX_HEDGEHOGS_IN_ROOM: u8 = 48;
 pub type RoomId = usize;
 
 pub struct HWRoom {
@@ -13,8 +15,11 @@
     pub protocol_number: u32,
 
     pub players_number: u32,
+    pub default_hedgehog_number: u8,
+    pub team_limit: u8,
     pub ready_players_number: u8,
-    pub teams: Vec<TeamInfo>,
+    pub teams: Vec<(ClientId, TeamInfo)>,
+    pub game_info: Option<()>
 }
 
 impl HWRoom {
@@ -26,11 +31,65 @@
             password: None,
             protocol_number: 0,
             players_number: 0,
+            default_hedgehog_number: 4,
+            team_limit: 8,
             ready_players_number: 0,
-            teams: Vec::new()
+            teams: Vec::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) -> &TeamInfo {
+        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 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().map(|(_, t)| t).find(|t| f(*t))
+    }
+
+    pub fn client_teams(&self, client_id: ClientId) -> impl Iterator<Item = &TeamInfo> {
+        self.teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
+    }
+
+    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 info(&self, master: Option<&HWClient>) -> Vec<String> {
         let flags = "-".to_string();
         vec![
@@ -39,10 +98,25 @@
             self.players_number.to_string(),
             self.teams.len().to_string(),
             master.map_or("?", |c| &c.nick).to_string(),
-            "Default".to_string(),
+            "Normal".to_string(),
             "Default".to_string(),
             "Default".to_string(),
             "Default".to_string(),
         ]
     }
+
+    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 Jun 21 23:09:20 2018 +0200
+++ b/gameServer2/src/server/server.rs	Thu Jun 21 17:23:10 2018 -0400
@@ -1,27 +1,19 @@
 use slab;
 use utils;
 use super::{
-    client::*, room::*, actions, handlers
+    client::*, room::*, actions, handlers,
+    actions::{Destination, PendingMessage}
 };
 use protocol::messages::*;
 
 type Slab<T> = slab::Slab<T>;
 
-#[derive(Debug)]
-pub enum Destination {
-    ToAll,
-    ToSelf(ClientId),
-    ToOthers(ClientId),
-    ToSelected(Vec<ClientId>)
-}
-
-pub struct PendingMessage(pub Destination, pub HWServerMessage);
 
 pub struct HWServer {
     pub clients: Slab<HWClient>,
     pub rooms: Slab<HWRoom>,
     pub lobby_id: RoomId,
-    pub output: Vec<PendingMessage>,
+    pub output: Vec<(Vec<ClientId>, HWServerMessage)>,
     pub removed_clients: Vec<ClientId>,
 }
 
@@ -47,7 +39,7 @@
             let client = HWClient::new(entry.key());
             entry.insert(client);
         }
-        self.send_self(key, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
+        self.send(key, Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
         key
     }
 
@@ -69,24 +61,28 @@
         handlers::handle(self, client_id, msg);
     }
 
-    pub fn send_all(&mut self, msg: HWServerMessage) {
-        self.output.push(PendingMessage(
-            Destination::ToAll, msg));
+    fn get_recipients(&self, client_id: ClientId, destination: Destination) -> Vec<ClientId> {
+        let mut ids = match destination {
+            Destination::ToSelf => vec![client_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<_>>(),
+            _ => Vec::new()
+        };
+        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_self(&mut self, client_id: ClientId, msg: HWServerMessage) {
-        self.output.push(PendingMessage(
-            Destination::ToSelf(client_id), msg));
-    }
-
-    pub fn send_others(&mut self, client_id: ClientId, msg: HWServerMessage) {
-        self.output.push(PendingMessage(
-            Destination::ToOthers(client_id), msg));
-    }
-
-    pub fn send_to_selected(&mut self, client_ids: Vec<ClientId>, msg: HWServerMessage) {
-        self.output.push(PendingMessage(
-            Destination::ToSelected(client_ids), msg));
+    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>) {