rust/hedgewars-server/src/core/server.rs
changeset 15523 f4f6060b536c
parent 15522 4a0b06b03199
child 15525 16d3c9acd715
--- a/rust/hedgewars-server/src/core/server.rs	Mon Dec 23 18:55:25 2019 +0300
+++ b/rust/hedgewars-server/src/core/server.rs	Mon Dec 23 23:47:06 2019 +0300
@@ -3,7 +3,7 @@
     client::HwClient,
     indexslab::IndexSlab,
     room::HwRoom,
-    types::{ClientId, RoomId, ServerVar, TeamInfo},
+    types::{ClientId, GameCfg, RoomId, ServerVar, TeamInfo},
 };
 use crate::{protocol::messages::HwProtocolMessage::Greeting, utils};
 
@@ -11,7 +11,7 @@
 use bitflags::*;
 use log::*;
 use slab::Slab;
-use std::{borrow::BorrowMut, collections::HashSet, iter, mem::replace};
+use std::{borrow::BorrowMut, cmp::min, collections::HashSet, iter, mem::replace};
 
 #[derive(Debug)]
 pub enum CreateRoomError {
@@ -40,11 +40,6 @@
 }
 
 #[derive(Debug)]
-pub enum LeaveRoomError {
-    NoRoom,
-}
-
-#[derive(Debug)]
 pub struct ChangeMasterResult {
     pub old_master_id: Option<ClientId>,
     pub new_master_id: ClientId,
@@ -80,6 +75,25 @@
 }
 
 #[derive(Debug)]
+pub enum SetTeamCountError {
+    InvalidNumber,
+    NotMaster,
+}
+
+#[derive(Debug)]
+pub enum SetHedgehogsError {
+    NoTeam,
+    InvalidNumber(u8),
+    NotMaster,
+}
+
+#[derive(Debug)]
+pub enum SetConfigError {
+    NotMaster,
+    RoomFixed,
+}
+
+#[derive(Debug)]
 pub enum ModifyRoomNameError {
     AccessDenied,
     InvalidName,
@@ -198,12 +212,18 @@
     }
 
     #[inline]
-    pub fn client_and_room_mut(
-        &mut self,
-        client_id: ClientId,
-        room_id: RoomId,
-    ) -> (&HwClient, &HwRoom) {
-        (&self.clients[client_id], &mut self.rooms[room_id])
+    fn client_and_room_mut(&mut self, client_id: ClientId) -> Option<(&mut HwClient, &mut HwRoom)> {
+        let client = &mut self.clients[client_id];
+        if let Some(room_id) = client.room_id {
+            Some((client, &mut self.rooms[room_id]))
+        } else {
+            None
+        }
+    }
+
+    #[inline]
+    pub fn get_room_control(&mut self, client_id: ClientId) -> Option<HwRoomControl> {
+        HwRoomControl::new(self, client_id)
     }
 
     #[inline]
@@ -313,226 +333,6 @@
         }
     }
 
