--- 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
- }
}