# HG changeset patch # User alfadur # Date 1554929813 -10800 # Node ID a1077e8d26f4571d6e49f09cc64623144cf529a2 # Parent 8390d5e4e39c658e8359fa3673b3b1236e584db6 implement watch message apart from replay deserializing diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/protocol/messages.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), Global(String), - Watch(String), + Watch(u32), ToggleServerRegisteredOnly, SuperPower, Info(String), @@ -210,6 +210,26 @@ } } +impl TeamInfo { + pub fn to_protocol(&self) -> Vec { + 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" diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/protocol/parser.rs --- 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, diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/coretypes.rs --- 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, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Scheme { + pub name: String, + pub settings: Vec, +} + +#[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, +} + +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 { + 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 { + 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, + pub message_log: Vec, +} + #[derive(PartialEq, Eq, Clone, Debug)] pub enum VoteType { Kick(String), diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/database.rs --- 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 { - Err(()) + pub fn get_replay_name(&mut self, replay_id: u32) -> Result, 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()) + } } } diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/handlers.rs --- 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), + Replay(Option), SaveRoom(RoomId, bool), LoadRoom(RoomId, Option), } @@ -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()); } diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/handlers/common.rs --- 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, +{ + 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( diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/handlers/inroom.rs --- 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 }); diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/handlers/lobby.rs --- 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]; diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/io.rs --- 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, diff -r 8390d5e4e39c -r a1077e8d26f4 rust/hedgewars-server/src/server/room.rs --- 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, -} - -#[derive(Clone, Serialize, Deserialize)] -struct Scheme { - name: String, - settings: Vec, -} - -#[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, -} - -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 { - 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 { - 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 { 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 { 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 { - 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 - } }