1 use mio; |
|
2 |
|
3 use crate::{ |
|
4 server::{ |
|
5 coretypes::{ |
|
6 ClientId, RoomId, Voting, VoteType, GameCfg, |
|
7 MAX_HEDGEHOGS_PER_TEAM |
|
8 }, |
|
9 core::HWServer, |
|
10 room::{HWRoom, RoomFlags}, |
|
11 actions::{Action, Action::*} |
|
12 }, |
|
13 protocol::messages::{ |
|
14 HWProtocolMessage, |
|
15 HWServerMessage::*, |
|
16 server_chat |
|
17 }, |
|
18 utils::is_name_illegal |
|
19 }; |
|
20 use std::{ |
|
21 mem::swap |
|
22 }; |
|
23 use base64::{encode, decode}; |
|
24 use super::common::rnd_reply; |
|
25 use log::*; |
|
26 |
|
27 #[derive(Clone)] |
|
28 struct ByMsg<'a> { |
|
29 messages: &'a[u8] |
|
30 } |
|
31 |
|
32 impl <'a> Iterator for ByMsg<'a> { |
|
33 type Item = &'a[u8]; |
|
34 |
|
35 fn next(&mut self) -> Option<<Self as Iterator>::Item> { |
|
36 if let Some(size) = self.messages.get(0) { |
|
37 let (msg, next) = self.messages.split_at(*size as usize + 1); |
|
38 self.messages = next; |
|
39 Some(msg) |
|
40 } else { |
|
41 None |
|
42 } |
|
43 } |
|
44 } |
|
45 |
|
46 fn by_msg(source: &[u8]) -> ByMsg { |
|
47 ByMsg {messages: source} |
|
48 } |
|
49 |
|
50 const VALID_MESSAGES: &[u8] = |
|
51 b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A"; |
|
52 const NON_TIMED_MESSAGES: &[u8] = b"M#hb"; |
|
53 |
|
54 #[cfg(canhazslicepatterns)] |
|
55 fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool { |
|
56 match msg { |
|
57 [size, typ, body..] => VALID_MESSAGES.contains(typ) |
|
58 && match body { |
|
59 [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => |
|
60 team_indices.contains(team), |
|
61 _ => *typ != b'h' |
|
62 }, |
|
63 _ => false |
|
64 } |
|
65 } |
|
66 |
|
67 fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool { |
|
68 if let Some(typ) = msg.get(1) { |
|
69 VALID_MESSAGES.contains(typ) |
|
70 } else { |
|
71 false |
|
72 } |
|
73 } |
|
74 |
|
75 fn is_msg_empty(msg: &[u8]) -> bool { |
|
76 msg.get(1).filter(|t| **t == b'+').is_some() |
|
77 } |
|
78 |
|
79 fn is_msg_timed(msg: &[u8]) -> bool { |
|
80 msg.get(1).filter(|t| !NON_TIMED_MESSAGES.contains(t)).is_some() |
|
81 } |
|
82 |
|
83 fn voting_description(kind: &VoteType) -> String { |
|
84 format!("New voting started: {}", match kind { |
|
85 VoteType::Kick(nick) => format!("kick {}", nick), |
|
86 VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()), |
|
87 VoteType::Pause => "pause".to_string(), |
|
88 VoteType::NewSeed => "new seed".to_string(), |
|
89 VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number) |
|
90 }) |
|
91 } |
|
92 |
|
93 fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags { |
|
94 use crate::protocol::messages::HWProtocolMessage::*; |
|
95 match msg { |
|
96 ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN, |
|
97 ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD, |
|
98 ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, |
|
99 _ => RoomFlags::empty() |
|
100 } |
|
101 } |
|
102 |
|
103 pub fn handle(server: &mut HWServer, client_id: ClientId, room_id: RoomId, message: HWProtocolMessage) { |
|
104 use crate::protocol::messages::HWProtocolMessage::*; |
|
105 match message { |
|
106 Part(None) => server.react(client_id, vec![ |
|
107 MoveToLobby("part".to_string())]), |
|
108 Part(Some(msg)) => server.react(client_id, vec![ |
|
109 MoveToLobby(format!("part: {}", msg))]), |
|
110 Chat(msg) => { |
|
111 let actions = { |
|
112 let c = &mut server.clients[client_id]; |
|
113 let chat_msg = ChatMsg {nick: c.nick.clone(), msg}; |
|
114 vec![chat_msg.send_all().in_room(room_id).but_self().action()] |
|
115 }; |
|
116 server.react(client_id, actions); |
|
117 }, |
|
118 Fix => { |
|
119 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
120 if c.is_admin() { r.set_is_fixed(true) } |
|
121 } |
|
122 } |
|
123 Unfix => { |
|
124 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
125 if c.is_admin() { r.set_is_fixed(false) } |
|
126 } |
|
127 } |
|
128 Greeting(text) => { |
|
129 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
130 if c.is_admin() || c.is_master() && !r.is_fixed() { |
|
131 r.greeting = text |
|
132 } |
|
133 } |
|
134 } |
|
135 RoomName(new_name) => { |
|
136 let actions = |
|
137 if is_name_illegal(&new_name) { |
|
138 vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] |
|
139 } else if server.rooms[room_id].is_fixed() { |
|
140 vec![Warn("Access denied.".to_string())] |
|
141 } else if server.has_room(&new_name) { |
|
142 vec![Warn("A room with the same name already exists.".to_string())] |
|
143 } else { |
|
144 let mut old_name = new_name.clone(); |
|
145 swap(&mut server.rooms[room_id].name, &mut old_name); |
|
146 vec![SendRoomUpdate(Some(old_name))] |
|
147 }; |
|
148 server.react(client_id, actions); |
|
149 }, |
|
150 ToggleReady => { |
|
151 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
152 let flags = if c.is_ready() { |
|
153 r.ready_players_number -= 1; |
|
154 "-r" |
|
155 } else { |
|
156 r.ready_players_number += 1; |
|
157 "+r" |
|
158 }; |
|
159 |
|
160 let msg = if c.protocol_number < 38 { |
|
161 LegacyReady(c.is_ready(), vec![c.nick.clone()]) |
|
162 } else { |
|
163 ClientFlags(flags.to_string(), vec![c.nick.clone()]) |
|
164 }; |
|
165 |
|
166 let mut v = vec![msg.send_all().in_room(r.id).action()]; |
|
167 |
|
168 if r.is_fixed() && r.ready_players_number == r.players_number { |
|
169 v.push(StartRoomGame(r.id)) |
|
170 } |
|
171 |
|
172 c.set_is_ready(!c.is_ready()); |
|
173 server.react(client_id, v); |
|
174 } |
|
175 } |
|
176 AddTeam(info) => { |
|
177 let mut actions = Vec::new(); |
|
178 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
179 if r.teams.len() >= r.team_limit as usize { |
|
180 actions.push(Warn("Too many teams!".to_string())) |
|
181 } else if r.addable_hedgehogs() == 0 { |
|
182 actions.push(Warn("Too many hedgehogs!".to_string())) |
|
183 } else if r.find_team(|t| t.name == info.name) != None { |
|
184 actions.push(Warn("There's already a team with same name in the list.".to_string())) |
|
185 } else if r.game_info.is_some() { |
|
186 actions.push(Warn("Joining not possible: Round is in progress.".to_string())) |
|
187 } else if r.is_team_add_restricted() { |
|
188 actions.push(Warn("This room currently does not allow adding new teams.".to_string())); |
|
189 } else { |
|
190 let team = r.add_team(c.id, *info, c.protocol_number < 42); |
|
191 c.teams_in_game += 1; |
|
192 c.clan = Some(team.color); |
|
193 actions.push(TeamAccepted(team.name.clone()) |
|
194 .send_self().action()); |
|
195 actions.push(TeamAdd(HWRoom::team_info(&c, team)) |
|
196 .send_all().in_room(room_id).but_self().action()); |
|
197 actions.push(TeamColor(team.name.clone(), team.color) |
|
198 .send_all().in_room(room_id).action()); |
|
199 actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number) |
|
200 .send_all().in_room(room_id).action()); |
|
201 actions.push(SendRoomUpdate(None)); |
|
202 } |
|
203 } |
|
204 server.react(client_id, actions); |
|
205 }, |
|
206 RemoveTeam(name) => { |
|
207 let mut actions = Vec::new(); |
|
208 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
209 match r.find_team_owner(&name) { |
|
210 None => |
|
211 actions.push(Warn("Error: The team you tried to remove does not exist.".to_string())), |
|
212 Some((id, _)) if id != client_id => |
|
213 actions.push(Warn("You can't remove a team you don't own.".to_string())), |
|
214 Some((_, name)) => { |
|
215 c.teams_in_game -= 1; |
|
216 c.clan = r.find_team_color(c.id); |
|
217 actions.push(Action::RemoveTeam(name.to_string())); |
|
218 } |
|
219 } |
|
220 }; |
|
221 server.react(client_id, actions); |
|
222 }, |
|
223 SetHedgehogsNumber(team_name, number) => { |
|
224 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
225 let addable_hedgehogs = r.addable_hedgehogs(); |
|
226 let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { |
|
227 if !c.is_master() { |
|
228 vec![ProtocolError("You're not the room master!".to_string())] |
|
229 } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM |
|
230 || number > addable_hedgehogs + team.hedgehogs_number { |
|
231 vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number) |
|
232 .send_self().action()] |
|
233 } else { |
|
234 team.hedgehogs_number = number; |
|
235 vec![HedgehogsNumber(team.name.clone(), number) |
|
236 .send_all().in_room(room_id).but_self().action()] |
|
237 } |
|
238 } else { |
|
239 vec![(Warn("No such team.".to_string()))] |
|
240 }; |
|
241 server.react(client_id, actions); |
|
242 } |
|
243 }, |
|
244 SetTeamColor(team_name, color) => { |
|
245 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
246 let mut owner_id = None; |
|
247 let actions = if let Some((owner, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { |
|
248 if !c.is_master() { |
|
249 vec![ProtocolError("You're not the room master!".to_string())] |
|
250 } else if false { |
|
251 Vec::new() |
|
252 } else { |
|
253 owner_id = Some(owner); |
|
254 team.color = color; |
|
255 vec![TeamColor(team.name.clone(), color) |
|
256 .send_all().in_room(room_id).but_self().action()] |
|
257 } |
|
258 } else { |
|
259 vec![(Warn("No such team.".to_string()))] |
|
260 }; |
|
261 |
|
262 if let Some(id) = owner_id { |
|
263 server.clients[id].clan = Some(color); |
|
264 } |
|
265 |
|
266 server.react(client_id, actions); |
|
267 }; |
|
268 }, |
|
269 Cfg(cfg) => { |
|
270 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
271 let actions = if r.is_fixed() { |
|
272 vec![Warn("Access denied.".to_string())] |
|
273 } else if !c.is_master() { |
|
274 vec![ProtocolError("You're not the room master!".to_string())] |
|
275 } else { |
|
276 let cfg = match cfg { |
|
277 GameCfg::Scheme(name, mut values) => { |
|
278 if c.protocol_number == 49 && values.len() >= 2 { |
|
279 let mut s = "X".repeat(50); |
|
280 s.push_str(&values.pop().unwrap()); |
|
281 values.push(s); |
|
282 } |
|
283 GameCfg::Scheme(name, values) |
|
284 } |
|
285 cfg => cfg |
|
286 }; |
|
287 |
|
288 let v = vec![cfg.to_server_msg() |
|
289 .send_all().in_room(r.id).but_self().action()]; |
|
290 r.set_config(cfg); |
|
291 v |
|
292 }; |
|
293 server.react(client_id, actions); |
|
294 } |
|
295 } |
|
296 Save(name, location) => { |
|
297 let actions = vec![server_chat(format!("Room config saved as {}", name)) |
|
298 .send_all().in_room(room_id).action()]; |
|
299 server.rooms[room_id].save_config(name, location); |
|
300 server.react(client_id, actions); |
|
301 } |
|
302 SaveRoom(filename) => { |
|
303 if server.clients[client_id].is_admin() { |
|
304 let actions = match server.rooms[room_id].get_saves() { |
|
305 Ok(text) => match server.io.write_file(&filename, &text) { |
|
306 Ok(_) => vec![server_chat("Room configs saved successfully.".to_string()) |
|
307 .send_self().action()], |
|
308 Err(e) => { |
|
309 warn!("Error while writing the config file \"{}\": {}", filename, e); |
|
310 vec![Warn("Unable to save the room configs.".to_string())] |
|
311 } |
|
312 } |
|
313 Err(e) => { |
|
314 warn!("Error while serializing the room configs: {}", e); |
|
315 vec![Warn("Unable to serialize the room configs.".to_string())] |
|
316 } |
|
317 }; |
|
318 server.react(client_id, actions); |
|
319 } |
|
320 } |
|
321 LoadRoom(filename) => { |
|
322 if server.clients[client_id].is_admin() { |
|
323 let actions = match server.io.read_file(&filename) { |
|
324 Ok(text) => match server.rooms[room_id].set_saves(&text) { |
|
325 Ok(_) => vec![server_chat("Room configs loaded successfully.".to_string()) |
|
326 .send_self().action()], |
|
327 Err(e) => { |
|
328 warn!("Error while deserializing the room configs: {}", e); |
|
329 vec![Warn("Unable to deserialize the room configs.".to_string())] |
|
330 } |
|
331 } |
|
332 Err(e) => { |
|
333 warn!("Error while reading the config file \"{}\": {}", filename, e); |
|
334 vec![Warn("Unable to load the room configs.".to_string())] |
|
335 } |
|
336 }; |
|
337 server.react(client_id, actions); |
|
338 } |
|
339 } |
|
340 Delete(name) => { |
|
341 let actions = if !server.rooms[room_id].delete_config(&name) { |
|
342 vec![Warn(format!("Save doesn't exist: {}", name))] |
|
343 } else { |
|
344 vec![server_chat(format!("Room config {} has been deleted", name)) |
|
345 .send_all().in_room(room_id).action()] |
|
346 }; |
|
347 server.react(client_id, actions); |
|
348 } |
|
349 CallVote(None) => { |
|
350 server.react(client_id, vec![ |
|
351 server_chat("Available callvote commands: kick <nickname>, map <name>, pause, newseed, hedgehogs <number>".to_string()) |
|
352 .send_self().action()]) |
|
353 } |
|
354 CallVote(Some(kind)) => { |
|
355 let is_in_game = server.rooms[room_id].game_info.is_some(); |
|
356 let error = match &kind { |
|
357 VoteType::Kick(nick) => { |
|
358 if server.find_client(&nick).filter(|c| c.room_id == Some(room_id)).is_some() { |
|
359 None |
|
360 } else { |
|
361 Some("/callvote kick: No such user!".to_string()) |
|
362 } |
|
363 }, |
|
364 VoteType::Map(None) => { |
|
365 let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect(); |
|
366 if names.is_empty() { |
|
367 Some("/callvote map: No maps saved in this room!".to_string()) |
|
368 } else { |
|
369 Some(format!("Available maps: {}", names.join(", "))) |
|
370 } |
|
371 }, |
|
372 VoteType::Map(Some(name)) => { |
|
373 if server.rooms[room_id].saves.get(&name[..]).is_some() { |
|
374 None |
|
375 } else { |
|
376 Some("/callvote map: No such map!".to_string()) |
|
377 } |
|
378 }, |
|
379 VoteType::Pause => { |
|
380 if is_in_game { |
|
381 None |
|
382 } else { |
|
383 Some("/callvote pause: No game in progress!".to_string()) |
|
384 } |
|
385 }, |
|
386 VoteType::NewSeed => { |
|
387 None |
|
388 }, |
|
389 VoteType::HedgehogsPerTeam(number) => { |
|
390 match number { |
|
391 1...MAX_HEDGEHOGS_PER_TEAM => None, |
|
392 _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()) |
|
393 } |
|
394 }, |
|
395 }; |
|
396 match error { |
|
397 None => { |
|
398 let msg = voting_description(&kind); |
|
399 let voting = Voting::new(kind, server.room_clients(client_id)); |
|
400 server.rooms[room_id].voting = Some(voting); |
|
401 server.react(client_id, vec![ |
|
402 server_chat(msg).send_all().in_room(room_id).action(), |
|
403 AddVote{ vote: true, is_forced: false}]); |
|
404 } |
|
405 Some(msg) => { |
|
406 server.react(client_id, vec![ |
|
407 server_chat(msg).send_self().action()]) |
|
408 } |
|
409 } |
|
410 } |
|
411 Vote(vote) => { |
|
412 server.react(client_id, vec![AddVote{ vote, is_forced: false }]); |
|
413 } |
|
414 ForceVote(vote) => { |
|
415 let is_forced = server.clients[client_id].is_admin(); |
|
416 server.react(client_id, vec![AddVote{ vote, is_forced }]); |
|
417 } |
|
418 ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => { |
|
419 if server.clients[client_id].is_master() { |
|
420 server.rooms[room_id].flags.toggle(room_message_flag(&message)); |
|
421 } |
|
422 server.react(client_id, vec![SendRoomUpdate(None)]); |
|
423 } |
|
424 StartGame => { |
|
425 server.react(client_id, vec![StartRoomGame(room_id)]); |
|
426 } |
|
427 EngineMessage(em) => { |
|
428 let mut actions = Vec::new(); |
|
429 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
430 if c.teams_in_game > 0 { |
|
431 let decoding = decode(&em[..]).unwrap(); |
|
432 let messages = by_msg(&decoding); |
|
433 let valid = messages.filter(|m| is_msg_valid(m, &c.team_indices)); |
|
434 let non_empty = valid.clone().filter(|m| !is_msg_empty(m)); |
|
435 let sync_msg = valid.clone().filter(|m| is_msg_timed(m)) |
|
436 .last().map(|m| if is_msg_empty(m) {Some(encode(m))} else {None}); |
|
437 |
|
438 let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::<Vec<_>>()); |
|
439 if !em_response.is_empty() { |
|
440 actions.push(ForwardEngineMessage(vec![em_response]) |
|
441 .send_all().in_room(r.id).but_self().action()); |
|
442 } |
|
443 let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::<Vec<_>>()); |
|
444 if let Some(ref mut info) = r.game_info { |
|
445 if !em_log.is_empty() { |
|
446 info.msg_log.push(em_log); |
|
447 } |
|
448 if let Some(msg) = sync_msg { |
|
449 info.sync_msg = msg; |
|
450 } |
|
451 } |
|
452 } |
|
453 } |
|
454 server.react(client_id, actions) |
|
455 } |
|
456 RoundFinished => { |
|
457 let mut actions = Vec::new(); |
|
458 if let (c, Some(r)) = server.client_and_room(client_id) { |
|
459 if c.is_in_game() { |
|
460 c.set_is_in_game(false); |
|
461 actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]). |
|
462 send_all().in_room(r.id).action()); |
|
463 if r.game_info.is_some() { |
|
464 for team in r.client_teams(c.id) { |
|
465 actions.push(SendTeamRemovalMessage(team.name.clone())); |
|
466 } |
|
467 } |
|
468 } |
|
469 } |
|
470 server.react(client_id, actions) |
|
471 }, |
|
472 Rnd(v) => { |
|
473 let result = rnd_reply(&v); |
|
474 let mut echo = vec!["/rnd".to_string()]; |
|
475 echo.extend(v.into_iter()); |
|
476 let chat_msg = ChatMsg { |
|
477 nick: server.clients[client_id].nick.clone(), |
|
478 msg: echo.join(" ") |
|
479 }; |
|
480 server.react(client_id, vec![ |
|
481 chat_msg.send_all().in_room(room_id).action(), |
|
482 result.send_all().in_room(room_id).action()]) |
|
483 }, |
|
484 _ => warn!("Unimplemented!") |
|
485 } |
|
486 } |
|