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)
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 ***********************************************************************/
15 #include <fc_config.h>
36 #include "stdinhand.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
);
61 /**************************************************************************
62 Formulate a name for this connection, prefering the player name when
63 available and unambiguous (since this is the "standard" case), else
65 **************************************************************************/
66 static void form_chat_name(struct connection
*pconn
, char *buffer
, size_t len
)
68 struct player
*pplayer
= pconn
->playing
;
72 || strcmp(player_name(pplayer
), ANON_PLAYER_NAME
) == 0) {
73 fc_snprintf(buffer
, len
, "(%s)", pconn
->username
);
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
;
90 va_start(args
, format
);
91 vpackage_chat_msg(&packet
, sender
, color
, format
, 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
,
105 switch(player_conn
) {
107 notify_conn(pconn
->self
, NULL
, E_CHAT_ERROR
, ftc_server
,
108 _("%s is an ambiguous player name-prefix."), name
);
111 notify_conn(pconn
->self
, NULL
, E_CHAT_ERROR
, ftc_server
,
112 _("%s is an ambiguous connection name-prefix."), name
);
115 notify_conn(pconn
->self
, NULL
, E_CHAT_ERROR
, ftc_server
,
116 _("%s is an anonymous name. Use connection name."), name
);
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."),
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
) {
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."),
174 return; /* NB: stop here, don't send to observers. */
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
) {
197 && !conn_is_ignored(sender
, pconn
)) {
198 send_packet_chat_msg(pconn
, &packet
);
200 } conn_list_iterate_end
;
201 if (NULL
!= sender
->playing
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
)) {
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
,
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
318 **************************************************************************/
319 void handle_chat_msg_req(struct connection
*pconn
, const char *message
)
321 char real_message
[MAX_LEN_MSG
], *cp
;
324 sz_strlcpy(real_message
, message
);
326 /* This loop to prevent players from sending multiple lines which can
328 for (cp
= real_message
; *cp
!= '\0'; cp
++) {
329 if (*cp
== '\n' || *cp
== '\r') {
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
);
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."));
356 if (NULL
!= pconn
->playing
) {
357 chat_msg_to_allies(pconn
, real_message
+ 1);
359 chat_msg_to_global_observers(pconn
, real_message
+ 1);
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.
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
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
];
396 (void) fc_strlcpy(name
, real_message
, MIN(sizeof(name
),
397 cp
- real_message
+ 1));
399 double_colon
= (*(cp
+1) == ':');
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);
406 if (conn_dest
&& match_result_conn
< M_PRE_AMBIGUOUS
) {
407 chat_msg_to_conn(pconn
, conn_dest
, cp
+2);
412 pdest
= player_by_name_prefix(name
, &match_result_player
);
413 if (match_result_player
== M_PRE_AMBIGUOUS
) {
414 complain_ambiguous(pconn
, name
, 0);
417 if (pdest
&& strcmp(player_name(pdest
), ANON_PLAYER_NAME
) == 0) {
418 complain_ambiguous(pconn
, name
, 2);
421 if (pdest
&& match_result_player
< M_PRE_AMBIGUOUS
) {
422 chat_msg_to_player(pconn
, pdest
, cp
+ 1);
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);
431 if (conn_dest
&& match_result_conn
< M_PRE_AMBIGUOUS
) {
432 chat_msg_to_conn(pconn
, conn_dest
, cp
+1);
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
));
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
)) {
448 notify_conn(pconn
->self
, NULL
, E_CHAT_ERROR
, ftc_server
,
449 _("There is no connection by the name %s."), name
);
451 notify_conn(pconn
->self
, NULL
, E_CHAT_ERROR
, ftc_server
,
452 _("There is no player nor connection by the name %s."),
458 /* global message: */
459 chat_msg_to_all(pconn
, real_message
);