Moved make_escapes() and remove_escapes() to support.c.
[freeciv.git] / server / connecthand.c
blob3e71d0376884e26e39725f839b9d4d14e2ea48ee
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 <string.h>
20 /* utility */
21 #include "capability.h"
22 #include "fcintl.h"
23 #include "log.h"
24 #include "mem.h"
25 #include "support.h"
27 /* common */
28 #include "capstr.h"
29 #include "events.h"
30 #include "game.h"
31 #include "packets.h"
32 #include "player.h"
33 #include "version.h"
35 /* server */
36 #include "aiiface.h"
37 #include "auth.h"
38 #include "diplhand.h"
39 #include "edithand.h"
40 #include "gamehand.h"
41 #include "maphand.h"
42 #include "meta.h"
43 #include "notify.h"
44 #include "plrhand.h"
45 #include "report.h"
46 #include "ruleset.h"
47 #include "sernet.h"
48 #include "settings.h"
49 #include "srv_main.h"
50 #include "stdinhand.h"
51 #include "voting.h"
53 #include "connecthand.h"
56 static bool connection_attach_real(struct connection *pconn,
57 struct player *pplayer,
58 bool observing, bool connecting);
60 /**************************************************************************
61 Set the access level of a connection, and re-send some needed info. If
62 granted is TRUE, then it will overwrite the granted_access_level too.
63 Else, it will affect only the current access level.
65 NB: This function does not send updated connection information to other
66 clients, you need to do that yourself afterwards.
67 **************************************************************************/
68 void conn_set_access(struct connection *pconn, enum cmdlevel new_level,
69 bool granted)
71 enum cmdlevel old_level = conn_get_access(pconn);
73 pconn->access_level = new_level;
74 if (granted) {
75 pconn->server.granted_access_level = new_level;
78 if (old_level != new_level
79 && (ALLOW_HACK == old_level || ALLOW_HACK == new_level)) {
80 send_server_hack_level_settings(pconn->self);
84 /**************************************************************************
85 Restore access level for the given connection (user). Used when taking
86 a player, observing, or detaching.
88 NB: This function does not send updated connection information to other
89 clients, you need to do that yourself afterwards.
90 **************************************************************************/
91 static void restore_access_level(struct connection *pconn)
93 /* Restore previous privileges. */
94 enum cmdlevel level = pconn->server.granted_access_level;
96 /* Detached connections must have at most the same privileges
97 * as observers, unless they were granted something higher than
98 * ALLOW_BASIC in the first place. */
99 if ((pconn->observer || !pconn->playing) && level == ALLOW_BASIC) {
100 level = ALLOW_INFO;
103 conn_set_access(pconn, level, FALSE);
106 /**************************************************************************
107 This is used when a new player joins a server, before the game
108 has started. If pconn is NULL, is an AI, else a client.
110 N.B. this only attachs a connection to a player if
111 pconn->username == player->username
113 Here we send initial packets:
114 - ruleset datas.
115 - server settings.
116 - scenario info.
117 - game info.
118 - players infos (note it's resent in srv_main.c::send_all_info(),
119 see comment there).
120 - connections infos.
121 - running vote infos.
122 ... and additionnal packets if the game already started.
123 **************************************************************************/
124 void establish_new_connection(struct connection *pconn)
126 struct conn_list *dest = pconn->self;
127 struct player *pplayer;
128 struct packet_server_join_reply packet;
129 struct packet_chat_msg connect_info;
130 char hostname[512];
131 bool delegation_error = FALSE;
132 struct packet_set_topology topo_packet;
134 /* zero out the password */
135 memset(pconn->server.password, 0, sizeof(pconn->server.password));
137 /* send join_reply packet */
138 packet.you_can_join = TRUE;
139 sz_strlcpy(packet.capability, our_capability);
140 fc_snprintf(packet.message, sizeof(packet.message), _("%s Welcome"),
141 pconn->username);
142 sz_strlcpy(packet.challenge_file, new_challenge_filename(pconn));
143 packet.conn_id = pconn->id;
144 send_packet_server_join_reply(pconn, &packet);
146 /* "establish" the connection */
147 pconn->established = TRUE;
148 pconn->server.status = AS_ESTABLISHED;
150 pconn->server.delegation.status = FALSE;
151 pconn->server.delegation.playing = NULL;
152 pconn->server.delegation.observer = FALSE;
154 conn_list_append(game.est_connections, pconn);
155 if (conn_list_size(game.est_connections) == 1) {
156 /* First connection
157 * Replace "restarting in x seconds" meta message */
158 maybe_automatic_meta_message(default_meta_message_string());
159 (void) send_server_info_to_metaserver(META_INFO);
162 /* introduce the server to the connection */
163 if (fc_gethostname(hostname, sizeof(hostname)) == 0) {
164 notify_conn(dest, NULL, E_CONNECTION, ftc_any,
165 _("Welcome to the %s Server running at %s port %d."),
166 freeciv_name_version(), hostname, srvarg.port);
167 } else {
168 notify_conn(dest, NULL, E_CONNECTION, ftc_any,
169 _("Welcome to the %s Server at port %d."),
170 freeciv_name_version(), srvarg.port);
173 /* FIXME: this (getting messages about others logging on) should be a
174 * message option for the client with event */
176 /* Notify the console that you're here. */
177 log_normal(_("%s has connected from %s."), pconn->username, pconn->addr);
179 conn_compression_freeze(pconn);
180 send_rulesets(dest);
181 send_server_setting_control(pconn);
182 send_server_settings(dest);
183 send_scenario_info(dest);
184 send_scenario_description(dest);
185 send_game_info(dest);
186 topo_packet.topology_id = game.map.topology_id;
187 send_packet_set_topology(pconn, &topo_packet);
189 /* Do we have a player that a delegate is currently controlling? */
190 if ((pplayer = player_by_user_delegated(pconn->username))) {
191 /* Reassert our control over the player. */
192 struct connection *pdelegate;
193 fc_assert_ret(player_delegation_get(pplayer) != NULL);
194 pdelegate = conn_by_user(player_delegation_get(pplayer));
196 if (pdelegate && connection_delegate_restore(pdelegate)) {
197 /* Delegate now detached from our player. We will restore control
198 * over them as normal below. */
199 notify_conn(pconn->self, NULL, E_CONNECTION, ftc_server,
200 _("Your delegate %s was controlling your player '%s'; "
201 "now detached."), pdelegate->username,
202 player_name(pplayer));
203 notify_conn(pdelegate->self, NULL, E_CONNECTION, ftc_server,
204 _("%s reconnected, ending your delegated control of "
205 "player '%s'."), pconn->username, player_name(pplayer));
206 } else {
207 fc_assert(pdelegate);
208 /* This really shouldn't happen. */
209 log_error("Failed to revoke delegate %s's control of %s, so owner %s "
210 "can't regain control.", pdelegate->username,
211 player_name(pplayer), pconn->username);
212 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
213 _("Couldn't get control of '%s' from delegation to %s."),
214 player_name(pplayer), pdelegate->username);
215 delegation_error = TRUE;
216 pplayer = NULL;
220 if (!delegation_error) {
221 if ((pplayer = player_by_user(pconn->username))
222 && connection_attach_real(pconn, pplayer, FALSE, TRUE)) {
223 /* a player has already been created for this user, reconnect */
225 if (S_S_INITIAL == server_state()) {
226 send_player_info_c(NULL, dest);
228 } else {
229 if (!game_was_started()) {
230 if (connection_attach_real(pconn, NULL, FALSE, TRUE)) {
231 pplayer = conn_get_player(pconn);
232 fc_assert(pplayer != NULL);
233 } else {
234 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
235 _("Couldn't attach your connection to new player."));
236 log_verbose("%s is not attached to a player", pconn->username);
239 send_player_info_c(NULL, dest);
243 send_conn_info(game.est_connections, dest);
245 if (NULL == pplayer) {
246 /* Else this has already been done in connection_attach_real(). */
247 send_pending_events(pconn, TRUE);
248 send_running_votes(pconn, FALSE);
249 restore_access_level(pconn);
250 send_conn_info(dest, game.est_connections);
252 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
253 _("You are logged in as '%s' connected to no player."),
254 pconn->username);
255 } else {
256 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
257 _("You are logged in as '%s' connected to %s."),
258 pconn->username,
259 player_name(pconn->playing));
262 /* Send information about delegation(s). */
263 send_delegation_info(pconn);
265 /* Notify the *other* established connections that you are connected, and
266 * add the info for all in event cache. Note we must to do it after we
267 * sent the pending events to pconn (from this function and also
268 * connection_attach()), otherwise pconn will receive it too. */
269 if (conn_controls_player(pconn)) {
270 package_event(&connect_info, NULL, E_CONNECTION, ftc_server,
271 _("%s has connected from %s (player %s)."),
272 pconn->username, pconn->addr,
273 player_name(conn_get_player(pconn)));
274 } else {
275 package_event(&connect_info, NULL, E_CONNECTION, ftc_server,
276 _("%s has connected from %s."),
277 pconn->username, pconn->addr);
279 conn_list_iterate(game.est_connections, aconn) {
280 if (aconn != pconn) {
281 send_packet_chat_msg(aconn, &connect_info);
283 } conn_list_iterate_end;
284 event_cache_add_for_all(&connect_info);
286 /* if need be, tell who we're waiting on to end the game.info.turn */
287 if (S_S_RUNNING == server_state() && game.server.turnblock) {
288 players_iterate_alive(cplayer) {
289 if (!cplayer->ai_controlled
290 && !cplayer->phase_done
291 && cplayer != pconn->playing) { /* skip current player */
292 notify_conn(dest, NULL, E_CONNECTION, ftc_any,
293 _("Turn-blocking game play: "
294 "waiting on %s to finish turn..."),
295 player_name(cplayer));
297 } players_iterate_alive_end;
300 if (game.info.is_edit_mode) {
301 notify_conn(dest, NULL, E_SETTING, ftc_editor,
302 _(" *** Server is in edit mode. *** "));
305 if (NULL != pplayer) {
306 /* Else, no need to do anything. */
307 reset_all_start_commands(TRUE);
308 (void) send_server_info_to_metaserver(META_INFO);
311 send_current_history_report(pconn->self);
313 conn_compression_thaw(pconn);
316 /**************************************************************************
317 send the rejection packet to the client.
318 **************************************************************************/
319 void reject_new_connection(const char *msg, struct connection *pconn)
321 struct packet_server_join_reply packet;
323 /* zero out the password */
324 memset(pconn->server.password, 0, sizeof(pconn->server.password));
326 packet.you_can_join = FALSE;
327 sz_strlcpy(packet.capability, our_capability);
328 sz_strlcpy(packet.message, msg);
329 packet.challenge_file[0] = '\0';
330 packet.conn_id = -1;
331 send_packet_server_join_reply(pconn, &packet);
332 log_normal(_("Client rejected: %s."), conn_description(pconn));
333 flush_connection_send_buffer_all(pconn);
336 /**************************************************************************
337 Returns FALSE if the clients gets rejected and the connection should be
338 closed. Returns TRUE if the client get accepted.
339 **************************************************************************/
340 bool handle_login_request(struct connection *pconn,
341 struct packet_server_join_req *req)
343 char msg[MAX_LEN_MSG];
344 int kick_time_remaining;
346 if (pconn->established || pconn->server.status != AS_NOT_ESTABLISHED) {
347 /* We read the PACKET_SERVER_JOIN_REQ twice from this connection,
348 * this is probably not a Freeciv client. */
349 return FALSE;
352 log_normal(_("Connection request from %s from %s"),
353 req->username, pconn->addr);
355 /* print server and client capabilities to console */
356 log_normal(_("%s has client version %d.%d.%d%s"),
357 pconn->username, req->major_version, req->minor_version,
358 req->patch_version, req->version_label);
359 log_verbose("Client caps: %s", req->capability);
360 log_verbose("Server caps: %s", our_capability);
361 conn_set_capability(pconn, req->capability);
363 /* Make sure the server has every capability the client needs */
364 if (!has_capabilities(our_capability, req->capability)) {
365 fc_snprintf(msg, sizeof(msg),
366 _("The client is missing a capability that this server needs.\n"
367 "Server version: %d.%d.%d%s Client version: %d.%d.%d%s."
368 " Upgrading may help!"),
369 MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, VERSION_LABEL,
370 req->major_version, req->minor_version,
371 req->patch_version, req->version_label);
372 reject_new_connection(msg, pconn);
373 log_normal(_("%s was rejected: Mismatched capabilities."),
374 req->username);
375 return FALSE;
378 /* Make sure the client has every capability the server needs */
379 if (!has_capabilities(req->capability, our_capability)) {
380 fc_snprintf(msg, sizeof(msg),
381 _("The server is missing a capability that the client needs.\n"
382 "Server version: %d.%d.%d%s Client version: %d.%d.%d%s."
383 " Upgrading may help!"),
384 MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, VERSION_LABEL,
385 req->major_version, req->minor_version,
386 req->patch_version, req->version_label);
387 reject_new_connection(msg, pconn);
388 log_normal(_("%s was rejected: Mismatched capabilities."),
389 req->username);
390 return FALSE;
393 remove_leading_trailing_spaces(req->username);
395 /* Name-sanity check: could add more checks? */
396 if (!is_valid_username(req->username)) {
397 fc_snprintf(msg, sizeof(msg), _("Invalid username '%s'"), req->username);
398 reject_new_connection(msg, pconn);
399 log_normal(_("%s was rejected: Invalid name [%s]."),
400 req->username, pconn->addr);
401 return FALSE;
404 if (conn_is_kicked(pconn, &kick_time_remaining)) {
405 fc_snprintf(msg, sizeof(msg), _("You have been kicked from this server "
406 "and cannot reconnect for %d seconds."),
407 kick_time_remaining);
408 reject_new_connection(msg, pconn);
409 log_normal(_("%s was rejected: Connection kicked "
410 "(%d seconds remaining)."),
411 req->username, kick_time_remaining);
412 return FALSE;
415 /* don't allow duplicate logins */
416 conn_list_iterate(game.all_connections, aconn) {
417 if (fc_strcasecmp(req->username, aconn->username) == 0) {
418 fc_snprintf(msg, sizeof(msg), _("'%s' already connected."),
419 req->username);
420 reject_new_connection(msg, pconn);
421 log_normal(_("%s was rejected: Duplicate login name [%s]."),
422 req->username, pconn->addr);
423 return FALSE;
425 } conn_list_iterate_end;
427 /* Remove the ping timeout given in sernet.c:server_make_connection(). */
428 fc_assert_msg(1 == timer_list_size(pconn->server.ping_timers),
429 "Ping timer list size %d, should be 1. Have we sent "
430 "a ping to unestablished connection %s?",
431 timer_list_size(pconn->server.ping_timers),
432 conn_description(pconn));
433 timer_list_pop_front(pconn->server.ping_timers);
435 if (game.server.connectmsg[0] != '\0') {
436 log_debug("Sending connectmsg: %s", game.server.connectmsg);
437 dsend_packet_connect_msg(pconn, game.server.connectmsg);
440 if (srvarg.auth_enabled) {
441 return auth_user(pconn, req->username);
442 } else {
443 sz_strlcpy(pconn->username, req->username);
444 establish_new_connection(pconn);
445 return TRUE;
449 /****************************************************************************
450 High-level server stuff when connection to client is closed or lost.
451 Reports loss to log, and to other players if the connection was a
452 player. Also removes player in pregame, applies auto_toggle, and
453 does check for turn done (since can depend on connection/ai status).
454 Note you shouldn't this function directly. You should use
455 server_break_connection() if you want to close the connection.
456 ****************************************************************************/
457 void lost_connection_to_client(struct connection *pconn)
459 const char *desc = conn_description(pconn);
461 fc_assert_ret(TRUE == pconn->server.is_closing);
463 log_normal(_("Lost connection: %s."), desc);
465 /* Special color (white on black) for player loss */
466 notify_conn(game.est_connections, NULL, E_CONNECTION,
467 conn_controls_player(pconn) ? ftc_player_lost : ftc_server,
468 _("Lost connection: %s."), desc);
470 connection_detach(pconn, TRUE);
471 send_conn_info_remove(pconn->self, game.est_connections);
472 notify_if_first_access_level_is_available();
474 check_for_full_turn_done();
477 /**************************************************************************
478 Fill in packet_conn_info from full connection struct.
479 **************************************************************************/
480 static void package_conn_info(struct connection *pconn,
481 struct packet_conn_info *packet)
483 packet->id = pconn->id;
484 packet->used = pconn->used;
485 packet->established = pconn->established;
486 packet->player_num = (NULL != pconn->playing)
487 ? player_number(pconn->playing)
488 : player_slot_count();
489 packet->observer = pconn->observer;
490 packet->access_level = pconn->access_level;
492 sz_strlcpy(packet->username, pconn->username);
493 sz_strlcpy(packet->addr, pconn->addr);
494 sz_strlcpy(packet->capability, pconn->capability);
497 /**************************************************************************
498 Handle both send_conn_info() and send_conn_info_removed(), depending
499 on 'remove' arg. Sends conn_info packets for 'src' to 'dest', turning
500 off 'used' if 'remove' is specified.
501 **************************************************************************/
502 static void send_conn_info_arg(struct conn_list *src,
503 struct conn_list *dest, bool remove_conn)
505 struct packet_conn_info packet;
507 if (!dest) {
508 dest = game.est_connections;
511 conn_list_iterate(src, psrc) {
512 package_conn_info(psrc, &packet);
513 if (remove_conn) {
514 packet.used = FALSE;
516 lsend_packet_conn_info(dest, &packet);
517 } conn_list_iterate_end;
520 /**************************************************************************
521 Send conn_info packets to tell 'dest' connections all about
522 'src' connections.
523 **************************************************************************/
524 void send_conn_info(struct conn_list *src, struct conn_list *dest)
526 send_conn_info_arg(src, dest, FALSE);
529 /**************************************************************************
530 Like send_conn_info(), but turn off the 'used' bits to tell clients
531 to remove info about these connections instead of adding it.
532 **************************************************************************/
533 void send_conn_info_remove(struct conn_list *src, struct conn_list *dest)
535 send_conn_info_arg(src, dest, TRUE);
538 /**************************************************************************
539 Search for first uncontrolled player
540 **************************************************************************/
541 struct player *find_uncontrolled_player(void)
543 players_iterate(played) {
544 if (!played->is_connected && !played->was_created) {
545 return played;
547 } players_iterate_end;
549 return NULL;
552 /****************************************************************************
553 Setup pconn as a client connected to pplayer or observer:
554 Updates pconn->playing, pplayer->connections, pplayer->is_connected
555 and pconn->observer.
557 - If pplayer is NULL and observing is FALSE: take the next available
558 player that is not connected.
559 - If pplayer is NULL and observing is TRUE: attach this connection to
560 the game as global observer.
561 - If pplayer is not NULL and observing is FALSE: take this player.
562 - If pplayer is not NULL and observing is TRUE: observe this player.
564 Note take_command() needs to know if this function will success before
565 it's time to call this. Keep take_command() checks in sync when
566 modifying this.
567 ****************************************************************************/
568 static bool connection_attach_real(struct connection *pconn,
569 struct player *pplayer,
570 bool observing, bool connecting)
572 fc_assert_ret_val(pconn != NULL, FALSE);
573 fc_assert_ret_val_msg(!pconn->observer && pconn->playing == NULL, FALSE,
574 "connections must be detached with "
575 "connection_detach() before calling this!");
577 if (!observing) {
578 if (NULL == pplayer) {
579 /* search for uncontrolled player */
580 pplayer = find_uncontrolled_player();
582 if (NULL == pplayer) {
583 /* no uncontrolled player found */
584 if (player_count() >= game.server.max_players
585 || normal_player_count() >= server.playable_nations) {
586 return FALSE;
588 /* add new player, or not */
589 /* Should only be called in such a way as to create a new player
590 * in the pregame */
591 fc_assert_ret_val(!game_was_started(), FALSE);
592 pplayer = server_create_player(-1, default_ai_type_name(),
593 NULL, FALSE);
594 /* Pregame => no need to assign_player_colors() */
595 if (!pplayer) {
596 return FALSE;
598 } else {
599 team_remove_player(pplayer);
601 server_player_init(pplayer, FALSE, TRUE);
603 /* Make it human! */
604 pplayer->ai_controlled = FALSE;
607 sz_strlcpy(pplayer->username, pconn->username);
608 pplayer->unassigned_user = FALSE;
609 pplayer->user_turns = 0; /* reset for a new user */
610 pplayer->is_connected = TRUE;
612 if (!game_was_started()) {
613 if (!pplayer->was_created && NULL == pplayer->nation) {
614 /* Temporarily set player_name() to username. */
615 server_player_set_name(pplayer, pconn->username);
617 (void) aifill(game.info.aifill);
620 if (game.server.auto_ai_toggle && pplayer->ai_controlled) {
621 toggle_ai_player_direct(NULL, pplayer);
624 send_player_info_c(pplayer, game.est_connections);
626 /* Remove from global observers list, if was there */
627 conn_list_remove(game.glob_observers, pconn);
628 } else if (pplayer == NULL) {
629 /* Global observer */
630 bool already = FALSE;
632 fc_assert(observing);
634 conn_list_iterate(game.glob_observers, pconn2) {
635 if (pconn2 == pconn) {
636 already = TRUE;
637 break;
639 } conn_list_iterate_end;
641 if (!already) {
642 conn_list_append(game.glob_observers, pconn);
646 /* We don't want the connection's username on another player. */
647 players_iterate(aplayer) {
648 if (aplayer != pplayer
649 && 0 == strncmp(aplayer->username, pconn->username, MAX_LEN_NAME)) {
650 sz_strlcpy(aplayer->username, _(ANON_USER_NAME));
651 aplayer->unassigned_user = TRUE;
652 send_player_info_c(aplayer, NULL);
654 } players_iterate_end;
656 pconn->observer = observing;
657 pconn->playing = pplayer;
658 if (pplayer) {
659 conn_list_append(pplayer->connections, pconn);
662 restore_access_level(pconn);
664 /* Reset the delta-state. */
665 send_conn_info(pconn->self, game.est_connections); /* Client side. */
666 conn_reset_delta_state(pconn); /* Server side. */
668 /* Initial packets don't need to be resent. See comment for
669 * connecthand.c::establish_new_connection(). */
670 switch (server_state()) {
671 case S_S_INITIAL:
672 send_pending_events(pconn, connecting);
673 send_running_votes(pconn, !connecting);
674 break;
676 case S_S_RUNNING:
677 conn_compression_freeze(pconn);
678 send_all_info(pconn->self);
679 if (game.info.is_edit_mode && can_conn_edit(pconn)) {
680 edithand_send_initial_packets(pconn->self);
682 conn_compression_thaw(pconn);
683 /* Enter C_S_RUNNING client state. */
684 dsend_packet_start_phase(pconn, game.info.phase);
685 /* Must be after C_S_RUNNING client state to be effective. */
686 send_diplomatic_meetings(pconn);
687 send_pending_events(pconn, connecting);
688 send_running_votes(pconn, !connecting);
689 break;
691 case S_S_OVER:
692 conn_compression_freeze(pconn);
693 send_all_info(pconn->self);
694 if (game.info.is_edit_mode && can_conn_edit(pconn)) {
695 edithand_send_initial_packets(pconn->self);
697 conn_compression_thaw(pconn);
698 report_final_scores(pconn->self);
699 send_pending_events(pconn, connecting);
700 send_running_votes(pconn, !connecting);
701 if (!connecting) {
702 /* Send information about delegation(s). */
703 send_delegation_info(pconn);
705 break;
708 send_updated_vote_totals(NULL);
710 return TRUE;
713 /****************************************************************************
714 Setup pconn as a client connected to pplayer or observer.
715 ****************************************************************************/
716 bool connection_attach(struct connection *pconn, struct player *pplayer,
717 bool observing)
719 return connection_attach_real(pconn, pplayer, observing, FALSE);
722 /****************************************************************************
723 Remove pconn as a client connected to pplayer:
724 Updates pconn->playing, pconn->playing->connections,
725 pconn->playing->is_connected and pconn->observer.
727 pconn remains a member of game.est_connections.
729 If remove_unused_player is TRUE, may remove a player left with no
730 controlling connection (only in pregame, and not if explicitly /created).
731 ****************************************************************************/
732 void connection_detach(struct connection *pconn, bool remove_unused_player)
734 struct player *pplayer;
736 fc_assert_ret(pconn != NULL);
738 if (NULL != (pplayer = pconn->playing)) {
739 bool was_connected = pplayer->is_connected;
741 send_remove_team_votes(pconn);
742 conn_list_remove(pplayer->connections, pconn);
743 pconn->playing = NULL;
744 pconn->observer = FALSE;
745 restore_access_level(pconn);
746 cancel_connection_votes(pconn);
747 send_updated_vote_totals(NULL);
748 send_conn_info(pconn->self, game.est_connections);
750 /* If any other (non-observing) conn is attached to this player, the
751 * player is still connected. */
752 pplayer->is_connected = FALSE;
753 conn_list_iterate(pplayer->connections, aconn) {
754 if (!aconn->observer) {
755 pplayer->is_connected = TRUE;
756 break;
758 } conn_list_iterate_end;
760 if (was_connected && !pplayer->is_connected) {
761 /* Player just lost its controlling connection. */
762 if (remove_unused_player &&
763 !pplayer->was_created && !game_was_started()) {
764 /* Remove player. */
765 conn_list_iterate(pplayer->connections, aconn) {
766 /* Detach all. */
767 fc_assert_action(aconn != pconn, continue);
768 notify_conn(aconn->self, NULL, E_CONNECTION, ftc_server,
769 _("Detaching from %s."), player_name(pplayer));
770 /* Recursive... but shouldn't be a problem, as this can only
771 * be a non-controlling connection so can't get back here. */
772 connection_detach(aconn, TRUE);
773 } conn_list_iterate_end;
775 /* Actually do the removal. */
776 server_remove_player(pplayer);
777 (void) aifill(game.info.aifill);
778 reset_all_start_commands(TRUE);
779 } else {
780 /* Aitoggle the player if no longer connected. */
781 if (game.server.auto_ai_toggle && !pplayer->ai_controlled) {
782 toggle_ai_player_direct(NULL, pplayer);
783 /* send_player_info_c() was formerly updated by
784 * toggle_ai_player_direct(), so it must be safe to send here now?
786 * At other times, data from send_conn_info() is used by the
787 * client to display player information.
788 * See establish_new_connection().
790 log_verbose("connection_detach() calls send_player_info_c()");
791 send_player_info_c(pplayer, NULL);
793 reset_all_start_commands(TRUE);
797 } else {
798 pconn->observer = FALSE;
799 restore_access_level(pconn);
800 send_conn_info(pconn->self, game.est_connections);
804 /*****************************************************************************
805 Use a delegation to get control over another player.
806 *****************************************************************************/
807 bool connection_delegate_take(struct connection *pconn,
808 struct player *dplayer)
810 fc_assert_ret_val(pconn->server.delegation.status == FALSE, FALSE);
812 /* Save the original player of this connection and the original username of
813 * the player. */
814 pconn->server.delegation.status = TRUE;
815 pconn->server.delegation.playing = conn_get_player(pconn);
816 pconn->server.delegation.observer = pconn->observer;
817 if (conn_controls_player(pconn)) {
818 /* Setting orig_username in the player we're about to put aside is
819 * a flag that no-one should be allowed to mess with it (e.g. /take). */
820 struct player *oplayer = conn_get_player(pconn);
822 fc_assert_ret_val(oplayer != dplayer, FALSE);
823 fc_assert_ret_val(strlen(oplayer->server.orig_username) == 0, FALSE);
824 sz_strlcpy(oplayer->server.orig_username, oplayer->username);
826 fc_assert_ret_val(strlen(dplayer->server.orig_username) == 0, FALSE);
827 sz_strlcpy(dplayer->server.orig_username, dplayer->username);
829 /* Detach the current connection. */
830 if (NULL != pconn->playing || pconn->observer) {
831 connection_detach(pconn, FALSE);
834 /* Try to attach to the new player */
835 if (!connection_attach(pconn, dplayer, FALSE)) {
837 /* Restore original connection. */
838 bool success = connection_attach(pconn,
839 pconn->server.delegation.playing,
840 pconn->server.delegation.observer);
841 fc_assert_ret_val(success, FALSE);
843 /* Reset all changes done above. */
844 pconn->server.delegation.status = FALSE;
845 pconn->server.delegation.playing = NULL;
846 pconn->server.delegation.observer = FALSE;
847 if (conn_controls_player(pconn)) {
848 struct player *oplayer = conn_get_player(pconn);
849 oplayer->server.orig_username[0] = '\0';
851 dplayer->server.orig_username[0] = '\0';
853 return FALSE;
856 return TRUE;
859 /*****************************************************************************
860 Restore the original status of a delegate connection pconn after potentially
861 using a delegation. pconn is detached from the delegated player, and
862 reattached to its previous view (e.g. observer), if any.
863 (Reattaching the original user to the delegated player is not handled here.)
864 *****************************************************************************/
865 bool connection_delegate_restore(struct connection *pconn)
867 struct player *dplayer;
869 if (!pconn->server.delegation.status) {
870 return FALSE;
873 if (pconn->server.delegation.playing
874 && !pconn->server.delegation.observer) {
875 /* If restoring to controlling another player, and we're not the
876 * original controller of that player, something's gone wrong. */
877 fc_assert_ret_val(
878 strcmp(pconn->server.delegation.playing->server.orig_username,
879 pconn->username) == 0, FALSE);
882 /* Save the current (delegated) player. */
883 dplayer = conn_get_player(pconn);
885 /* There should be a delegated player connected to pconn. */
886 fc_assert_ret_val(dplayer, FALSE);
888 /* Detach the current (delegate) connection from the delegated player. */
889 if (NULL != pconn->playing || pconn->observer) {
890 connection_detach(pconn, FALSE);
893 /* Try to attach to the delegate's original player */
894 if ((NULL != pconn->server.delegation.playing
895 || pconn->server.delegation.observer)
896 && !connection_attach(pconn, pconn->server.delegation.playing,
897 pconn->server.delegation.observer)) {
898 return FALSE;
901 /* Reset data. */
902 pconn->server.delegation.status = FALSE;
903 pconn->server.delegation.playing = NULL;
904 pconn->server.delegation.observer = FALSE;
905 if (conn_controls_player(pconn) && conn_get_player(pconn) != NULL) {
906 /* Remove flag that we had 'put aside' our original player. */
907 struct player *oplayer = conn_get_player(pconn);
908 fc_assert_ret_val(oplayer != dplayer, FALSE);
909 oplayer->server.orig_username[0] = '\0';
912 /* Restore the username of the original controller in the previously-
913 * delegated player. */
914 sz_strlcpy(dplayer->username, dplayer->server.orig_username);
915 dplayer->server.orig_username[0] = '\0';
916 /* Send updated username to all connections. */
917 send_player_info_c(dplayer, NULL);
919 return TRUE;
922 /*****************************************************************************
923 Close a connection. Use this in the server to take care of delegation stuff
924 (reset the username of the controlled connection).
925 *****************************************************************************/
926 void connection_close_server(struct connection *pconn, const char *reason)
928 /* Restore possible delegations before the connection is closed. */
929 connection_delegate_restore(pconn);
930 connection_close(pconn, reason);