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>
21 #include "capability.h"
50 #include "stdinhand.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
,
71 enum cmdlevel old_level
= conn_get_access(pconn
);
73 pconn
->access_level
= new_level
;
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
) {
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:
118 - players infos (note it's resent in srv_main.c::send_all_info(),
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
;
131 bool delegation_error
= FALSE
;
133 /* zero out the password */
134 memset(pconn
->server
.password
, 0, sizeof(pconn
->server
.password
));
136 /* send join_reply packet */
137 packet
.you_can_join
= TRUE
;
138 sz_strlcpy(packet
.capability
, our_capability
);
139 fc_snprintf(packet
.message
, sizeof(packet
.message
), _("%s Welcome"),
141 sz_strlcpy(packet
.challenge_file
, new_challenge_filename(pconn
));
142 packet
.conn_id
= pconn
->id
;
143 send_packet_server_join_reply(pconn
, &packet
);
145 /* "establish" the connection */
146 pconn
->established
= TRUE
;
147 pconn
->server
.status
= AS_ESTABLISHED
;
149 pconn
->server
.delegation
.status
= FALSE
;
150 pconn
->server
.delegation
.playing
= NULL
;
151 pconn
->server
.delegation
.observer
= FALSE
;
153 conn_list_append(game
.est_connections
, pconn
);
154 if (conn_list_size(game
.est_connections
) == 1) {
156 * Replace "restarting in x seconds" meta message */
157 maybe_automatic_meta_message(default_meta_message_string());
158 (void) send_server_info_to_metaserver(META_INFO
);
161 /* introduce the server to the connection */
162 if (fc_gethostname(hostname
, sizeof(hostname
)) == 0) {
163 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_any
,
164 _("Welcome to the %s Server running at %s port %d."),
165 freeciv_name_version(), hostname
, srvarg
.port
);
167 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_any
,
168 _("Welcome to the %s Server at port %d."),
169 freeciv_name_version(), srvarg
.port
);
172 /* FIXME: this (getting messages about others logging on) should be a
173 * message option for the client with event */
175 /* Notify the console that you're here. */
176 log_normal(_("%s has connected from %s."), pconn
->username
, pconn
->addr
);
178 conn_compression_freeze(pconn
);
180 send_server_setting_control(pconn
);
181 send_server_settings(dest
);
182 send_scenario_info(dest
);
183 send_game_info(dest
);
185 /* Do we have a player that a delegate is currently controlling? */
186 if ((pplayer
= player_by_user_delegated(pconn
->username
))) {
187 /* Reassert our control over the player. */
188 struct connection
*pdelegate
;
189 fc_assert_ret(player_delegation_get(pplayer
) != NULL
);
190 pdelegate
= conn_by_user(player_delegation_get(pplayer
));
192 if (pdelegate
&& connection_delegate_restore(pdelegate
)) {
193 /* Delegate now detached from our player. We will restore control
194 * over them as normal below. */
195 notify_conn(pconn
->self
, NULL
, E_CONNECTION
, ftc_server
,
196 _("Your delegate %s was controlling your player '%s'; "
197 "now detached."), pdelegate
->username
,
198 player_name(pplayer
));
199 notify_conn(pdelegate
->self
, NULL
, E_CONNECTION
, ftc_server
,
200 _("%s reconnected, ending your delegated control of "
201 "player '%s'."), pconn
->username
, player_name(pplayer
));
203 fc_assert(pdelegate
);
204 /* This really shouldn't happen. */
205 log_error("Failed to revoke delegate %s's control of %s, so owner %s "
206 "can't regain control.", pdelegate
->username
,
207 player_name(pplayer
), pconn
->username
);
208 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_server
,
209 _("Couldn't get control of '%s' from delegation to %s."),
210 player_name(pplayer
), pdelegate
->username
);
211 delegation_error
= TRUE
;
216 if (!delegation_error
) {
217 if ((pplayer
= player_by_user(pconn
->username
))
218 && connection_attach_real(pconn
, pplayer
, FALSE
, TRUE
)) {
219 /* a player has already been created for this user, reconnect */
221 if (S_S_INITIAL
== server_state()) {
222 send_player_info_c(NULL
, dest
);
225 if (!game_was_started()) {
226 if (connection_attach_real(pconn
, NULL
, FALSE
, TRUE
)) {
227 pplayer
= conn_get_player(pconn
);
228 fc_assert(pplayer
!= NULL
);
230 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_server
,
231 _("Couldn't attach your connection to new player."));
232 log_verbose("%s is not attached to a player", pconn
->username
);
235 send_player_info_c(NULL
, dest
);
239 send_conn_info(game
.est_connections
, dest
);
241 if (NULL
== pplayer
) {
242 /* Else this has already been done in connection_attach_real(). */
243 send_pending_events(pconn
, TRUE
);
244 send_running_votes(pconn
, FALSE
);
245 restore_access_level(pconn
);
246 send_conn_info(dest
, game
.est_connections
);
248 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_server
,
249 _("You are logged in as '%s' connected to no player."),
252 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_server
,
253 _("You are logged in as '%s' connected to %s."),
255 player_name(pconn
->playing
));
258 /* Send information about delegation(s). */
259 send_delegation_info(pconn
);
261 /* Notify the *other* established connections that you are connected, and
262 * add the info for all in event cache. Note we must to do it after we
263 * sent the pending events to pconn (from this function and also
264 * connection_attach()), otherwise pconn will receive it too. */
265 if (conn_controls_player(pconn
)) {
266 package_event(&connect_info
, NULL
, E_CONNECTION
, ftc_server
,
267 _("%s has connected from %s (player %s)."),
268 pconn
->username
, pconn
->addr
,
269 player_name(conn_get_player(pconn
)));
271 package_event(&connect_info
, NULL
, E_CONNECTION
, ftc_server
,
272 _("%s has connected from %s."),
273 pconn
->username
, pconn
->addr
);
275 conn_list_iterate(game
.est_connections
, aconn
) {
276 if (aconn
!= pconn
) {
277 send_packet_chat_msg(aconn
, &connect_info
);
279 } conn_list_iterate_end
;
280 event_cache_add_for_all(&connect_info
);
282 /* if need be, tell who we're waiting on to end the game.info.turn */
283 if (S_S_RUNNING
== server_state() && game
.server
.turnblock
) {
284 players_iterate_alive(cplayer
) {
285 if (!cplayer
->ai_controlled
286 && !cplayer
->phase_done
287 && cplayer
!= pconn
->playing
) { /* skip current player */
288 notify_conn(dest
, NULL
, E_CONNECTION
, ftc_any
,
289 _("Turn-blocking game play: "
290 "waiting on %s to finish turn..."),
291 player_name(cplayer
));
293 } players_iterate_alive_end
;
296 if (game
.info
.is_edit_mode
) {
297 notify_conn(dest
, NULL
, E_SETTING
, ftc_editor
,
298 _(" *** Server is in edit mode. *** "));
301 if (NULL
!= pplayer
) {
302 /* Else, no need to do anything. */
303 reset_all_start_commands();
304 (void) send_server_info_to_metaserver(META_INFO
);
306 conn_compression_thaw(pconn
);
309 /**************************************************************************
310 send the rejection packet to the client.
311 **************************************************************************/
312 void reject_new_connection(const char *msg
, struct connection
*pconn
)
314 struct packet_server_join_reply packet
;
316 /* zero out the password */
317 memset(pconn
->server
.password
, 0, sizeof(pconn
->server
.password
));
319 packet
.you_can_join
= FALSE
;
320 sz_strlcpy(packet
.capability
, our_capability
);
321 sz_strlcpy(packet
.message
, msg
);
322 packet
.challenge_file
[0] = '\0';
324 send_packet_server_join_reply(pconn
, &packet
);
325 log_normal(_("Client rejected: %s."), conn_description(pconn
));
326 flush_connection_send_buffer_all(pconn
);
329 /**************************************************************************
330 Returns FALSE if the clients gets rejected and the connection should be
331 closed. Returns TRUE if the client get accepted.
332 **************************************************************************/
333 bool handle_login_request(struct connection
*pconn
,
334 struct packet_server_join_req
*req
)
336 char msg
[MAX_LEN_MSG
];
337 int kick_time_remaining
;
339 if (pconn
->established
|| pconn
->server
.status
!= AS_NOT_ESTABLISHED
) {
340 /* We read the PACKET_SERVER_JOIN_REQ twice from this connection,
341 * this is probably not a Freeciv client. */
345 log_normal(_("Connection request from %s from %s"),
346 req
->username
, pconn
->addr
);
348 /* print server and client capabilities to console */
349 log_normal(_("%s has client version %d.%d.%d%s"),
350 pconn
->username
, req
->major_version
, req
->minor_version
,
351 req
->patch_version
, req
->version_label
);
352 log_verbose("Client caps: %s", req
->capability
);
353 log_verbose("Server caps: %s", our_capability
);
354 sz_strlcpy(pconn
->capability
, req
->capability
);
356 /* Make sure the server has every capability the client needs */
357 if (!has_capabilities(our_capability
, req
->capability
)) {
358 fc_snprintf(msg
, sizeof(msg
),
359 _("The client is missing a capability that this server needs.\n"
360 "Server version: %d.%d.%d%s Client version: %d.%d.%d%s."
361 " Upgrading may help!"),
362 MAJOR_VERSION
, MINOR_VERSION
, PATCH_VERSION
, VERSION_LABEL
,
363 req
->major_version
, req
->minor_version
,
364 req
->patch_version
, req
->version_label
);
365 reject_new_connection(msg
, pconn
);
366 log_normal(_("%s was rejected: Mismatched capabilities."),
371 /* Make sure the client has every capability the server needs */
372 if (!has_capabilities(req
->capability
, our_capability
)) {
373 fc_snprintf(msg
, sizeof(msg
),
374 _("The server is missing a capability that the client needs.\n"
375 "Server version: %d.%d.%d%s Client version: %d.%d.%d%s."
376 " Upgrading may help!"),
377 MAJOR_VERSION
, MINOR_VERSION
, PATCH_VERSION
, VERSION_LABEL
,
378 req
->major_version
, req
->minor_version
,
379 req
->patch_version
, req
->version_label
);
380 reject_new_connection(msg
, pconn
);
381 log_normal(_("%s was rejected: Mismatched capabilities."),
386 remove_leading_trailing_spaces(req
->username
);
388 /* Name-sanity check: could add more checks? */
389 if (!is_valid_username(req
->username
)) {
390 fc_snprintf(msg
, sizeof(msg
), _("Invalid username '%s'"), req
->username
);
391 reject_new_connection(msg
, pconn
);
392 log_normal(_("%s was rejected: Invalid name [%s]."),
393 req
->username
, pconn
->addr
);
397 if (conn_is_kicked(pconn
, &kick_time_remaining
)) {
398 fc_snprintf(msg
, sizeof(msg
), _("You have been kicked from this server "
399 "and cannot reconnect for %d seconds."),
400 kick_time_remaining
);
401 reject_new_connection(msg
, pconn
);
402 log_normal(_("%s was rejected: Connection kicked "
403 "(%d seconds remaining)."),
404 req
->username
, kick_time_remaining
);
408 /* don't allow duplicate logins */
409 conn_list_iterate(game
.all_connections
, aconn
) {
410 if (fc_strcasecmp(req
->username
, aconn
->username
) == 0) {
411 fc_snprintf(msg
, sizeof(msg
), _("'%s' already connected."),
413 reject_new_connection(msg
, pconn
);
414 log_normal(_("%s was rejected: Duplicate login name [%s]."),
415 req
->username
, pconn
->addr
);
418 } conn_list_iterate_end
;
420 /* Remove the ping timeout given in sernet.c:server_make_connection(). */
421 fc_assert_msg(1 == timer_list_size(pconn
->server
.ping_timers
),
422 "Ping timer list size %d, should be 1. Have we sent "
423 "a ping to unestablished connection %s?",
424 timer_list_size(pconn
->server
.ping_timers
),
425 conn_description(pconn
));
426 timer_list_pop_front(pconn
->server
.ping_timers
);
428 if (game
.server
.connectmsg
[0] != '\0') {
429 log_debug("Sending connectmsg: %s", game
.server
.connectmsg
);
430 dsend_packet_connect_msg(pconn
, game
.server
.connectmsg
);
433 if (srvarg
.auth_enabled
) {
434 return auth_user(pconn
, req
->username
);
436 sz_strlcpy(pconn
->username
, req
->username
);
437 establish_new_connection(pconn
);
442 /****************************************************************************
443 High-level server stuff when connection to client is closed or lost.
444 Reports loss to log, and to other players if the connection was a
445 player. Also removes player in pregame, applies auto_toggle, and
446 does check for turn done (since can depend on connection/ai status).
447 Note you shouldn't this function directly. You should use
448 server_break_connection() if you want to close the connection.
449 ****************************************************************************/
450 void lost_connection_to_client(struct connection
*pconn
)
452 const char *desc
= conn_description(pconn
);
454 fc_assert_ret(TRUE
== pconn
->server
.is_closing
);
456 log_normal(_("Lost connection: %s."), desc
);
458 /* Special color (white on black) for player loss */
459 notify_conn(game
.est_connections
, NULL
, E_CONNECTION
,
460 conn_controls_player(pconn
) ? ftc_player_lost
: ftc_server
,
461 _("Lost connection: %s."), desc
);
463 connection_detach(pconn
, TRUE
);
464 send_conn_info_remove(pconn
->self
, game
.est_connections
);
465 notify_if_first_access_level_is_available();
467 check_for_full_turn_done();
470 /**************************************************************************
471 Fill in packet_conn_info from full connection struct.
472 **************************************************************************/
473 static void package_conn_info(struct connection
*pconn
,
474 struct packet_conn_info
*packet
)
476 packet
->id
= pconn
->id
;
477 packet
->used
= pconn
->used
;
478 packet
->established
= pconn
->established
;
479 packet
->player_num
= (NULL
!= pconn
->playing
)
480 ? player_number(pconn
->playing
)
481 : player_slot_count();
482 packet
->observer
= pconn
->observer
;
483 packet
->access_level
= pconn
->access_level
;
485 sz_strlcpy(packet
->username
, pconn
->username
);
486 sz_strlcpy(packet
->addr
, pconn
->addr
);
487 sz_strlcpy(packet
->capability
, pconn
->capability
);
490 /**************************************************************************
491 Handle both send_conn_info() and send_conn_info_removed(), depending
492 on 'remove' arg. Sends conn_info packets for 'src' to 'dest', turning
493 off 'used' if 'remove' is specified.
494 **************************************************************************/
495 static void send_conn_info_arg(struct conn_list
*src
,
496 struct conn_list
*dest
, bool remove
)
498 struct packet_conn_info packet
;
501 dest
= game
.est_connections
;
504 conn_list_iterate(src
, psrc
) {
505 package_conn_info(psrc
, &packet
);
509 lsend_packet_conn_info(dest
, &packet
);
510 } conn_list_iterate_end
;
513 /**************************************************************************
514 Send conn_info packets to tell 'dest' connections all about
516 **************************************************************************/
517 void send_conn_info(struct conn_list
*src
, struct conn_list
*dest
)
519 send_conn_info_arg(src
, dest
, FALSE
);
522 /**************************************************************************
523 Like send_conn_info(), but turn off the 'used' bits to tell clients
524 to remove info about these connections instead of adding it.
525 **************************************************************************/
526 void send_conn_info_remove(struct conn_list
*src
, struct conn_list
*dest
)
528 send_conn_info_arg(src
, dest
, TRUE
);
531 /**************************************************************************
532 Search for first uncontrolled player
533 **************************************************************************/
534 struct player
*find_uncontrolled_player(void)
536 players_iterate(played
) {
537 if (!played
->is_connected
&& !played
->was_created
) {
540 } players_iterate_end
;
545 /****************************************************************************
546 Setup pconn as a client connected to pplayer or observer:
547 Updates pconn->playing, pplayer->connections, pplayer->is_connected
550 - If pplayer is NULL and observing is FALSE: take the next available
551 player that is not connected.
552 - If pplayer is NULL and observing is TRUE: attach this connection to
553 the game as global observer.
554 - If pplayer is not NULL and observing is FALSE: take this player.
555 - If pplayer is not NULL and observing is TRUE: observe this player.
557 Note take_command() needs to know if this function will success before
558 it's time to call this. Keep take_command() checks in sync when
560 ****************************************************************************/
561 static bool connection_attach_real(struct connection
*pconn
,
562 struct player
*pplayer
,
563 bool observing
, bool connecting
)
565 fc_assert_ret_val(pconn
!= NULL
, FALSE
);
566 fc_assert_ret_val_msg(!pconn
->observer
&& pconn
->playing
== NULL
, FALSE
,
567 "connections must be detached with "
568 "connection_detach() before calling this!");
571 if (NULL
== pplayer
) {
572 /* search for uncontrolled player */
573 pplayer
= find_uncontrolled_player();
575 if (NULL
== pplayer
) {
576 /* no uncontrolled player found */
577 if (player_count() >= game
.server
.max_players
578 || normal_player_count() >= server
.playable_nations
) {
581 /* add new player, or not */
582 /* Should only be called in such a way as to create a new player
584 fc_assert_ret_val(!game_was_started(), FALSE
);
585 pplayer
= server_create_player(-1, default_ai_type_name(), NULL
);
586 /* Pregame => no need to assign_player_colors() */
591 team_remove_player(pplayer
);
593 server_player_init(pplayer
, FALSE
, TRUE
);
596 pplayer
->ai_controlled
= FALSE
;
599 sz_strlcpy(pplayer
->username
, pconn
->username
);
600 pplayer
->user_turns
= 0; /* reset for a new user */
601 pplayer
->is_connected
= TRUE
;
603 if (!game_was_started()) {
604 if (!pplayer
->was_created
&& NULL
== pplayer
->nation
) {
605 /* Temporarily set player_name() to username. */
606 server_player_set_name(pplayer
, pconn
->username
);
608 aifill(game
.info
.aifill
);
611 if (game
.server
.auto_ai_toggle
&& pplayer
->ai_controlled
) {
612 toggle_ai_player_direct(NULL
, pplayer
);
615 send_player_info_c(pplayer
, game
.est_connections
);
618 /* We don't want the connection's username on another player. */
619 players_iterate(aplayer
) {
620 if (aplayer
!= pplayer
621 && 0 == strncmp(aplayer
->username
, pconn
->username
, MAX_LEN_NAME
)) {
622 sz_strlcpy(aplayer
->username
, ANON_USER_NAME
);
623 send_player_info_c(aplayer
, NULL
);
625 } players_iterate_end
;
627 pconn
->observer
= observing
;
628 pconn
->playing
= pplayer
;
630 conn_list_append(pplayer
->connections
, pconn
);
633 restore_access_level(pconn
);
635 /* Reset the delta-state. */
636 send_conn_info(pconn
->self
, game
.est_connections
); /* Client side. */
637 conn_reset_delta_state(pconn
); /* Server side. */
639 /* Initial packets don't need to be resent. See comment for
640 * connecthand.c::establish_new_connection(). */
641 switch (server_state()) {
643 send_pending_events(pconn
, connecting
);
644 send_running_votes(pconn
, !connecting
);
648 conn_compression_freeze(pconn
);
649 send_all_info(pconn
->self
);
650 if (game
.info
.is_edit_mode
&& can_conn_edit(pconn
)) {
651 edithand_send_initial_packets(pconn
->self
);
653 conn_compression_thaw(pconn
);
654 /* Enter C_S_RUNNING client state. */
655 dsend_packet_start_phase(pconn
, game
.info
.phase
);
656 /* Must be after C_S_RUNNING client state to be effective. */
657 send_diplomatic_meetings(pconn
);
658 send_pending_events(pconn
, connecting
);
659 send_running_votes(pconn
, !connecting
);
663 conn_compression_freeze(pconn
);
664 send_all_info(pconn
->self
);
665 if (game
.info
.is_edit_mode
&& can_conn_edit(pconn
)) {
666 edithand_send_initial_packets(pconn
->self
);
668 conn_compression_thaw(pconn
);
669 report_final_scores(pconn
->self
);
670 send_pending_events(pconn
, connecting
);
671 send_running_votes(pconn
, !connecting
);
673 /* Send information about delegation(s). */
674 send_delegation_info(pconn
);
679 send_updated_vote_totals(NULL
);
684 /****************************************************************************
685 Setup pconn as a client connected to pplayer or observer.
686 ****************************************************************************/
687 bool connection_attach(struct connection
*pconn
, struct player
*pplayer
,
690 return connection_attach_real(pconn
, pplayer
, observing
, FALSE
);
693 /****************************************************************************
694 Remove pconn as a client connected to pplayer:
695 Updates pconn->playing, pconn->playing->connections,
696 pconn->playing->is_connected and pconn->observer.
698 pconn remains a member of game.est_connections.
700 If remove_unused_player is TRUE, may remove a player left with no
701 controlling connection (only in pregame, and not if explicitly /created).
702 ****************************************************************************/
703 void connection_detach(struct connection
*pconn
, bool remove_unused_player
)
705 struct player
*pplayer
;
707 fc_assert_ret(pconn
!= NULL
);
709 if (NULL
!= (pplayer
= pconn
->playing
)) {
710 bool was_connected
= pplayer
->is_connected
;
712 send_remove_team_votes(pconn
);
713 conn_list_remove(pplayer
->connections
, pconn
);
714 pconn
->playing
= NULL
;
715 pconn
->observer
= FALSE
;
716 restore_access_level(pconn
);
717 cancel_connection_votes(pconn
);
718 send_updated_vote_totals(NULL
);
719 send_conn_info(pconn
->self
, game
.est_connections
);
721 /* If any other (non-observing) conn is attached to this player, the
722 * player is still connected. */
723 pplayer
->is_connected
= FALSE
;
724 conn_list_iterate(pplayer
->connections
, aconn
) {
725 if (!aconn
->observer
) {
726 pplayer
->is_connected
= TRUE
;
729 } conn_list_iterate_end
;
731 if (was_connected
&& !pplayer
->is_connected
) {
732 /* Player just lost its controlling connection. */
733 if (remove_unused_player
&&
734 !pplayer
->was_created
&& !game_was_started()) {
736 conn_list_iterate(pplayer
->connections
, aconn
) {
738 fc_assert_action(aconn
!= pconn
, continue);
739 notify_conn(aconn
->self
, NULL
, E_CONNECTION
, ftc_server
,
740 _("Detaching from %s."), player_name(pplayer
));
741 /* Recursive... but shouldn't be a problem, as this can only
742 * be a non-controlling connection so can't get back here. */
743 connection_detach(aconn
, TRUE
);
744 } conn_list_iterate_end
;
746 /* Actually do the removal. */
747 server_remove_player(pplayer
);
748 aifill(game
.info
.aifill
);
749 reset_all_start_commands();
751 /* Aitoggle the player if no longer connected. */
752 if (game
.server
.auto_ai_toggle
&& !pplayer
->ai_controlled
) {
753 toggle_ai_player_direct(NULL
, pplayer
);
754 /* send_player_info_c() was formerly updated by
755 * toggle_ai_player_direct(), so it must be safe to send here now?
757 * At other times, data from send_conn_info() is used by the
758 * client to display player information.
759 * See establish_new_connection().
761 log_verbose("connection_detach() calls send_player_info_c()");
762 send_player_info_c(pplayer
, NULL
);
764 reset_all_start_commands();
769 pconn
->observer
= FALSE
;
770 restore_access_level(pconn
);
771 send_conn_info(pconn
->self
, game
.est_connections
);
775 /*****************************************************************************
776 Use a delegation to get control over another player.
777 *****************************************************************************/
778 bool connection_delegate_take(struct connection
*pconn
,
779 struct player
*dplayer
)
781 fc_assert_ret_val(pconn
->server
.delegation
.status
== FALSE
, FALSE
);
783 /* Save the original player of this connection and the original username of
785 pconn
->server
.delegation
.status
= TRUE
;
786 pconn
->server
.delegation
.playing
= conn_get_player(pconn
);
787 pconn
->server
.delegation
.observer
= pconn
->observer
;
788 if (conn_controls_player(pconn
)) {
789 /* Setting orig_username in the player we're about to put aside is
790 * a flag that no-one should be allowed to mess with it (e.g. /take). */
791 struct player
*oplayer
= conn_get_player(pconn
);
792 fc_assert_ret_val(oplayer
!= dplayer
, FALSE
);
793 fc_assert_ret_val(strlen(oplayer
->server
.orig_username
) == 0, FALSE
);
794 sz_strlcpy(oplayer
->server
.orig_username
, oplayer
->username
);
796 fc_assert_ret_val(strlen(dplayer
->server
.orig_username
) == 0, FALSE
);
797 sz_strlcpy(dplayer
->server
.orig_username
, dplayer
->username
);
799 /* Detach the current connection. */
800 if (NULL
!= pconn
->playing
|| pconn
->observer
) {
801 connection_detach(pconn
, FALSE
);
804 /* Try to attach to the new player */
805 if (!connection_attach(pconn
, dplayer
, FALSE
)) {
807 /* Restore original connection. */
808 bool success
= connection_attach(pconn
,
809 pconn
->server
.delegation
.playing
,
810 pconn
->server
.delegation
.observer
);
811 fc_assert_ret_val(success
, FALSE
);
813 /* Reset all changes done above. */
814 pconn
->server
.delegation
.status
= FALSE
;
815 pconn
->server
.delegation
.playing
= NULL
;
816 pconn
->server
.delegation
.observer
= FALSE
;
817 if (conn_controls_player(pconn
)) {
818 struct player
*oplayer
= conn_get_player(pconn
);
819 oplayer
->server
.orig_username
[0] = '\0';
821 dplayer
->server
.orig_username
[0] = '\0';
829 /*****************************************************************************
830 Restore the original status of a delegate connection pconn after potentially
831 using a delegation. pconn is detached from the delegated player, and
832 reattached to its previous view (e.g. observer), if any.
833 (Reattaching the original user to the delegated player is not handled here.)
834 *****************************************************************************/
835 bool connection_delegate_restore(struct connection
*pconn
)
837 struct player
*dplayer
;
839 if (!pconn
->server
.delegation
.status
) {
843 if (pconn
->server
.delegation
.playing
844 && !pconn
->server
.delegation
.observer
) {
845 /* If restoring to controlling another player, and we're not the
846 * original controller of that player, something's gone wrong. */
848 strcmp(pconn
->server
.delegation
.playing
->server
.orig_username
,
849 pconn
->username
) == 0, FALSE
);
852 /* Save the current (delegated) player. */
853 dplayer
= conn_get_player(pconn
);
855 /* There should be a delegated player connected to pconn. */
856 fc_assert_ret_val(dplayer
, FALSE
);
858 /* Detach the current (delegate) connection from the delegated player. */
859 if (NULL
!= pconn
->playing
|| pconn
->observer
) {
860 connection_detach(pconn
, FALSE
);
863 /* Try to attach to the delegate's original player */
864 if ((NULL
!= pconn
->server
.delegation
.playing
865 || pconn
->server
.delegation
.observer
)
866 && !connection_attach(pconn
, pconn
->server
.delegation
.playing
,
867 pconn
->server
.delegation
.observer
)) {
872 pconn
->server
.delegation
.status
= FALSE
;
873 pconn
->server
.delegation
.playing
= NULL
;
874 pconn
->server
.delegation
.observer
= FALSE
;
875 if (conn_controls_player(pconn
) && conn_get_player(pconn
) != NULL
) {
876 /* Remove flag that we had 'put aside' our original player. */
877 struct player
*oplayer
= conn_get_player(pconn
);
878 fc_assert_ret_val(oplayer
!= dplayer
, FALSE
);
879 oplayer
->server
.orig_username
[0] = '\0';
882 /* Restore the username of the original controller in the previously-
883 * delegated player. */
884 sz_strlcpy(dplayer
->username
, dplayer
->server
.orig_username
);
885 dplayer
->server
.orig_username
[0] = '\0';
886 /* Send updated username to all connections. */
887 send_player_info_c(dplayer
, NULL
);
892 /*****************************************************************************
893 Close a connection. Use this in the server to take care of delegation stuff
894 (reset the username of the controlled connection).
895 *****************************************************************************/
896 void connection_close_server(struct connection
*pconn
, const char *reason
)
898 /* Restore possible delegations before the connection is closed. */
899 connection_delegate_restore(pconn
);
900 connection_close(pconn
, reason
);