-    pub fn leave_room(&mut self, client_id: ClientId) -> Result<LeaveRoomResult, LeaveRoomError> {
-        let client = &mut self.clients[client_id];
-        if let Some(room_id) = client.room_id {
-            let room = &mut self.rooms[room_id];
-
-            room.players_number -= 1;
-            client.room_id = None;
-
-            let is_empty = room.players_number == 0;
-            let is_fixed = room.is_fixed();
-            let was_master = room.master_id == Some(client_id);
-            let was_in_game = client.is_in_game();
-            let mut removed_teams = vec![];
-
-            if is_empty && !is_fixed {
-                if client.is_ready() && room.ready_players_number > 0 {
-                    room.ready_players_number -= 1;
-                }
-
-                removed_teams = room
-                    .client_teams(client.id)
-                    .map(|t| t.name.clone())
-                    .collect();
-
-                for team_name in &removed_teams {
-                    room.remove_team(team_name);
-                }
-
-                if client.is_master() && !is_fixed {
-                    client.set_is_master(false);
-                    room.master_id = None;
-                }
-            }
-
-            client.set_is_ready(false);
-            client.set_is_in_game(false);
-
-            if !is_fixed {
-                if room.players_number == 0 {
-                    self.rooms.remove(room_id);
-                } else if room.master_id == None {
-                    let new_master_id = self.room_clients(room_id).next();
-                    if let Some(new_master_id) = new_master_id {
-                        let room = &mut self.rooms[room_id];
-                        room.master_id = Some(new_master_id);
-                        let new_master = &mut self.clients[new_master_id];
-                        new_master.set_is_master(true);
-
-                        if room.protocol_number < 42 {
-                            room.name = new_master.nick.clone();
-                        }
-
-                        room.set_join_restriction(false);
-                        room.set_team_add_restriction(false);
-                        room.set_unregistered_players_restriction(true);
-                    }
-                }
-            }
-
-            if is_empty && !is_fixed {
-                Ok(LeaveRoomResult::RoomRemoved)
-            } else {
-                Ok(LeaveRoomResult::RoomRemains {
-                    is_empty,
-                    was_master,
-                    was_in_game,
-                    new_master: self.rooms[room_id].master_id,
-                    removed_teams,
-                })
-            }
-        } else {
-            Err(LeaveRoomError::NoRoom)
-        }
-    }
-
-    pub fn change_master(
-        &mut self,
-        client_id: ClientId,
-        room_id: RoomId,
-        new_master_nick: String,
-    ) -> Result<ChangeMasterResult, ChangeMasterError> {
-        let client = &mut self.clients[client_id];
-        let room = &mut self.rooms[room_id];
-
-        if client.is_admin() || room.master_id == Some(client_id) {
-            let new_master_id = self
-                .clients
-                .iter()
-                .find(|(_, c)| c.nick == new_master_nick)
-                .map(|(id, _)| id);
-
-            match new_master_id {
-                Some(new_master_id) if new_master_id == client_id => {
-                    Err(ChangeMasterError::AlreadyMaster)
-                }
-                Some(new_master_id) => {
-                    let new_master = &mut self.clients[new_master_id];
-                    if new_master.room_id == Some(room_id) {
-                        self.clients[new_master_id].set_is_master(true);
-                        let old_master_id = room.master_id;
-                        if let Some(master_id) = old_master_id {
-                            self.clients[master_id].set_is_master(false);
-                        }
-                        room.master_id = Some(new_master_id);
-                        Ok(ChangeMasterResult {
-                            old_master_id,
-                            new_master_id,
-                        })
-                    } else {
-                        Err(ChangeMasterError::ClientNotInRoom)
-                    }
-                }
-                None => Err(ChangeMasterError::NoClient),
-            }
-        } else {
-            Err(ChangeMasterError::NoAccess)
-        }
-    }
-
-    pub fn start_game(&mut self, room_id: RoomId) -> Result<Vec<String>, StartGameError> {
-        let (room_clients, room_nicks): (Vec<_>, Vec<_>) = self
-            .clients
-            .iter()
-            .map(|(id, c)| (id, c.nick.clone()))
-            .unzip();
-
-        let room = &mut self.rooms[room_id];
-
-        if !room.has_multiple_clans() {
-            Err(StartGameError::NotEnoughClans)
-        } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number {
-            Err(StartGameError::NotReady)
-        } else if room.game_info.is_some() {
-            Err(StartGameError::AlreadyInGame)
-        } else {
-            room.start_round();
-            for id in room_clients {
-                let c = &mut self.clients[id];
-                c.set_is_in_game(true);
-                c.team_indices = room.client_team_indices(c.id);
-            }
-            Ok(room_nicks)
-        }
-    }
-
-    pub fn leave_game(&mut self, client_id: ClientId) -> Option<Vec<String>> {
-        let client = &mut self.clients[client_id];
-        let client_left = client.is_in_game();
-        if client_left {
-            client.set_is_in_game(false);
-            let room = &mut self.rooms[client.room_id.expect("Client should've been in the game")];
-
-            let team_names: Vec<_> = room
-                .client_teams(client_id)
-                .map(|t| t.name.clone())
-                .collect();
-
-            if let Some(ref mut info) = room.game_info {
-                info.teams_in_game -= team_names.len() as u8;
-
-                for team_name in &team_names {
-                    let remove_msg =
-                        utils::to_engine_msg(std::iter::once(b'F').chain(team_name.bytes()));
-                    if let Some(m) = &info.sync_msg {
-                        info.msg_log.push(m.clone());
-                    }
-                    if info.sync_msg.is_some() {
-                        info.sync_msg = None
-                    }
-                    info.msg_log.push(remove_msg);
-                }
-                Some(team_names)
-            } else {
-                unreachable!();
-            }
-        } else {
-            None
-        }
-    }
-
-    pub fn end_game(&mut self, room_id: RoomId) -> EndGameResult {
-        let room = &mut self.rooms[room_id];
-        room.ready_players_number = room.master_id.is_some() as u8;
-
-        if let Some(info) = replace(&mut room.game_info, None) {
-            let joined_mid_game_clients = self
-                .clients
-                .iter()
-                .filter(|(_, c)| c.room_id == Some(room_id) && c.is_joined_mid_game())
-                .map(|(_, c)| c.id)
-                .collect();
-
-            let unreadied_nicks: Vec<_> = self
-                .clients
-                .iter_mut()
-                .filter(|(_, c)| c.room_id == Some(room_id))
-                .map(|(_, c)| {
-                    c.set_is_ready(c.is_master());
-                    c.set_is_joined_mid_game(false);
-                    c
-                })
-                .filter_map(|c| {
-                    if !c.is_master() {
-                        Some(c.nick.clone())
-                    } else {
-                        None
-                    }
-                })
-                .collect();
-
-            EndGameResult {
-                joined_mid_game_clients,
-                left_teams: info.left_teams.clone(),
-                unreadied_nicks,
-            }
-        } else {
-            unreachable!()
-        }
-    }
-
     pub fn enable_super_power(&mut self, client_id: ClientId) -> bool {
         let client = &mut self.clients[client_id];
         if client.is_admin() {
@@ -541,116 +341,6 @@
         client.is_admin()
     }
 
-    pub fn set_room_name(
-        &mut self,
-        client_id: ClientId,
-        room_id: RoomId,
-        mut name: String,
-    ) -> Result<String, ModifyRoomNameError> {
-        let room_exists = self.has_room(&name);
-        let room = &mut self.rooms[room_id];
-        if room.is_fixed() || room.master_id != Some(client_id) {
-            Err(ModifyRoomNameError::AccessDenied)
-        } else if utils::is_name_illegal(&name) {
-            Err(ModifyRoomNameError::InvalidName)
-        } else if room_exists {
-            Err(ModifyRoomNameError::DuplicateName)
-        } else {
-            std::mem::swap(&mut room.name, &mut name);
-            Ok(name)
-        }
-    }
-
-    pub fn add_team(
-        &mut self,
-        client_id: ClientId,
-        mut info: Box<TeamInfo>,
-    ) -> Result<&TeamInfo, AddTeamError> {
-        let client = &mut self.clients[client_id];
-        if let Some(room_id) = client.room_id {
-            let room = &mut self.rooms[room_id];
-            if room.teams.len() >= room.max_teams as usize {
-                Err(AddTeamError::TooManyTeams)
-            } else if room.addable_hedgehogs() == 0 {
-                Err(AddTeamError::TooManyHedgehogs)
-            } else if room.find_team(|t| t.name == info.name) != None {
-                Err(AddTeamError::TeamAlreadyExists)
-            } else if room.game_info.is_some() {
-                Err(AddTeamError::GameInProgress)
-            } else if room.is_team_add_restricted() {
-                Err(AddTeamError::Restricted)
-            } else {
-                info.owner = client.nick.clone();
-                let team = room.add_team(client.id, *info, client.protocol_number < 42);
-                client.teams_in_game += 1;
-                client.clan = Some(team.color);
-                Ok(team)
-            }
-        } else {
-            unreachable!()
-        }
-    }
-
-    pub fn remove_team(
-        &mut self,
-        client_id: ClientId,
-        team_name: &str,
-    ) -> Result<(), RemoveTeamError> {
-        let client = &mut self.clients[client_id];
-        if let Some(room_id) = client.room_id {
-            let room = &mut self.rooms[room_id];
-            match room.find_team_owner(team_name) {
-                None => Err(RemoveTeamError::NoTeam),
-                Some((id, _)) if id != client_id => Err(RemoveTeamError::TeamNotOwned),
-                Some(_) => {
-                    client.teams_in_game -= 1;
-                    client.clan = room.find_team_color(client.id);
-                    room.remove_team(team_name);
-                    Ok(())
-                }
-            }
-        } else {
-            unreachable!();
-        }
-    }
-
-    pub fn set_team_color(
-        &mut self,
-        client_id: ClientId,
-        room_id: RoomId,
-        team_name: &str,
-        color: u8,
-    ) -> Result<(), ModifyTeamError> {
-        let client = &self.clients[client_id];
-        let room = &mut self.rooms[room_id];
-        if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) {
-            if !client.is_master() {
-                Err(ModifyTeamError::NotMaster)
-            } else {
-                team.color = color;
-                self.clients[owner].clan = Some(color);
-                Ok(())
-            }
-        } else {
-            Err(ModifyTeamError::NoTeam)
-        }
-    }
-
-    pub fn toggle_ready(&mut self, client_id: ClientId) -> bool {
-        let client = &mut self.clients[client_id];
-        if let Some(room_id) = client.room_id {
-            let room = &mut self.rooms[room_id];
-
-            client.set_is_ready(!client.is_ready());
-            if client.is_ready() {
-                room.ready_players_number += 1;
-            } else {
-                room.ready_players_number -= 1;
-            }
-        }
-        client.is_ready()
-    }
-
     #[inline]
     pub fn set_var(&mut self, client_id: ClientId, var: ServerVar) -> Result<(), AccessError> {
         if self.clients[client_id].is_admin() {
@@ -790,6 +480,489 @@
     }
 }
 
