Make wider range of numbers representable with FPNum on the cost of smaller precision
use super::{common::rnd_reply, strings::*};
use crate::core::room::GameInfo;
use crate::core::server::{AddTeamError, SetTeamCountError};
use crate::handlers::actions::ToPendingMessage;
use crate::{
core::{
room::{HwRoom, RoomFlags, MAX_TEAMS_IN_ROOM},
server::{
ChangeMasterError, ChangeMasterResult, HwRoomControl, HwServer, LeaveRoomResult,
ModifyTeamError, StartGameError,
},
types::{ClientId, RoomId, Voting},
},
utils::{is_name_illegal, to_engine_msg},
};
use base64::{decode, encode};
use hedgewars_network_protocol::{
messages::{
add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*,
ProtocolFlags as Flags,
},
types,
types::{GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM},
};
use log::*;
use std::{cmp::min, iter::once, mem::swap};
#[derive(Clone)]
struct ByMsg<'a> {
messages: &'a [u8],
}
impl<'a> Iterator for ByMsg<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
if let Some(size) = self.messages.get(0) {
let (msg, next) = self.messages.split_at(*size as usize + 1);
self.messages = next;
Some(msg)
} else {
None
}
}
}
fn by_msg(source: &[u8]) -> ByMsg {
ByMsg { messages: source }
}
const VALID_MESSAGES: &[u8] =
b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A";
const NON_TIMED_MESSAGES: &[u8] = b"M#hb";
fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool {
match msg {
[size, typ, body @ ..] => {
VALID_MESSAGES.contains(typ)
&& match body {
[1..=MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => {
team_indices.contains(team)
}
_ => *typ != b'h',
}
}
_ => false,
}
}
fn is_msg_empty(msg: &[u8]) -> bool {
msg.get(1).filter(|t| **t == b'+').is_some()
}
fn is_msg_timed(msg: &[u8]) -> bool {
msg.get(1)
.filter(|t| !NON_TIMED_MESSAGES.contains(t))
.is_some()
}
fn voting_description(kind: &VoteType) -> String {
format!(
"New voting started: {}",
match kind {
VoteType::Kick(nick) => format!("kick {}", nick),
VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()),
VoteType::Pause => "pause".to_string(),
VoteType::NewSeed => "new seed".to_string(),
VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number),
}
)
}
fn room_message_flag(msg: &HwProtocolMessage) -> RoomFlags {
use hedgewars_network_protocol::messages::HwProtocolMessage::*;
match msg {
ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN,
ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD,
ToggleRegisteredOnly => RoomFlags::REGISTRATION_REQUIRED,
_ => RoomFlags::empty(),
}
}
pub fn handle(
mut room_control: HwRoomControl,
response: &mut super::Response,
message: HwProtocolMessage,
) {
let (client, room) = room_control.get();
let (client_id, room_id) = (client.id, room.id);
use hedgewars_network_protocol::messages::HwProtocolMessage::*;
match message {
Part(msg) => {
let msg = match msg {
Some(s) => format!("part: {}", s),
None => "part".to_string(),
};
let result = room_control.leave_room();
super::common::get_room_leave_result(
room_control.server(),
room_control.room(),
&msg,
result,
response,
);
}
Chat(msg) => {
response.add(
ChatMsg {
nick: client.nick.clone(),
msg,
}
.send_all()
.in_room(room_id),
);
}
TeamChat(msg) => {
if room.game_info.is_some() {
if let Some(clan_color) = room.find_team_color(client_id) {
let engine_msg =
to_engine_msg(format!("b{}]{}\x20\x20", client.nick, msg).bytes());
let team = room.clan_team_owners(clan_color).collect();
response.add(ForwardEngineMessage(vec![engine_msg]).send_many(team))
}
}
}
Fix => {
if let Err(_) = room_control.fix_room() {
response.warn(ACCESS_DENIED)
}
}
Unfix => {
if let Err(_) = room_control.unfix_room() {
response.warn(ACCESS_DENIED)
}
}
Greeting(text) => {
if let Err(_) = room_control.set_room_greeting(text) {
response.warn(ACCESS_DENIED)
}
}
MaxTeams(count) => {
use crate::core::server::SetTeamCountError;
match room_control.set_room_max_teams(count) {
Ok(()) => {}
Err(SetTeamCountError::NotMaster) => response.warn(NOT_MASTER),
Err(SetTeamCountError::InvalidNumber) => {
response.warn("/maxteams: specify number from 2 to 8")
}
};
}
RoomName(new_name) => {
use crate::core::server::ModifyRoomNameError;
match room_control.set_room_name(new_name) {
Ok(old_name) => {
let (client, room) = room_control.get();
super::common::get_room_update(Some(old_name), room, Some(client), response)
}
Err(ModifyRoomNameError::AccessDenied) => response.warn(ACCESS_DENIED),
Err(ModifyRoomNameError::InvalidName) => response.warn(ILLEGAL_ROOM_NAME),
Err(ModifyRoomNameError::DuplicateName) => response.warn(ROOM_EXISTS),
}
}
ToggleReady => {
let flags = if room_control.toggle_ready() {
add_flags(&[Flags::Ready])
} else {
remove_flags(&[Flags::Ready])
};
let (client, room) = room_control.get();
let msg = if client.protocol_number < 38 {
LegacyReady(client.is_ready(), vec![client.nick.clone()])
} else {
ClientFlags(flags, vec![client.nick.clone()])
};
response.add(msg.send_all().in_room(room_id));
if room.is_fixed() && room.ready_players_number == room.players_number {
let result = room_control.start_game();
super::common::get_start_game_data(
room_control.server(),
room_id,
result,
response,
);
}
}
AddTeam(info) => {
use crate::core::server::AddTeamError;
match room_control.add_team(info) {
Ok(team) => {
response.add(TeamAccepted(team.name.clone()).send_self());
response.add(
TeamAdd(team.to_protocol())
.send_all()
.in_room(room_id)
.but_self(),
);
response.add(
TeamColor(team.name.clone(), team.color)
.send_all()
.in_room(room_id),
);
response.add(
HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
.send_all()
.in_room(room_id),
);
let room = room_control.room();
let room_master = room.master_id.map(|id| room_control.server().client(id));
super::common::get_room_update(None, room, room_master, response);
}
Err(AddTeamError::TooManyTeams) => response.warn(TOO_MANY_TEAMS),
Err(AddTeamError::TooManyHedgehogs) => response.warn(TOO_MANY_HEDGEHOGS),
Err(AddTeamError::TeamAlreadyExists) => response.warn(TEAM_EXISTS),
Err(AddTeamError::Restricted) => response.warn(TEAM_ADD_RESTRICTED),
}
}
RemoveTeam(name) => {
use crate::core::server::RemoveTeamError;
match room_control.remove_team(&name) {
Ok(()) => {
let (client, room) = room_control.get();
let removed_teams = vec![name];
super::common::get_remove_teams_data(room_id, false, removed_teams, response);
}
Err(RemoveTeamError::NoTeam) => response.warn(NO_TEAM_TO_REMOVE),
Err(RemoveTeamError::TeamNotOwned) => response.warn(TEAM_NOT_OWNED),
}
}
SetHedgehogsNumber(team_name, number) => {
use crate::core::server::SetHedgehogsError;
match room_control.set_team_hedgehogs_number(&team_name, number) {
Ok(()) => {
response.add(
HedgehogsNumber(team_name.clone(), number)
.send_all()
.in_room(room_id)
.but_self(),
);
}
Err(SetHedgehogsError::NotMaster) => response.error(NOT_MASTER),
Err(SetHedgehogsError::NoTeam) => response.warn(NO_TEAM),
Err(SetHedgehogsError::InvalidNumber(previous_number)) => {
response.add(HedgehogsNumber(team_name.clone(), previous_number).send_self())
}
}
}
SetTeamColor(team_name, color) => match room_control.set_team_color(&team_name, color) {
Ok(()) => response.add(
TeamColor(team_name, color)
.send_all()
.in_room(room_id)
.but_self(),
),
Err(ModifyTeamError::NoTeam) => response.warn(NO_TEAM),
Err(ModifyTeamError::NotMaster) => response.error(NOT_MASTER),
},
Cfg(cfg) => {
use crate::core::server::SetConfigError;
let msg = cfg.to_server_msg();
match room_control.set_config(cfg) {
Ok(()) => {
response.add(msg.send_all().in_room(room_control.room().id).but_self());
}
Err(SetConfigError::NotMaster) => response.error(NOT_MASTER),
Err(SetConfigError::RoomFixed) => response.warn(ACCESS_DENIED),
}
}
Save(name, location) => {
response.add(
server_chat(format!("Room config saved as {}", name))
.send_all()
.in_room(room_id),
);
room_control.save_config(name, location);
}
#[cfg(feature = "official-server")]
SaveRoom(filename) => {
if client.is_admin() {
match room.get_saves() {
Ok(contents) => response.request_io(super::IoTask::SaveRoom {
room_id,
filename,
contents,
}),
Err(e) => {
warn!("Error while serializing the room configs: {}", e);
response.warn("Unable to serialize the room configs.")
}
}
}
}
#[cfg(feature = "official-server")]
LoadRoom(filename) => {
if client.is_admin() {
response.request_io(super::IoTask::LoadRoom { room_id, filename });
}
}
Delete(name) => {
if !room_control.delete_config(&name) {
response.add(Warning(format!("Save doesn't exist: {}", name)).send_self());
} else {
response.add(
server_chat(format!("Room config {} has been deleted", name))
.send_all()
.in_room(room_id),
);
}
}
CallVote(None) => {
//todo!("implement ghost points")
response.add(server_chat("Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs <number>".to_string())
.send_self());
}
CallVote(Some(kind)) => {
use crate::core::server::StartVoteError;
let room_id = room_control.room().id;
if super::common::check_vote(
room_control.server(),
room_control.room(),
&kind,
response,
) {
match room_control.start_vote(kind.clone()) {
Ok(()) => {
let msg = voting_description(&kind);
response.add(server_chat(msg).send_all().in_room(room_id));
let vote_result = room_control.vote(types::Vote {
is_pro: true,
is_forced: false,
});
super::common::handle_vote(room_control, vote_result, response);
}
Err(StartVoteError::VotingInProgress) => {
response.add(
server_chat("There is already voting in progress".to_string())
.send_self(),
);
}
}
}
}
Vote(vote) => {
let vote_result = room_control.vote(types::Vote {
is_pro: vote,
is_forced: false,
});
super::common::handle_vote(room_control, vote_result, response);
}
ForceVote(vote) => {
let is_forced = client.is_admin();
let vote_result = room_control.vote(types::Vote {
is_pro: vote,
is_forced,
});
super::common::handle_vote(room_control, vote_result, response);
}
ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => {
if room_control.toggle_flag(room_message_flag(&message)) {
let (client, room) = room_control.get();
super::common::get_room_update(None, room, Some(&client), response);
}
}
StartGame => {
let result = room_control.start_game();
super::common::get_start_game_data(room_control.server(), room_id, result, response);
}
EngineMessage(em) => {
if client.teams_in_game > 0 {
let decoding = decode(&em[..]).unwrap();
let messages = by_msg(&decoding);
let valid = messages.filter(|m| is_msg_valid(m, &client.team_indices));
let non_empty = valid.clone().filter(|m| !is_msg_empty(m));
let sync_msg = valid.clone().filter(|m| is_msg_timed(m)).last().map(|m| {
if is_msg_empty(m) {
Some(encode(m))
} else {
None
}
});
let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::<Vec<_>>());
if !em_response.is_empty() {
response.add(
ForwardEngineMessage(vec![em_response])
.send_all()
.in_room(room.id)
.but_self(),
);
}
let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::<Vec<_>>());
room_control.log_engine_msg(em_log, sync_msg);
}
}
RoundFinished => {
if let Some(team_names) = room_control.leave_game() {
let (client, room) = room_control.get();
response.add(
ClientFlags(remove_flags(&[Flags::InGame]), vec![client.nick.clone()])
.send_all()
.in_room(room.id),
);
for team_name in team_names {
let msg = once(b'F').chain(team_name.bytes());
response.add(
ForwardEngineMessage(vec![to_engine_msg(msg)])
.send_all()
.in_room(room_id)
.but_self(),
);
}
if let Some(0) = room.teams_in_game() {
if let Some(result) = room_control.end_game() {
super::common::get_end_game_result(
room_control.server(),
room_id,
result,
response,
);
}
}
}
}
Rnd(v) => {
let result = rnd_reply(&v);
let mut echo = vec!["/rnd".to_string()];
echo.extend(v);
let chat_msg = ChatMsg {
nick: client.nick.clone(),
msg: echo.join(" "),
};
response.add(chat_msg.send_all().in_room(room_id));
response.add(result.send_all().in_room(room_id));
}
Delegate(nick) => match room_control.change_master(nick) {
Ok(ChangeMasterResult {
old_master_id,
new_master_id,
}) => {
if let Some(master_id) = old_master_id {
response.add(
ClientFlags(
remove_flags(&[Flags::RoomMaster]),
vec![room_control.server().client(master_id).nick.clone()],
)
.send_all()
.in_room(room_id),
);
}
response.add(
ClientFlags(
add_flags(&[Flags::RoomMaster]),
vec![room_control.server().client(new_master_id).nick.clone()],
)
.send_all()
.in_room(room_id),
);
}
Err(ChangeMasterError::NoAccess) => {
response.warn("You're not the room master or a server admin!")
}
Err(ChangeMasterError::AlreadyMaster) => {
response.warn("You're already the room master.")
}
Err(ChangeMasterError::NoClient) => response.warn("Player is not online."),
Err(ChangeMasterError::ClientNotInRoom) => {
response.warn("The player is not in your room.")
}
},
message => warn!("Unimplemented: {:?}", message),
}
}