implement watch message apart from replay deserializing
authoralfadur
Wed, 10 Apr 2019 23:56:53 +0300
changeset 14785 a1077e8d26f4
parent 14784 8390d5e4e39c
child 14786 8ecdb5c6bb2a
implement watch message apart from replay deserializing
rust/hedgewars-server/src/protocol/messages.rs
rust/hedgewars-server/src/protocol/parser.rs
rust/hedgewars-server/src/server/coretypes.rs
rust/hedgewars-server/src/server/database.rs
rust/hedgewars-server/src/server/handlers.rs
rust/hedgewars-server/src/server/handlers/common.rs
rust/hedgewars-server/src/server/handlers/inroom.rs
rust/hedgewars-server/src/server/handlers/lobby.rs
rust/hedgewars-server/src/server/io.rs
rust/hedgewars-server/src/server/room.rs
--- a/rust/hedgewars-server/src/protocol/messages.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/protocol/messages.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -8,7 +8,7 @@
     Pong,
     Quit(Option<String>),
     Global(String),
-    Watch(String),
+    Watch(u32),
     ToggleServerRegisteredOnly,
     SuperPower,
     Info(String),
@@ -210,6 +210,26 @@
     }
 }
 
+impl TeamInfo {
+    pub fn to_protocol(&self) -> Vec<String> {
+        let mut info = vec![
+            self.name.clone(),
+            self.grave.clone(),
+            self.fort.clone(),
+            self.voice_pack.clone(),
+            self.flag.clone(),
+            self.owner.clone(),
+            self.difficulty.to_string(),
+        ];
+        let hogs = self
+            .hedgehogs
+            .iter()
+            .flat_map(|h| once(h.name.clone()).chain(once(h.hat.clone())));
+        info.extend(hogs);
+        info
+    }
+}
+
 macro_rules! const_braces {
     ($e: expr) => {
         "{}\n"
--- a/rust/hedgewars-server/src/protocol/parser.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/protocol/parser.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -272,7 +272,7 @@
             |i| cmdc_single_arg(i, "SAVEROOM", a_line, SaveRoom),
             |i| cmdc_single_arg(i, "LOADROOM", a_line, LoadRoom),
             |i| cmdc_single_arg(i, "GLOBAL", a_line, Global),
-            |i| cmdc_single_arg(i, "WATCH", a_line, Watch),
+            |i| cmdc_single_arg(i, "WATCH", u32_line, Watch),
             |i| cmdc_single_arg(i, "GREETING", a_line, Greeting),
             |i| cmdc_single_arg(i, "VOTE", yes_no_line, Vote),
             |i| cmdc_single_arg(i, "FORCE", yes_no_line, ForceVote),
@@ -455,6 +455,7 @@
                     Ok((
                         i,
                         AddTeam(Box::new(TeamInfo {
+                            owner: String::new(),
                             name,
                             color,
                             grave,
--- a/rust/hedgewars-server/src/server/coretypes.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/coretypes.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -1,3 +1,5 @@
+use serde_derive::{Deserialize, Serialize};
+
 pub type ClientId = usize;
 pub type RoomId = usize;
 
@@ -28,6 +30,7 @@
 
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub struct TeamInfo {
+    pub owner: String,
     pub name: String,
     pub color: u8,
     pub grave: String,
@@ -45,6 +48,117 @@
     pub hat: String,
 }
 
+#[derive(Clone, Serialize, Deserialize)]
+pub struct Ammo {
+    pub name: String,
+    pub settings: Option<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct Scheme {
+    pub name: String,
+    pub settings: Vec<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct RoomConfig {
+    pub feature_size: u32,
+    pub map_type: String,
+    pub map_generator: u32,
+    pub maze_size: u32,
+    pub seed: String,
+    pub template: u32,
+
+    pub ammo: Ammo,
+    pub scheme: Scheme,
+    pub script: String,
+    pub theme: String,
+    pub drawn_map: Option<String>,
+}
+
+impl RoomConfig {
+    pub fn new() -> RoomConfig {
+        RoomConfig {
+            feature_size: 12,
+            map_type: "+rnd+".to_string(),
+            map_generator: 0,
+            maze_size: 0,
+            seed: "seed".to_string(),
+            template: 0,
+
+            ammo: Ammo {
+                name: "Default".to_string(),
+                settings: None,
+            },
+            scheme: Scheme {
+                name: "Default".to_string(),
+                settings: Vec::new(),
+            },
+            script: "Normal".to_string(),
+            theme: "\u{1f994}".to_string(),
+            drawn_map: None,
+        }
+    }
+
+    pub fn set_config(&mut self, cfg: GameCfg) {
+        match cfg {
+            GameCfg::FeatureSize(s) => self.feature_size = s,
+            GameCfg::MapType(t) => self.map_type = t,
+            GameCfg::MapGenerator(g) => self.map_generator = g,
+            GameCfg::MazeSize(s) => self.maze_size = s,
+            GameCfg::Seed(s) => self.seed = s,
+            GameCfg::Template(t) => self.template = t,
+
+            GameCfg::Ammo(n, s) => {
+                self.ammo = Ammo {
+                    name: n,
+                    settings: s,
+                }
+            }
+            GameCfg::Scheme(n, s) => {
+                self.scheme = Scheme {
+                    name: n,
+                    settings: s,
+                }
+            }
+            GameCfg::Script(s) => self.script = s,
+            GameCfg::Theme(t) => self.theme = t,
+            GameCfg::DrawnMap(m) => self.drawn_map = Some(m),
+        };
+    }
+
+    pub fn to_map_config(&self) -> Vec<String> {
+        vec![
+            self.feature_size.to_string(),
+            self.map_type.to_string(),
+            self.map_generator.to_string(),
+            self.maze_size.to_string(),
+            self.seed.to_string(),
+            self.template.to_string(),
+        ]
+    }
+
+    pub fn to_game_config(&self) -> Vec<GameCfg> {
+        use crate::server::coretypes::GameCfg::*;
+        let mut v = vec![
+            Ammo(self.ammo.name.to_string(), self.ammo.settings.clone()),
+            Scheme(self.scheme.name.to_string(), self.scheme.settings.clone()),
+            Script(self.script.to_string()),
+            Theme(self.theme.to_string()),
+        ];
+        if let Some(ref m) = self.drawn_map {
+            v.push(DrawnMap(m.to_string()))
+        }
+        v
+    }
+}
+
+pub struct Replay {
+    pub config: RoomConfig,
+    pub teams: Vec<TeamInfo>,
+    pub message_log: Vec<String>,
+}
+
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum VoteType {
     Kick(String),
--- a/rust/hedgewars-server/src/server/database.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/database.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -16,6 +16,8 @@
             VALUES
             (:players, :rooms, UNIX_TIMESTAMP())";
 
+const GET_REPLAY_NAME_QUERY: &str = r"SELECT filename FROM achievements WHERE id = :id";
+
 struct ServerStatistics {
     rooms: u32,
     players: u32,
@@ -95,8 +97,19 @@
         Ok(())
     }
 
-    pub fn get_replay_name(&mut self, replay_id: u32) -> Result<String, ()> {
-        Err(())
+    pub fn get_replay_name(&mut self, replay_id: u32) -> Result<Option<String>, Error> {
+        if let Some(pool) = &self.pool {
+            if let Some(row) =
+                pool.first_exec(GET_REPLAY_NAME_QUERY, params! { "id" => replay_id })?
+            {
+                let (filename) = from_row_opt::<(String)>(row)?;
+                Ok(Some(filename))
+            } else {
+                Ok(None)
+            }
+        } else {
+            Err(DriverError::SetupError.into())
+        }
     }
 }
 
--- a/rust/hedgewars-server/src/server/handlers.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/handlers.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -4,7 +4,7 @@
 use super::{
     actions::{Destination, DestinationRoom},
     core::HWServer,
-    coretypes::{ClientId, RoomId},
+    coretypes::{ClientId, Replay, RoomId},
     room::RoomSave,
 };
 use crate::{
@@ -24,6 +24,8 @@
 
 use self::loggingin::LoginResult;
 use crate::protocol::messages::global_chat;
+use crate::protocol::messages::HWProtocolMessage::EngineMessage;
+use crate::server::coretypes::{GameCfg, TeamInfo};
 use std::fmt::{Formatter, LowerHex};
 
 #[derive(PartialEq)]
@@ -59,6 +61,9 @@
         client_salt: String,
         server_salt: String,
     },
+    GetReplay {
+        id: u32,
+    },
     SaveRoom {
         room_id: RoomId,
         filename: String,
@@ -72,6 +77,7 @@
 
 pub enum IoResult {
     Account(Option<AccountInfo>),
+    Replay(Option<Replay>),
     SaveRoom(RoomId, bool),
     LoadRoom(RoomId, Option<String>),
 }
@@ -216,7 +222,27 @@
                     HWProtocolMessage::Quit(None) => {
                         common::remove_client(server, response, "User quit".to_string());
                     }
-                    HWProtocolMessage::Global(msg) => response.add(global_chat(msg).send_all()),
+                    HWProtocolMessage::Global(msg) => {
+                        if !server.clients[client_id].is_admin() {
+                            response.add(Warning("Access denied.".to_string()).send_self());
+                        } else {
+                            response.add(global_chat(msg).send_all())
+                        }
+                    }
+                    HWProtocolMessage::Watch(id) => {
+                        #[cfg(feature = "official-server")]
+                        {
+                            response.request_io(IoTask::GetReplay { id })
+                        }
+
+                        #[cfg(not(feature = "official-server"))]
+                        {
+                            response.add(
+                                Warning("This server does not support replays!".to_string())
+                                    .send_self(),
+                            );
+                        }
+                    }
                     _ => match server.clients[client_id].room_id {
                         None => lobby::handle(server, client_id, response, message),
                         Some(room_id) => {
@@ -264,6 +290,17 @@
             response.add(Error("Authentication failed.".to_string()).send_self());
             response.remove_client(client_id);
         }
+        IoResult::Replay(Some(replay)) => {
+            response.add(RoomJoined(vec![server.clients[client_id].nick.clone()]).send_self());
+            common::get_room_config_impl(&replay.config, client_id, response);
+            common::get_teams(replay.teams.iter(), client_id, response);
+            response.add(RunGame.send_self());
+            response.add(ForwardEngineMessage(replay.message_log).send_self());
+            response.add(Kicked.send_self());
+        }
+        IoResult::Replay(None) => {
+            response.add(Warning("Could't load the replay".to_string()).send_self())
+        }
         IoResult::SaveRoom(_, true) => {
             response.add(server_chat("Room configs saved successfully.".to_string()).send_self());
         }
--- a/rust/hedgewars-server/src/server/handlers/common.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/handlers/common.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -9,7 +9,7 @@
     server::{
         client::HWClient,
         core::HWServer,
-        coretypes::{ClientId, GameCfg, RoomId, Vote, VoteType},
+        coretypes::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType},
         room::HWRoom,
     },
     utils::to_engine_msg,
@@ -17,6 +17,7 @@
 
 use super::Response;
 
+use crate::server::coretypes::RoomConfig;
 use rand::{self, seq::SliceRandom, thread_rng, Rng};
 use std::{iter::once, mem::replace};
 
@@ -247,10 +248,25 @@
     response.add(update_msg.send_all().with_protocol(room.protocol_number));
 }
 
+pub fn get_room_config_impl(config: &RoomConfig, to_client: ClientId, response: &mut Response) {
+    response.add(ConfigEntry("FULLMAPCONFIG".to_string(), config.to_map_config()).send(to_client));
+    for cfg in config.to_game_config() {
+        response.add(cfg.to_server_msg().send(to_client));
+    }
+}
+
 pub fn get_room_config(room: &HWRoom, to_client: ClientId, response: &mut Response) {
-    response.add(ConfigEntry("FULLMAPCONFIG".to_string(), room.map_config()).send(to_client));
-    for cfg in room.game_config() {
-        response.add(cfg.to_server_msg().send(to_client));
+    get_room_config_impl(room.active_config(), to_client, response);
+}
+
+pub fn get_teams<'a, I>(teams: I, to_client: ClientId, response: &mut Response)
+where
+    I: Iterator<Item = &'a TeamInfo>,
+{
+    for team in teams {
+        response.add(TeamAdd(team.to_protocol()).send(to_client));
+        response.add(TeamColor(team.name.clone(), team.color).send(to_client));
+        response.add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send(to_client));
     }
 }
 
@@ -266,11 +282,7 @@
         None => &room.teams,
     };
 
-    for (owner_id, team) in current_teams.iter() {
-        response.add(TeamAdd(HWRoom::team_info(&server.clients[*owner_id], &team)).send(to_client));
-        response.add(TeamColor(team.name.clone(), team.color).send(to_client));
-        response.add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send(to_client));
-    }
+    get_teams(current_teams.iter().map(|(_, t)| t), to_client, response);
 }
 
 pub fn get_room_flags(
--- a/rust/hedgewars-server/src/server/handlers/inroom.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/handlers/inroom.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -191,7 +191,7 @@
                 super::common::start_game(server, room_id, response);
             }
         }
-        AddTeam(info) => {
+        AddTeam(mut info) => {
             if room.teams.len() >= room.team_limit as usize {
                 response.add(Warning("Too many teams!".to_string()).send_self());
             } else if room.addable_hedgehogs() == 0 {
@@ -211,12 +211,13 @@
                         .send_self(),
                 );
             } 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);
                 response.add(TeamAccepted(team.name.clone()).send_self());
                 response.add(
-                    TeamAdd(HWRoom::team_info(&client, team))
+                    TeamAdd(team.to_protocol())
                         .send_all()
                         .in_room(room_id)
                         .but_self(),
@@ -337,6 +338,7 @@
             );
             room.save_config(name, location);
         }
+        #[cfg(feature = "official-server")]
         SaveRoom(filename) => {
             if client.is_admin() {
                 match room.get_saves() {
@@ -355,6 +357,7 @@
                 }
             }
         }
+        #[cfg(feature = "official-server")]
         LoadRoom(filename) => {
             if client.is_admin() {
                 response.request_io(super::IoTask::LoadRoom { room_id, filename });
--- a/rust/hedgewars-server/src/server/handlers/lobby.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/handlers/lobby.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -64,12 +64,7 @@
         JoinRoom(name, _password) => {
             let room = server.rooms.iter().find(|(_, r)| r.name == name);
             let room_id = room.map(|(_, r)| r.id);
-            let nicks = server
-                .clients
-                .iter()
-                .filter(|(_, c)| c.room_id == room_id)
-                .map(|(_, c)| c.nick.clone())
-                .collect();
+
             let client = &mut server.clients[client_id];
 
             if let Some((_, room)) = room {
@@ -93,6 +88,7 @@
 
                     response.add(RoomJoined(vec![nick.clone()]).send_all().in_room(room_id));
                     response.add(ClientFlags(add_flags(&[Flags::InRoom]), vec![nick]).send_all());
+                    let nicks = server.collect_nicks(|(_, c)| c.room_id == Some(room_id));
                     response.add(RoomJoined(nicks).send_self());
 
                     let room = &server.rooms[room_id];
--- a/rust/hedgewars-server/src/server/io.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/io.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -46,13 +46,44 @@
                             &server_salt,
                         ) {
                             Ok(account) => IoResult::Account(account),
-                            Err(..) => {
-                                warn!("Unable to get account data: {}", 0);
+                            Err(e) => {
+                                warn!("Unable to get account data: {}", e);
                                 IoResult::Account(None)
                             }
                         }
                     }
 
+                    IoTask::GetReplay { id } => {
+                        let result = match db.get_replay_name(id) {
+                            Ok(Some(filename)) => {
+                                let filename = format!(
+                                    "checked/{}",
+                                    if filename.starts_with("replays/") {
+                                        &filename[8..]
+                                    } else {
+                                        &filename
+                                    }
+                                );
+                                match load_file(&filename) {
+                                    Ok(contents) => Some(unimplemented!()),
+                                    Err(e) => {
+                                        warn!(
+                                            "Error while writing the room config file \"{}\": {}",
+                                            filename, e
+                                        );
+                                        None
+                                    }
+                                }
+                            }
+                            Ok(None) => None,
+                            Err(e) => {
+                                warn!("Unable to get replay name: {}", e);
+                                None
+                            }
+                        };
+                        IoResult::Replay(result)
+                    }
+
                     IoTask::SaveRoom {
                         room_id,
                         filename,
--- a/rust/hedgewars-server/src/server/room.rs	Wed Apr 10 19:30:08 2019 +0300
+++ b/rust/hedgewars-server/src/server/room.rs	Wed Apr 10 23:56:53 2019 +0300
@@ -1,6 +1,8 @@
 use crate::server::{
     client::HWClient,
-    coretypes::{ClientId, GameCfg, GameCfg::*, RoomId, TeamInfo, Voting, MAX_HEDGEHOGS_PER_TEAM},
+    coretypes::{
+        ClientId, GameCfg, GameCfg::*, RoomConfig, RoomId, TeamInfo, Voting, MAX_HEDGEHOGS_PER_TEAM,
+    },
 };
 use bitflags::*;
 use serde::{Deserialize, Serialize};
@@ -11,59 +13,6 @@
 const MAX_TEAMS_IN_ROOM: u8 = 8;
 const MAX_HEDGEHOGS_IN_ROOM: u8 = MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM;
 
-#[derive(Clone, Serialize, Deserialize)]
-struct Ammo {
-    name: String,
-    settings: Option<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Scheme {
-    name: String,
-    settings: Vec<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct RoomConfig {
-    feature_size: u32,
-    map_type: String,
-    map_generator: u32,
-    maze_size: u32,
-    seed: String,
-    template: u32,
-
-    ammo: Ammo,
-    scheme: Scheme,
-    script: String,
-    theme: String,
-    drawn_map: Option<String>,
-}
-
-impl RoomConfig {
-    fn new() -> RoomConfig {
-        RoomConfig {
-            feature_size: 12,
-            map_type: "+rnd+".to_string(),
-            map_generator: 0,
-            maze_size: 0,
-            seed: "seed".to_string(),
-            template: 0,
-
-            ammo: Ammo {
-                name: "Default".to_string(),
-                settings: None,
-            },
-            scheme: Scheme {
-                name: "Default".to_string(),
-                settings: Vec::new(),
-            },
-            script: "Normal".to_string(),
-            theme: "\u{1f994}".to_string(),
-            drawn_map: None,
-        }
-    }
-}
-
 fn client_teams_impl(
     teams: &[(ClientId, TeamInfo)],
     client_id: ClientId,
@@ -74,31 +23,6 @@
         .map(|(_, t)| t)
 }
 
-fn map_config_from(c: &RoomConfig) -> Vec<String> {
-    vec![
-        c.feature_size.to_string(),
-        c.map_type.to_string(),
-        c.map_generator.to_string(),
-        c.maze_size.to_string(),
-        c.seed.to_string(),
-        c.template.to_string(),
-    ]
-}
-
-fn game_config_from(c: &RoomConfig) -> Vec<GameCfg> {
-    use crate::server::coretypes::GameCfg::*;
-    let mut v = vec![
-        Ammo(c.ammo.name.to_string(), c.ammo.settings.clone()),
-        Scheme(c.scheme.name.to_string(), c.scheme.settings.clone()),
-        Script(c.script.to_string()),
-        Theme(c.theme.to_string()),
-    ];
-    if let Some(ref m) = c.drawn_map {
-        v.push(DrawnMap(m.to_string()))
-    }
-    v
-}
-
 pub struct GameInfo {
     pub teams_in_game: u8,
     pub teams_at_start: Vec<(ClientId, TeamInfo)>,
@@ -290,31 +214,7 @@
     }
 
     pub fn set_config(&mut self, cfg: GameCfg) {
-        let c = &mut self.config;
-        match cfg {
-            FeatureSize(s) => c.feature_size = s,
-            MapType(t) => c.map_type = t,
-            MapGenerator(g) => c.map_generator = g,
-            MazeSize(s) => c.maze_size = s,
-            Seed(s) => c.seed = s,
-            Template(t) => c.template = t,
-
-            Ammo(n, s) => {
-                c.ammo = Ammo {
-                    name: n,
-                    settings: s,
-                }
-            }
-            Scheme(n, s) => {
-                c.scheme = Scheme {
-                    name: n,
-                    settings: s,
-                }
-            }
-            Script(s) => c.script = s,
-            Theme(t) => c.theme = t,
-            DrawnMap(m) => c.drawn_map = Some(m),
-        };
+        self.config.set_config(cfg);
     }
 
     pub fn start_round(&mut self) {
@@ -383,17 +283,24 @@
         ]
     }
 
+    pub fn active_config(&self) -> &RoomConfig {
+        match self.game_info {
+            Some(ref info) => &info.config,
+            None => &self.config,
+        }
+    }
+
     pub fn map_config(&self) -> Vec<String> {
         match self.game_info {
-            Some(ref info) => map_config_from(&info.config),
-            None => map_config_from(&self.config),
+            Some(ref info) => info.config.to_map_config(),
+            None => self.config.to_map_config(),
         }
     }
 
     pub fn game_config(&self) -> Vec<GameCfg> {
         match self.game_info {
-            Some(ref info) => game_config_from(&info.config),
-            None => game_config_from(&self.config),
+            Some(ref info) => info.config.to_game_config(),
+            None => self.config.to_game_config(),
         }
     }
 
@@ -432,22 +339,4 @@
             },
         )
     }
-
-    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
-    }
 }