+pub struct HwRoomControl<'a> {
+    server: &'a mut HwServer,
+    client_id: ClientId,
+    room_id: RoomId,
+}
+
+impl<'a> HwRoomControl<'a> {
+    #[inline]
+    pub fn new(server: &'a mut HwServer, client_id: ClientId) -> Option<Self> {
+        if let Some(room_id) = server.clients[client_id].room_id {
+            Some(Self {
+                server,
+                client_id,
+                room_id,
+            })
+        } else {
+            None
+        }
+    }
+
+    #[inline]
+    pub fn server(&self) -> &HwServer {
+        self.server
+    }
+
+    #[inline]
+    pub fn client(&self) -> &HwClient {
+        &self.server.clients[self.client_id]
+    }
+
+    #[inline]
+    fn client_mut(&mut self) -> &mut HwClient {
+        &mut self.server.clients[self.client_id]
+    }
+
+    #[inline]
+    pub fn room(&self) -> &HwRoom {
+        &self.server.rooms[self.room_id]
+    }
+
+    #[inline]
+    fn room_mut(&mut self) -> &mut HwRoom {
+        &mut self.server.rooms[self.room_id]
+    }
+
+    #[inline]
+    pub fn get(&self) -> (&HwClient, &HwRoom) {
+        (self.client(), self.room())
+    }
+
+    #[inline]
+    fn get_mut(&mut self) -> (&mut HwClient, &mut HwRoom) {
+        (
+            &mut self.server.clients[self.client_id],
+            &mut self.server.rooms[self.room_id],
+        )
+    }
+
+    pub fn leave_room(&mut self) -> LeaveRoomResult {
+        let (client, room) = self.get_mut();
+        room.players_number -= 1;
+        client.room_id = None;
+
+        let is_empty = room.players_number == 0;
+        let is_fixed = room.is_fixed();
+        let was_master = room.master_id == Some(client.id);
+        let was_in_game = client.is_in_game();
+        let mut removed_teams = vec![];
+
+        if is_empty && !is_fixed {
+            if client.is_ready() && room.ready_players_number > 0 {
+                room.ready_players_number -= 1;
+            }
+
+            removed_teams = room
+                .client_teams(client.id)
+                .map(|t| t.name.clone())
+                .collect();
+
+            for team_name in &removed_teams {
+                room.remove_team(team_name);
+            }
+
+            if client.is_master() && !is_fixed {
+                client.set_is_master(false);
+                room.master_id = None;
+            }
+        }
+
+        client.set_is_ready(false);
+        client.set_is_in_game(false);
+
+        if !is_fixed {
+            if room.players_number == 0 {
+                self.server.rooms.remove(self.room_id);
+            } else if room.master_id == None {
+                let protocol_number = room.protocol_number;
+                let new_master_id = self.server.room_clients(self.room_id).next();
+
+                if let Some(new_master_id) = new_master_id {
+                    let room = self.room_mut();
+                    room.master_id = Some(new_master_id);
+                    let new_master = &mut self.server.clients[new_master_id];
+                    new_master.set_is_master(true);
+
+                    if protocol_number < 42 {
+                        todo!();
+                        let nick = new_master.nick.clone();
+                        self.room_mut().name = nick;
+                    }
+
+                    let room = self.room_mut();
+                    room.set_join_restriction(false);
+                    room.set_team_add_restriction(false);
+                    room.set_unregistered_players_restriction(true);
+                }
+            }
+        }
+
+        if is_empty && !is_fixed {
+            LeaveRoomResult::RoomRemoved
+        } else {
+            LeaveRoomResult::RoomRemains {
+                is_empty,
+                was_master,
+                was_in_game,
+                new_master: self.room().master_id,
+                removed_teams,
+            }
+        }
+    }
+
+    pub fn change_master(
+        &mut self,
+        new_master_nick: String,
+    ) -> Result<ChangeMasterResult, ChangeMasterError> {
+        use ChangeMasterError::*;
+        let (client, room) = self.get_mut();
+
+        if client.is_admin() || room.master_id == Some(client.id) {
+            let new_master_id = self
+                .server
+                .clients
+                .iter()
+                .find(|(_, c)| c.nick == new_master_nick)
+                .map(|(id, _)| id);
+
+            match new_master_id {
+                Some(new_master_id) if new_master_id == self.client_id => Err(AlreadyMaster),
+                Some(new_master_id) => {
+                    let new_master = &mut self.server.clients[new_master_id];
+                    if new_master.room_id == Some(self.room_id) {
+                        self.server.clients[new_master_id].set_is_master(true);
+                        let room = self.room_mut();
+                        let old_master_id = self.room().master_id;
+
+                        if let Some(master_id) = old_master_id {
+                            self.server.clients[master_id].set_is_master(false);
+                        }
+                        self.room_mut().master_id = Some(new_master_id);
+                        Ok(ChangeMasterResult {
+                            old_master_id,
+                            new_master_id,
+                        })
+                    } else {
+                        Err(ClientNotInRoom)
+                    }
+                }
+                None => Err(NoClient),
+            }
+        } else {
+            Err(NoAccess)
+        }
+    }
+
+    pub fn vote(&mut self) {
+        todo!("port from the room handler")
+    }
+
+    pub fn add_engine_message(&mut self) {
+        todo!("port from the room handler")
+    }
+
+    pub fn toggle_flag(&mut self, flags: super::room::RoomFlags) -> bool {
+        let (client, room) = self.get_mut();
+        if client.is_master() {
+            room.flags.toggle(flags);
+        }
+        client.is_master()
+    }
+
+    pub fn fix_room(&mut self) -> Result<(), AccessError> {
+        let (client, room) = self.get_mut();
+        if client.is_admin() {
+            room.set_is_fixed(true);
+            room.set_join_restriction(false);
+            room.set_team_add_restriction(false);
+            room.set_unregistered_players_restriction(true);
+            Ok(())
+        } else {
+            Err(AccessError())
+        }
+    }
+
+    pub fn unfix_room(&mut self) -> Result<(), AccessError> {
+        let (client, room) = self.get_mut();
+        if client.is_admin() {
+            room.set_is_fixed(false);
+            Ok(())
+        } else {
+            Err(AccessError())
+        }
+    }
+
+    pub fn set_room_name(&mut self, mut name: String) -> Result<String, ModifyRoomNameError> {
+        use ModifyRoomNameError::*;
+        let room_exists = self.server.has_room(&name);
+        let (client, room) = self.get_mut();
+        if room.is_fixed() || room.master_id != Some(client.id) {
+            Err(AccessDenied)
+        } else if utils::is_name_illegal(&name) {
+            Err(InvalidName)
+        } else if room_exists {
+            Err(DuplicateName)
+        } else {
+            std::mem::swap(&mut room.name, &mut name);
+            Ok(name)
+        }
+    }
+
+    pub fn set_room_greeting(&mut self, greeting: Option<String>) -> Result<(), AccessError> {
+        let (client, room) = self.get_mut();
+        if client.is_admin() {
+            room.greeting = greeting.unwrap_or(String::new());
+            Ok(())
+        } else {
+            Err(AccessError())
+        }
+    }
+
+    pub fn set_room_max_teams(&mut self, count: u8) -> Result<(), SetTeamCountError> {
+        use SetTeamCountError::*;
+        let (client, room) = self.get_mut();
+        if !client.is_master() {
+            Err(NotMaster)
+        } else if !(2..=super::room::MAX_TEAMS_IN_ROOM).contains(&count) {
+            Err(InvalidNumber)
+        } else {
+            room.max_teams = count;
+            Ok(())
+        }
+    }
+
+    pub fn set_team_hedgehogs_number(
+        &mut self,
+        team_name: &str,
+        number: u8,
+    ) -> Result<(), SetHedgehogsError> {
+        use SetHedgehogsError::*;
+        let (client, room) = self.get_mut();
+        let addable_hedgehogs = room.addable_hedgehogs();
+        if let Some((_, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) {
+            let max_hedgehogs = min(
+                super::room::MAX_HEDGEHOGS_IN_ROOM,
+                addable_hedgehogs + team.hedgehogs_number,
+            );
+            if !client.is_master() {
+                Err(NotMaster)
+            } else if !(1..=max_hedgehogs).contains(&number) {
+                Err(InvalidNumber(team.hedgehogs_number))
+            } else {
+                team.hedgehogs_number = number;
+                Ok(())
+            }
+        } else {
+            Err(NoTeam)
+        }
+    }
+
+    pub fn add_team(&mut self, mut info: Box<TeamInfo>) -> Result<&TeamInfo, AddTeamError> {
+        use AddTeamError::*;
+        let (client, room) = self.get_mut();
+        if room.teams.len() >= room.max_teams as usize {
+            Err(TooManyTeams)
+        } else if room.addable_hedgehogs() == 0 {
+            Err(TooManyHedgehogs)
+        } else if room.find_team(|t| t.name == info.name) != None {
+            Err(TeamAlreadyExists)
+        } else if room.game_info.is_some() {
+            Err(GameInProgress)
+        } else if room.is_team_add_restricted() {
+            Err(Restricted)
+        } else {
+            info.owner = client.nick.clone();
+            let team = room.add_team(client.id, *info, client.protocol_number < 42);
+            client.teams_in_game += 1;
+            client.clan = Some(team.color);
+            Ok(team)
+        }
+    }
+
+    pub fn remove_team(&mut self, team_name: &str) -> Result<(), RemoveTeamError> {
+        use RemoveTeamError::*;
+        let (client, room) = self.get_mut();
+        match room.find_team_owner(team_name) {
+            None => Err(NoTeam),
+            Some((id, _)) if id != client.id => Err(RemoveTeamError::TeamNotOwned),
+            Some(_) => {
+                client.teams_in_game -= 1;
+                client.clan = room.find_team_color(client.id);
+                room.remove_team(team_name);
+                Ok(())
+            }
+        }
+    }
+
+    pub fn set_team_color(&mut self, team_name: &str, color: u8) -> Result<(), ModifyTeamError> {
+        use ModifyTeamError::*;
+        let (client, room) = self.get_mut();
+        if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) {
+            if !client.is_master() {
+                Err(NotMaster)
+            } else {
+                team.color = color;
+                self.server.clients[owner].clan = Some(color);
+                Ok(())
+            }
+        } else {
+            Err(NoTeam)
+        }
+    }
+
+    pub fn set_config(&mut self, cfg: GameCfg) -> Result<(), SetConfigError> {
+        use SetConfigError::*;
+        let (client, room) = self.get_mut();
+        if room.is_fixed() {
+            Err(RoomFixed)
+        } else if !client.is_master() {
+            Err(NotMaster)
+        } else {
+            let cfg = match cfg {
+                GameCfg::Scheme(name, mut values) => {
+                    if client.protocol_number == 49 && values.len() >= 2 {
+                        let mut s = "X".repeat(50);
+                        s.push_str(&values.pop().unwrap());
+                        values.push(s);
+                    }
+                    GameCfg::Scheme(name, values)
+                }
+                cfg => cfg,
+            };
+
+            room.set_config(cfg);
+            Ok(())
+        }
+    }
+
+    pub fn save_config(&mut self, name: String, location: String) {
+        self.room_mut().save_config(name, location);
+    }
+
+    pub fn delete_config(&mut self, name: &str) -> bool {
+        self.room_mut().delete_config(name)
+    }
+
+    pub fn toggle_ready(&mut self) -> bool {
+        let (client, room) = self.get_mut();
+        client.set_is_ready(!client.is_ready());
+        if client.is_ready() {
+            room.ready_players_number += 1;
+        } else {
+            room.ready_players_number -= 1;
+        }
+        client.is_ready()
+    }
+
+    pub fn start_game(&mut self) -> Result<Vec<String>, StartGameError> {
+        use StartGameError::*;
+        let (room_clients, room_nicks): (Vec<_>, Vec<_>) = self
+            .server
+            .clients
+            .iter()
+            .map(|(id, c)| (id, c.nick.clone()))
+            .unzip();
+
+        let room = self.room_mut();
+
+        if !room.has_multiple_clans() {
+            Err(NotEnoughClans)
+        } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number {
+            Err(NotReady)
+        } else if room.game_info.is_some() {
+            Err(AlreadyInGame)
+        } else {
+            room.start_round();
+            for id in room_clients {
+                let team_indices = self.room().client_team_indices(id);
+                let c = &mut self.server.clients[id];
+                c.set_is_in_game(true);
+                c.team_indices = team_indices;
+            }
+            Ok(room_nicks)
+        }
+    }
+
+    pub fn leave_game(&mut self) -> Option<Vec<String>> {
+        let (client, room) = self.get_mut();
+        let client_left = client.is_in_game();
+        if client_left {
+            client.set_is_in_game(false);
+
+            let team_names: Vec<_> = room
+                .client_teams(client.id)
+                .map(|t| t.name.clone())
+                .collect();
+
+            if let Some(ref mut info) = room.game_info {
+                info.teams_in_game -= team_names.len() as u8;
+
+                for team_name in &team_names {
+                    let remove_msg =
+                        utils::to_engine_msg(std::iter::once(b'F').chain(team_name.bytes()));
+                    if let Some(m) = &info.sync_msg {
+                        info.msg_log.push(m.clone());
+                    }
+                    if info.sync_msg.is_some() {
+                        info.sync_msg = None
+                    }
+                    info.msg_log.push(remove_msg);
+                }
+                Some(team_names)
+            } else {
+                unreachable!();
+            }
+        } else {
+            None
+        }
+    }
+
+    pub fn end_game(&mut self) -> Option<EndGameResult> {
+        let room = self.room_mut();
+        room.ready_players_number = room.master_id.is_some() as u8;
+
+        if let Some(info) = replace(&mut room.game_info, None) {
+            let room_id = room.id;
+            let joined_mid_game_clients = self
+                .server
+                .clients
+                .iter()
+                .filter(|(_, c)| c.room_id == Some(self.room_id) && c.is_joined_mid_game())
+                .map(|(_, c)| c.id)
+                .collect();
+
+            let unreadied_nicks: Vec<_> = self
+                .server
+                .clients
+                .iter_mut()
+                .filter(|(_, c)| c.room_id == Some(room_id))
+                .map(|(_, c)| {
+                    c.set_is_ready(c.is_master());
+                    c.set_is_joined_mid_game(false);
+                    c
+                })
+                .filter_map(|c| {
+                    if !c.is_master() {
+                        Some(c.nick.clone())
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            Some(EndGameResult {
+                joined_mid_game_clients,
+                left_teams: info.left_teams.clone(),
+                unreadied_nicks,
+            })
+        } else {
+            None
+        }
+    }
+}
+
 fn allocate_room(rooms: &mut Slab<HwRoom>) -> &mut HwRoom {
     let entry = rooms.vacant_entry();
     let room = HwRoom::new(entry.key());