Freeciv21
Develop your civilization from humble roots to a global empire
handchat.cpp
Go to the documentation of this file.
1 /*__ ___ ***************************************
2 / \ / \ Copyright (c) 1996-2020 Freeciv21 and Freeciv
3 \_ \ / __/ contributors. This file is part of Freeciv21.
4  _\ \ / /__ Freeciv21 is free software: you can redistribute it
5  \___ \____/ __/ and/or modify it under the terms of the GNU General
6  \_ _/ Public License as published by the Free Software
7  | @ @ \_ Foundation, either version 3 of the License,
8  | or (at your option) any later version.
9  _/ /\ You should have received a copy of the GNU
10  /o) (o/\ \_ General Public License along with Freeciv21.
11  \_____/ / If not, see https://www.gnu.org/licenses/.
12  \____/ ********************************************************/
13 
14 #include <cstdarg>
15 #include <cstring>
16 
17 // utility
18 #include "fcintl.h"
19 #include "log.h"
20 #include "shared.h"
21 #include "support.h"
22 
23 // common
24 #include "chat.h"
25 #include "game.h"
26 #include "packets.h"
27 #include "player.h"
28 
29 // server
30 #include "console.h"
31 #include "notify.h"
32 #include "stdinhand.h"
33 
34 #include "handchat.h"
35 
36 #define MAX_LEN_CHAT_NAME \
37  (2 * MAX_LEN_NAME + 10) // for form_chat_name() names
38 
39 static void send_chat_msg(struct connection *pconn,
40  const struct connection *sender,
41  const struct ft_color color, const char *format,
42  ...) fc__attribute((__format__(__printf__, 4, 5)));
43 
47 static inline bool conn_is_ignored(const struct connection *sender,
48  const struct connection *dest)
49 {
50  if (nullptr != sender && nullptr != dest) {
51  return conn_pattern_list_match(dest->server.ignore_list, sender);
52  } else {
53  return false;
54  }
55 }
56 
62 static void form_chat_name(struct connection *pconn, char *buffer,
63  size_t len)
64 {
65  struct player *pplayer = pconn->playing;
66 
67  if (!pplayer || pconn->observer
68  || strcmp(player_name(pplayer), ANON_PLAYER_NAME) == 0) {
69  fc_snprintf(buffer, len, "(%s)", pconn->username);
70  } else {
71  fc_snprintf(buffer, len, "%s", player_name(pplayer));
72  }
73 }
74 
78 static void send_chat_msg(struct connection *pconn,
79  const struct connection *sender,
80  const struct ft_color color, const char *format,
81  ...)
82 {
83  struct packet_chat_msg packet;
84  va_list args;
85 
86  va_start(args, format);
87  vpackage_chat_msg(&packet, sender, color, format, args);
88  va_end(args);
89 
90  send_packet_chat_msg(pconn, &packet);
91 }
92 
98 static void complain_ambiguous(struct connection *pconn, const char *name,
99  int player_conn)
100 {
101  switch (player_conn) {
102  case 0:
103  notify_conn(pconn->self, nullptr, E_CHAT_ERROR, ftc_server,
104  _("%s is an ambiguous player name-prefix."), name);
105  break;
106  case 1:
107  notify_conn(pconn->self, nullptr, E_CHAT_ERROR, ftc_server,
108  _("%s is an ambiguous connection name-prefix."), name);
109  break;
110  case 2:
111  notify_conn(pconn->self, nullptr, E_CHAT_ERROR, ftc_server,
112  _("%s is an anonymous name. Use connection name."), name);
113  break;
114  default:
115  qCritical("Unknown variant in %s(): %d.", __FUNCTION__, player_conn);
116  }
117 }
118 
122 static void chat_msg_to_conn(struct connection *sender,
123  struct connection *dest, char *msg)
124 {
125  char sender_name[MAX_LEN_CHAT_NAME], dest_name[MAX_LEN_CHAT_NAME];
126 
127  form_chat_name(dest, dest_name, sizeof(dest_name));
128 
129  if (conn_is_ignored(sender, dest)) {
130  send_chat_msg(sender, nullptr, ftc_warning,
131  _("You cannot send messages to %s; you are ignored."),
132  dest_name);
133  return;
134  }
135 
136  msg = skip_leading_spaces(msg);
137  form_chat_name(sender, sender_name, sizeof(sender_name));
138 
139  send_chat_msg(sender, sender, ftc_chat_private, "->*%s* %s", dest_name,
140  msg);
141 
142  if (sender != dest) {
143  send_chat_msg(dest, sender, ftc_chat_private, "*%s* %s", sender_name,
144  msg);
145  }
146 }
147 
151 static void chat_msg_to_player(struct connection *sender,
152  struct player *pdest, char *msg)
153 {
154  struct packet_chat_msg packet;
155  char sender_name[MAX_LEN_CHAT_NAME];
156  struct connection *dest = nullptr; // The 'pdest' user.
157  struct event_cache_players *players =
158  event_cache_player_add(nullptr, pdest);
159 
160  msg = skip_leading_spaces(msg);
161  form_chat_name(sender, sender_name, sizeof(sender_name));
162 
163  // Find the user of the player 'pdest'.
164  conn_list_iterate(pdest->connections, pconn)
165  {
166  if (!pconn->observer) {
167  // Found it!
168  if (conn_is_ignored(sender, pconn)) {
169  send_chat_msg(sender, nullptr, ftc_warning,
170  _("You cannot send messages to %s; you are ignored."),
171  player_name(pdest));
172  return; // NB: stop here, don't send to observers.
173  }
174  dest = pconn;
175  break;
176  }
177  }
179 
180  // Repeat the message for the sender.
181  send_chat_msg(sender, sender, ftc_chat_private, "->{%s} %s",
182  player_name(pdest), msg);
183 
184  // Send the message to destination.
185  if (nullptr != dest && dest != sender) {
186  send_chat_msg(dest, sender, ftc_chat_private, "{%s} %s", sender_name,
187  msg);
188  }
189 
190  // Send the message to player observers.
191  package_chat_msg(&packet, sender, ftc_chat_private, "{%s -> %s} %s",
192  sender_name, player_name(pdest), msg);
193  conn_list_iterate(pdest->connections, pconn)
194  {
195  if (pconn != dest && pconn != sender
196  && !conn_is_ignored(sender, pconn)) {
197  send_packet_chat_msg(pconn, &packet);
198  }
199  }
201  if (nullptr != sender->playing && !sender->observer
202  && sender->playing != pdest) {
203  // The sender is another player.
204  conn_list_iterate(sender->playing->connections, pconn)
205  {
206  if (pconn != sender && !conn_is_ignored(sender, pconn)) {
207  send_packet_chat_msg(pconn, &packet);
208  }
209  }
211 
212  // Add player to event cache.
213  players = event_cache_player_add(players, sender->playing);
214  }
215 
216  event_cache_add_for_players(&packet, players);
217 }
218 
222 static void chat_msg_to_allies(struct connection *sender, char *msg)
223 {
224  struct packet_chat_msg packet;
225  struct event_cache_players *players = nullptr;
226  char sender_name[MAX_LEN_CHAT_NAME];
227 
228  msg = skip_leading_spaces(msg);
229  form_chat_name(sender, sender_name, sizeof(sender_name));
230 
231  package_chat_msg(&packet, sender, ftc_chat_ally, _("%s to allies: %s"),
232  sender_name, msg);
233 
234  players_iterate(aplayer)
235  {
236  if (!pplayers_allied(sender->playing, aplayer)) {
237  continue;
238  }
239 
240  conn_list_iterate(aplayer->connections, pconn)
241  {
242  if (!conn_is_ignored(sender, pconn)) {
243  send_packet_chat_msg(pconn, &packet);
244  }
245  }
247  players = event_cache_player_add(players, aplayer);
248  }
250 
251  // Add to the event cache.
252  event_cache_add_for_players(&packet, players);
253 }
254 
258 static void chat_msg_to_global_observers(struct connection *sender,
259  char *msg)
260 {
261  struct packet_chat_msg packet;
262  char sender_name[MAX_LEN_CHAT_NAME];
263 
264  msg = skip_leading_spaces(msg);
265  form_chat_name(sender, sender_name, sizeof(sender_name));
266 
267  package_chat_msg(&packet, sender, ftc_chat_ally,
268  _("%s to global observers: %s"), sender_name, msg);
269 
271  {
272  if (conn_is_global_observer(dest_conn)
273  && !conn_is_ignored(sender, dest_conn)) {
274  send_packet_chat_msg(dest_conn, &packet);
275  }
276  }
278 
279  // Add to the event cache.
281 }
282 
286 static void chat_msg_to_all(struct connection *sender, char *msg)
287 {
288  struct packet_chat_msg packet;
289  char sender_name[MAX_LEN_CHAT_NAME];
290 
291  msg = skip_leading_spaces(msg);
292  form_chat_name(sender, sender_name, sizeof(sender_name));
293 
294  package_chat_msg(&packet, sender, ftc_chat_public, "<%s> %s", sender_name,
295  msg);
296  con_write(C_COMMENT, "%s", packet.message);
297  lsend_packet_chat_msg(game.est_connections, &packet);
298 
299  // Add to the event cache.
300  event_cache_add_for_all(&packet);
301 }
302 
326 void handle_chat_msg_req(struct connection *pconn, const char *message)
327 {
328  char real_message[MAX_LEN_MSG], *cp;
329  bool double_colon;
330 
331  sz_strlcpy(real_message, message);
332 
333  /* This loop to prevent players from sending multiple lines which can
334  * be abused */
335  for (cp = real_message; *cp != '\0'; cp++) {
336  if (*cp == '\n' || *cp == '\r') {
337  *cp = '\0';
338  break;
339  }
340  }
341 
342  /* Server commands are prefixed with '/', which is an obvious
343  but confusing choice: even before this feature existed,
344  novice players were trying /who, /nick etc.
345  So consider this an incentive for IRC support,
346  or change it in chat.h - rp
347  */
348  if (real_message[0] == SERVER_COMMAND_PREFIX) {
349  // pass it to the command parser, which will chop the prefix off
350  (void) handle_stdin_input(pconn, real_message);
351  return;
352  }
353 
354  // Send to allies command
355  if (real_message[0] == CHAT_ALLIES_PREFIX) {
356  // this won't work if we aren't attached to a player
357  if (nullptr == pconn->playing && !pconn->observer) {
358  notify_conn(pconn->self, nullptr, E_CHAT_ERROR, ftc_server,
359  _("You are not attached to a player."));
360  return;
361  }
362 
363  if (nullptr != pconn->playing) {
364  chat_msg_to_allies(pconn, real_message + 1);
365  } else {
366  chat_msg_to_global_observers(pconn, real_message + 1);
367  }
368  return;
369  }
370 
371  /* Want to allow private messages with "player_name: message",
372  (or "connection_name: message"), including unambiguously
373  abbreviated player/connection name, but also want to allow
374  sensible use of ':' within messages, and _also_ want to
375  notice intended private messages with (eg) mis-spelt name.
376 
377  Approach:
378 
379  If there is no ':', or ':' is first on line,
380  message is global (send to all players)
381  else if the ':' is double, try matching part before "::" against
382  connection names: for single match send to that connection,
383  for multiple matches complain, else goto heuristics below.
384  else try matching part before (single) ':' against player names:
385  for single match send to that player, for multiple matches
386  complain
387  else try matching against connection names: for single match send
388  to that connection, for multiple matches complain
389  else if some heuristics apply (a space anywhere before first ':')
390  then treat as global message,
391  else complain (might be a typo-ed intended private message)
392  */
393 
394  cp = strchr(real_message, CHAT_DIRECT_PREFIX);
395 
396  if (cp && (cp != &real_message[0])) {
397  enum m_pre_result match_result_player, match_result_conn;
398  struct player *pdest = nullptr;
399  struct connection *conn_dest = nullptr;
400  char name[MAX_LEN_NAME];
401  char *cpblank;
402 
403  (void) fc_strlcpy(name, real_message,
404  MIN(sizeof(name), cp - real_message + 1));
405 
406  double_colon = (*(cp + 1) == CHAT_DIRECT_PREFIX);
407  if (double_colon) {
408  conn_dest = conn_by_user_prefix(name, &match_result_conn);
409  if (match_result_conn == M_PRE_AMBIGUOUS) {
410  complain_ambiguous(pconn, name, 1);
411  return;
412  }
413  if (conn_dest && match_result_conn < M_PRE_AMBIGUOUS) {
414  chat_msg_to_conn(pconn, conn_dest, cp + 2);
415  return;
416  }
417  } else {
418  // single colon
419  pdest = player_by_name_prefix(name, &match_result_player);
420  if (match_result_player == M_PRE_AMBIGUOUS) {
421  complain_ambiguous(pconn, name, 0);
422  return;
423  }
424  if (pdest && strcmp(player_name(pdest), ANON_PLAYER_NAME) == 0) {
425  complain_ambiguous(pconn, name, 2);
426  return;
427  }
428  if (pdest && match_result_player < M_PRE_AMBIGUOUS) {
429  chat_msg_to_player(pconn, pdest, cp + 1);
430  return;
431  // else try for connection name match before complaining
432  }
433  conn_dest = conn_by_user_prefix(name, &match_result_conn);
434  if (match_result_conn == M_PRE_AMBIGUOUS) {
435  complain_ambiguous(pconn, name, 1);
436  return;
437  }
438  if (conn_dest && match_result_conn < M_PRE_AMBIGUOUS) {
439  chat_msg_to_conn(pconn, conn_dest, cp + 1);
440  return;
441  }
442  }
443  /* Didn't match; check heuristics to see if this is likely
444  * to be a global message
445  */
446  cpblank = strchr(real_message, ' ');
447  if (!cpblank || (cp < cpblank)) {
448  if (double_colon) {
449  notify_conn(pconn->self, nullptr, E_CHAT_ERROR, ftc_server,
450  _("There is no connection by the name %s."), name);
451  } else {
452  notify_conn(pconn->self, nullptr, E_CHAT_ERROR, ftc_server,
453  _("There is no player nor connection by the name %s."),
454  name);
455  }
456  return;
457  }
458  }
459  // global message:
460  chat_msg_to_all(pconn, real_message);
461 }
#define CHAT_DIRECT_PREFIX
Definition: chat.h:25
#define CHAT_ALLIES_PREFIX
Definition: chat.h:24
#define SERVER_COMMAND_PREFIX
Definition: chat.h:23
bool conn_is_global_observer(const struct connection *pconn)
Returns TRUE if the given connection is a global observer.
Definition: connection.cpp:683
struct connection * conn_by_user_prefix(const char *user_name, enum m_pre_result *result)
Definition: connection.cpp:353
bool conn_pattern_list_match(const struct conn_pattern_list *plist, const struct connection *pconn)
Returns TRUE whether the connection fits one of the connection patterns.
Definition: connection.cpp:779
#define conn_list_iterate(connlist, pconn)
Definition: connection.h:99
#define conn_list_iterate_end
Definition: connection.h:101
void con_write(enum rfc_status rfc_status, const char *message,...)
Write to console and add line-break, and show prompt if required.
Definition: console.cpp:139
@ C_COMMENT
Definition: console.h:37
#define MAX_LEN_NAME
Definition: fc_types.h:61
#define _(String)
Definition: fcintl.h:50
const struct ft_color ftc_chat_private
const struct ft_color ftc_server
const struct ft_color ftc_warning
const struct ft_color ftc_chat_public
const struct ft_color ftc_chat_ally
struct civ_game game
Definition: game.cpp:47
static void static bool conn_is_ignored(const struct connection *sender, const struct connection *dest)
Returns whether 'dest' is ignoring the 'sender' connection.
Definition: handchat.cpp:47
static void complain_ambiguous(struct connection *pconn, const char *name, int player_conn)
Complain to sender that name was ambiguous.
Definition: handchat.cpp:98
static void chat_msg_to_player(struct connection *sender, struct player *pdest, char *msg)
Send private message to multi-connected player.
Definition: handchat.cpp:151
static void send_chat_msg(struct connection *pconn, const struct connection *sender, const struct ft_color color, const char *format,...) fc__attribute((__format__(__printf__
Send a chat message packet.
Definition: handchat.cpp:78
static void form_chat_name(struct connection *pconn, char *buffer, size_t len)
Formulate a name for this connection, prefering the player name when available and unambiguous (since...
Definition: handchat.cpp:62
#define MAX_LEN_CHAT_NAME
Definition: handchat.cpp:36
static void chat_msg_to_all(struct connection *sender, char *msg)
Send private message to all connections.
Definition: handchat.cpp:286
static void chat_msg_to_allies(struct connection *sender, char *msg)
Send private message to player allies.
Definition: handchat.cpp:222
static void chat_msg_to_global_observers(struct connection *sender, char *msg)
Send private message to all global observers.
Definition: handchat.cpp:258
void handle_chat_msg_req(struct connection *pconn, const char *message)
Handle a chat message packet from client:
Definition: handchat.cpp:326
static void chat_msg_to_conn(struct connection *sender, struct connection *dest, char *msg)
Send private message to single connection.
Definition: handchat.cpp:122
const char * name
Definition: inputfile.cpp:118
struct event_cache_players * event_cache_player_add(struct event_cache_players *players, const struct player *pplayer)
Select players for event_cache_add_for_players().
Definition: notify.cpp:693
void package_chat_msg(struct packet_chat_msg *packet, const struct connection *sender, const struct ft_color color, const char *format,...)
Fill a packet_chat_msg structure for a chat message.
Definition: notify.cpp:122
void event_cache_add_for_players(const struct packet_chat_msg *packet, struct event_cache_players *players)
Add an event to the cache for selected players.
Definition: notify.cpp:662
void notify_conn(struct conn_list *dest, const struct tile *ptile, enum event_type event, const struct ft_color color, const char *format,...)
See notify_conn_packet - this is just the "non-v" version, with varargs.
Definition: notify.cpp:235
void event_cache_add_for_global_observers(const struct packet_chat_msg *packet)
Add an event to the cache for all global observers.
Definition: notify.cpp:620
void vpackage_chat_msg(struct packet_chat_msg *packet, const struct connection *sender, const struct ft_color color, const char *format, va_list vargs)
Fill a packet_chat_msg structure for a chat message.
Definition: notify.cpp:102
void event_cache_add_for_all(const struct packet_chat_msg *packet)
Add an event to the cache for all connections.
Definition: notify.cpp:609
#define MAX_LEN_MSG
Definition: packets.h:37
int len
Definition: packhand.cpp:127
struct player * player_by_name_prefix(const char *name, enum m_pre_result *result)
Find player by its name prefix.
Definition: player.cpp:841
const char * player_name(const struct player *pplayer)
Return the leader name of the player.
Definition: player.cpp:816
bool pplayers_allied(const struct player *pplayer, const struct player *pplayer2)
Returns true iff players are allied.
Definition: player.cpp:1334
#define players_iterate_end
Definition: player.h:520
#define players_iterate(_pplayer)
Definition: player.h:514
#define ANON_PLAYER_NAME
Definition: player.h:26
char * skip_leading_spaces(char *s)
Returns 's' incremented to first non-space character.
Definition: shared.cpp:297
#define MIN(x, y)
Definition: shared.h:49
m_pre_result
Definition: shared.h:152
@ M_PRE_AMBIGUOUS
Definition: shared.h:155
bool handle_stdin_input(struct connection *caller, char *str)
Main entry point for "command input".
Definition: stdinhand.cpp:4445
struct conn_list * est_connections
Definition: game.h:88
struct player * playing
Definition: connection.h:142
struct conn_list * self
Definition: connection.h:150
bool observer
Definition: connection.h:138
char username[MAX_LEN_NAME]
Definition: connection.h:151
Definition: player.h:231
struct conn_list * connections
Definition: player.h:280
int fc_snprintf(char *str, size_t n, const char *format,...)
See also fc_utf8_snprintf_trunc(), fc_utf8_snprintf_rep().
Definition: support.cpp:537
size_t fc_strlcpy(char *dest, const char *src, size_t n)
fc_strlcpy() provides utf-8 version of (non-standard) function strlcpy() It is intended as more user-...
Definition: support.cpp:412
#define sz_strlcpy(dest, src)
Definition: support.h:140
int fc__attribute((nonnull(1, 3)))