Stop help talking about units' ability to attack relative to non-native
[freeciv.git] / server / handchat.c
blobfcbd198ed3b2849b6343df68ba990db68a68c8e8
1 /**********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <stdarg.h>
19 #include <stdio.h>
20 #include <string.h>
22 /* utility */
23 #include "fcintl.h"
24 #include "log.h"
25 #include "shared.h"
26 #include "support.h"
28 /* common */
29 #include "game.h"
30 #include "packets.h"
31 #include "player.h"
33 /* server */
34 #include "console.h"
35 #include "notify.h"
36 #include "stdinhand.h"
38 #include "handchat.h"
40 #define MAX_LEN_CHAT_NAME (2*MAX_LEN_NAME+10) /* for form_chat_name() names */
42 static void send_chat_msg(struct connection *pconn,
43 const struct connection *sender,
44 const struct ft_color color,
45 const char *format, ...)
46 fc__attribute((__format__ (__printf__, 4, 5)));
48 /****************************************************************************
49 Returns whether 'dest' is ignoring the 'sender' connection.
50 ****************************************************************************/
51 static inline bool conn_is_ignored(const struct connection *sender,
52 const struct connection *dest)
54 if (NULL != sender && NULL != dest) {
55 return conn_pattern_list_match(dest->server.ignore_list, sender);
56 } else {
57 return FALSE;
61 /**************************************************************************
62 Formulate a name for this connection, prefering the player name when
63 available and unambiguous (since this is the "standard" case), else
64 use the username.
65 **************************************************************************/
66 static void form_chat_name(struct connection *pconn, char *buffer, size_t len)
68 struct player *pplayer = pconn->playing;
70 if (!pplayer
71 || pconn->observer
72 || strcmp(player_name(pplayer), ANON_PLAYER_NAME) == 0) {
73 fc_snprintf(buffer, len, "(%s)", pconn->username);
74 } else {
75 fc_snprintf(buffer, len, "%s", player_name(pplayer));
79 /**************************************************************************
80 Send a chat message packet.
81 **************************************************************************/
82 static void send_chat_msg(struct connection *pconn,
83 const struct connection *sender,
84 const struct ft_color color,
85 const char *format, ...)
87 struct packet_chat_msg packet;
88 va_list args;
90 va_start(args, format);
91 vpackage_chat_msg(&packet, sender, color, format, args);
92 va_end(args);
94 send_packet_chat_msg(pconn, &packet);
97 /**************************************************************************
98 Complain to sender that name was ambiguous.
99 'player_conn' is 0 for player names, 1 for connection names,
100 2 for attempt to send to an anonymous player.
101 **************************************************************************/
102 static void complain_ambiguous(struct connection *pconn, const char *name,
103 int player_conn)
105 switch(player_conn) {
106 case 0:
107 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
108 _("%s is an ambiguous player name-prefix."), name);
109 break;
110 case 1:
111 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
112 _("%s is an ambiguous connection name-prefix."), name);
113 break;
114 case 2:
115 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
116 _("%s is an anonymous name. Use connection name."), name);
117 break;
118 default:
119 log_error("Unknown variant in %s(): %d.", __FUNCTION__, player_conn);
123 /**************************************************************************
124 Send private message to single connection.
125 **************************************************************************/
126 static void chat_msg_to_conn(struct connection *sender,
127 struct connection *dest, char *msg)
129 char sender_name[MAX_LEN_CHAT_NAME], dest_name[MAX_LEN_CHAT_NAME];
131 form_chat_name(dest, dest_name, sizeof(dest_name));
133 if (conn_is_ignored(sender, dest)) {
134 send_chat_msg(sender, NULL, ftc_warning,
135 _("You cannot send messages to %s; you are ignored."),
136 dest_name);
137 return;
140 msg = skip_leading_spaces(msg);
141 form_chat_name(sender, sender_name, sizeof(sender_name));
143 send_chat_msg(sender, sender, ftc_chat_private,
144 "->*%s* %s", dest_name, msg);
146 if (sender != dest) {
147 send_chat_msg(dest, sender, ftc_chat_private,
148 "*%s* %s", sender_name, msg);
152 /**************************************************************************
153 Send private message to multi-connected player.
154 **************************************************************************/
155 static void chat_msg_to_player(struct connection *sender,
156 struct player *pdest, char *msg)
158 struct packet_chat_msg packet;
159 char sender_name[MAX_LEN_CHAT_NAME];
160 struct connection *dest = NULL; /* The 'pdest' user. */
161 struct event_cache_players *players = event_cache_player_add(NULL, pdest);
163 msg = skip_leading_spaces(msg);
164 form_chat_name(sender, sender_name, sizeof(sender_name));
166 /* Find the user of the player 'pdest'. */
167 conn_list_iterate(pdest->connections, pconn) {
168 if (!pconn->observer) {
169 /* Found it! */
170 if (conn_is_ignored(sender, pconn)) {
171 send_chat_msg(sender, NULL, ftc_warning,
172 _("You cannot send messages to %s; you are ignored."),
173 player_name(pdest));
174 return; /* NB: stop here, don't send to observers. */
176 dest = pconn;
177 break;
179 } conn_list_iterate_end;
181 /* Repeat the message for the sender. */
182 send_chat_msg(sender, sender, ftc_chat_private,
183 "->{%s} %s", player_name(pdest), msg);
185 /* Send the message to destination. */
186 if (NULL != dest && dest != sender) {
187 send_chat_msg(dest, sender, ftc_chat_private,
188 "{%s} %s", sender_name, msg);
191 /* Send the message to player observers. */
192 package_chat_msg(&packet, sender, ftc_chat_private,
193 "{%s -> %s} %s", sender_name, player_name(pdest), msg);
194 conn_list_iterate(pdest->connections, pconn) {
195 if (pconn != dest
196 && pconn != sender
197 && !conn_is_ignored(sender, pconn)) {
198 send_packet_chat_msg(pconn, &packet);
200 } conn_list_iterate_end;
201 if (NULL != sender->playing
202 && !sender->observer
203 && sender->playing != pdest) {
204 /* The sender is another player. */
205 conn_list_iterate(sender->playing->connections, pconn) {
206 if (pconn != sender && !conn_is_ignored(sender, pconn)) {
207 send_packet_chat_msg(pconn, &packet);
209 } conn_list_iterate_end;
211 /* Add player to event cache. */
212 players = event_cache_player_add(players, sender->playing);
215 event_cache_add_for_players(&packet, players);
218 /**************************************************************************
219 Send private message to player allies.
220 **************************************************************************/
221 static void chat_msg_to_allies(struct connection *sender, char *msg)
223 struct packet_chat_msg packet;
224 struct event_cache_players *players = NULL;
225 char sender_name[MAX_LEN_CHAT_NAME];
227 msg = skip_leading_spaces(msg);
228 form_chat_name(sender, sender_name, sizeof(sender_name));
230 package_chat_msg(&packet, sender, ftc_chat_ally,
231 _("%s to allies: %s"), sender_name, msg);
233 players_iterate(aplayer) {
234 if (!pplayers_allied(sender->playing, aplayer)) {
235 continue;
238 conn_list_iterate(aplayer->connections, pconn) {
239 if (!conn_is_ignored(sender, pconn)) {
240 send_packet_chat_msg(pconn, &packet);
242 } conn_list_iterate_end;
243 players = event_cache_player_add(players, aplayer);
244 } players_iterate_end;
246 /* Add to the event cache. */
247 event_cache_add_for_players(&packet, players);
250 /**************************************************************************
251 Send private message to all global observers.
252 **************************************************************************/
253 static void chat_msg_to_global_observers(struct connection *sender,
254 char *msg)
256 struct packet_chat_msg packet;
257 char sender_name[MAX_LEN_CHAT_NAME];
259 msg = skip_leading_spaces(msg);
260 form_chat_name(sender, sender_name, sizeof(sender_name));
262 package_chat_msg(&packet, sender, ftc_chat_ally,
263 _("%s to global observers: %s"), sender_name, msg);
265 conn_list_iterate(game.est_connections, dest_conn) {
266 if (conn_is_global_observer(dest_conn)
267 && !conn_is_ignored(sender, dest_conn)) {
268 send_packet_chat_msg(dest_conn, &packet);
270 } conn_list_iterate_end;
272 /* Add to the event cache. */
273 event_cache_add_for_global_observers(&packet);
276 /**************************************************************************
277 Send private message to all connections.
278 **************************************************************************/
279 static void chat_msg_to_all(struct connection *sender, char *msg)
281 struct packet_chat_msg packet;
282 char sender_name[MAX_LEN_CHAT_NAME];
284 msg = skip_leading_spaces(msg);
285 form_chat_name(sender, sender_name, sizeof(sender_name));
287 package_chat_msg(&packet, sender, ftc_chat_public,
288 "<%s> %s", sender_name, msg);
289 con_write(C_COMMENT, "%s", packet.message);
290 lsend_packet_chat_msg(game.est_connections, &packet);
292 /* Add to the event cache. */
293 event_cache_add_for_all(&packet);
296 /**************************************************************************
297 Handle a chat message packet from client:
298 1. Work out whether it is a server command and if so run it;
299 2. Otherwise work out whether it is directed to a single player, or
300 to a single connection, and send there. (For a player, send to
301 all clients connected as that player, in multi-connect case);
302 3. Or it may be intended for all allied players.
303 4. Else send to all connections (game.est_connections).
305 In case 2, there can sometimes be ambiguity between player and
306 connection names. By default this tries to match player name first,
307 and only if that fails tries to match connection name. User can
308 override this and specify connection only by using two colons ("::")
309 after the destination name/prefix, instead of one.
311 The message sent will name the sender, and via format differences
312 also indicates whether the recipient is either all connections, a
313 single connection, or multiple connections to a single player.
315 Message is also echoed back to sender (with different format),
316 avoiding sending both original and echo if sender is in destination
317 set.
318 **************************************************************************/
319 void handle_chat_msg_req(struct connection *pconn, const char *message)
321 char real_message[MAX_LEN_MSG], *cp;
322 bool double_colon;
324 sz_strlcpy(real_message, message);
326 /* This loop to prevent players from sending multiple lines which can
327 * be abused */
328 for (cp = real_message; *cp != '\0'; cp++) {
329 if (*cp == '\n' || *cp == '\r') {
330 *cp = '\0';
331 break;
335 /* Server commands are prefixed with '/', which is an obvious
336 but confusing choice: even before this feature existed,
337 novice players were trying /who, /nick etc.
338 So consider this an incentive for IRC support,
339 or change it in stdinhand.h - rp
341 if (real_message[0] == SERVER_COMMAND_PREFIX) {
342 /* pass it to the command parser, which will chop the prefix off */
343 (void) handle_stdin_input(pconn, real_message);
344 return;
347 /* Send to allies command */
348 if (real_message[0] == ALLIESCHAT_COMMAND_PREFIX) {
349 /* this won't work if we aren't attached to a player */
350 if (NULL == pconn->playing && !pconn->observer) {
351 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
352 _("You are not attached to a player."));
353 return;
356 if (NULL != pconn->playing) {
357 chat_msg_to_allies(pconn, real_message + 1);
358 } else {
359 chat_msg_to_global_observers(pconn, real_message + 1);
361 return;
364 /* Want to allow private messages with "player_name: message",
365 (or "connection_name: message"), including unambiguously
366 abbreviated player/connection name, but also want to allow
367 sensible use of ':' within messages, and _also_ want to
368 notice intended private messages with (eg) mis-spelt name.
370 Approach:
372 If there is no ':', or ':' is first on line,
373 message is global (send to all players)
374 else if the ':' is double, try matching part before "::" against
375 connection names: for single match send to that connection,
376 for multiple matches complain, else goto heuristics below.
377 else try matching part before (single) ':' against player names:
378 for single match send to that player, for multiple matches
379 complain
380 else try matching against connection names: for single match send
381 to that connection, for multiple matches complain
382 else if some heuristics apply (a space anywhere before first ':')
383 then treat as global message,
384 else complain (might be a typo-ed intended private message)
387 cp = strchr(real_message, ':');
389 if (cp && (cp != &real_message[0])) {
390 enum m_pre_result match_result_player, match_result_conn;
391 struct player *pdest = NULL;
392 struct connection *conn_dest = NULL;
393 char name[MAX_LEN_NAME];
394 char *cpblank;
396 (void) fc_strlcpy(name, real_message, MIN(sizeof(name),
397 cp - real_message + 1));
399 double_colon = (*(cp+1) == ':');
400 if (double_colon) {
401 conn_dest = conn_by_user_prefix(name, &match_result_conn);
402 if (match_result_conn == M_PRE_AMBIGUOUS) {
403 complain_ambiguous(pconn, name, 1);
404 return;
406 if (conn_dest && match_result_conn < M_PRE_AMBIGUOUS) {
407 chat_msg_to_conn(pconn, conn_dest, cp+2);
408 return;
410 } else {
411 /* single colon */
412 pdest = player_by_name_prefix(name, &match_result_player);
413 if (match_result_player == M_PRE_AMBIGUOUS) {
414 complain_ambiguous(pconn, name, 0);
415 return;
417 if (pdest && strcmp(player_name(pdest), ANON_PLAYER_NAME) == 0) {
418 complain_ambiguous(pconn, name, 2);
419 return;
421 if (pdest && match_result_player < M_PRE_AMBIGUOUS) {
422 chat_msg_to_player(pconn, pdest, cp + 1);
423 return;
424 /* else try for connection name match before complaining */
426 conn_dest = conn_by_user_prefix(name, &match_result_conn);
427 if (match_result_conn == M_PRE_AMBIGUOUS) {
428 complain_ambiguous(pconn, name, 1);
429 return;
431 if (conn_dest && match_result_conn < M_PRE_AMBIGUOUS) {
432 chat_msg_to_conn(pconn, conn_dest, cp+1);
433 return;
435 if (pdest && match_result_player < M_PRE_AMBIGUOUS) {
436 /* Would have done something above if connected */
437 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
438 _("%s is not connected."), player_name(pdest));
439 return;
442 /* Didn't match; check heuristics to see if this is likely
443 * to be a global message
445 cpblank = strchr(real_message, ' ');
446 if (!cpblank || (cp < cpblank)) {
447 if (double_colon) {
448 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
449 _("There is no connection by the name %s."), name);
450 } else {
451 notify_conn(pconn->self, NULL, E_CHAT_ERROR, ftc_server,
452 _("There is no player nor connection by the name %s."),
453 name);
455 return;
458 /* global message: */
459 chat_msg_to_all(pconn, real_message);