[Freeciv-Dev] (PR#4746) new commands /observe and /detach and connection
[Top] [All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: |
undisclosed-recipients: ; |
Subject: |
[Freeciv-Dev] (PR#4746) new commands /observe and /detach and connection behaviour |
From: |
"Mike Kaufman" <kaufman@xxxxxxxxxxxxxxxxxxxxxx> |
Date: |
Sun, 3 Aug 2003 12:49:39 -0700 |
Reply-to: |
rt@xxxxxxxxxxxxxx |
this patch brings the connection code closer to completion.
we introduce /observe and /detach. We also change the behavior of /take.
The bug in duplicate player names and teams has been fixed too.
NOTE: connection behavior has been changed! so pay attention.
The main behavior here is that there is now a difference (in pregame)
between players created by /create and players automatically created
by people logging in. /created players will NOT be removed automatically
when !pplayer->is_connected = FALSE. Players created by login will.
Also, automatically created players will get their player names from the
username of the connection.
/detach detaches a connection from a player (either controlling or
observing). If the player was an "automatic" player, any observers are
detached and that player is removed. autotoggle rules apply for /created
players.
/observe observes a player. If the connection is already attached to a
player, he will be detached first. detaching rules apply.
The patch does reveal a bug which I haven't fixed. Perhaps the
culprit[s] should fix them first, eh?
when attaching to an AI player, I get this little gem after
/start:
0: tech "None": requires itself
and bang! Interestingly enough, observing humans doesn't have this problem.
kinda makes observing less useful certainly.
-mike
diff -Nur -Xsnap/diff_ignore snap/common/capstr.c snap-obs/common/capstr.c
--- snap/common/capstr.c 2003-07-25 23:21:13.000000000 -0500
+++ snap-obs/common/capstr.c 2003-08-03 12:32:04.000000000 -0500
@@ -79,7 +79,7 @@
"+impr_req +waste +fastfocus +continent +small_dipl " \
"+no_nation_selected +diplomacy +no_extra_tiles " \
"+diplomacy2 +citizens_style +root_tech auth " \
- "+nat_ulimit retake +goto_pack borders"
+ "+nat_ulimit +retake +goto_pack borders"
/* "+1.14.0" is protocol for 1.14.0 release.
*
diff -Nur -Xsnap/diff_ignore snap/common/player.c snap-obs/common/player.c
--- snap/common/player.c 2003-07-04 06:20:45.000000000 -0500
+++ snap-obs/common/player.c 2003-08-03 11:57:24.000000000 -0500
@@ -101,6 +101,7 @@
conn_list_init(&plr->connections);
plr->current_conn = NULL;
plr->is_connected = FALSE;
+ plr->was_created = FALSE;
plr->is_alive=TRUE;
plr->embassy=0;
plr->reputation=GAME_DEFAULT_REPUTATION;
diff -Nur -Xsnap/diff_ignore snap/common/player.h snap-obs/common/player.h
--- snap/common/player.h 2003-06-18 18:00:37.000000000 -0500
+++ snap-obs/common/player.h 2003-08-03 11:57:02.000000000 -0500
@@ -195,6 +195,7 @@
struct player_spaceship spaceship;
int future_tech;
struct player_ai ai;
+ bool was_created; /* if the player was /created */
bool is_connected; /* observers don't count */
struct connection *current_conn; /* non-null while handling packet */
struct conn_list connections; /* will replace conn */
diff -Nur -Xsnap/diff_ignore snap/server/connecthand.c
snap-obs/server/connecthand.c
--- snap/server/connecthand.c 2003-07-25 23:21:28.000000000 -0500
+++ snap-obs/server/connecthand.c 2003-08-03 13:12:53.000000000 -0500
@@ -141,6 +141,8 @@
if (!attach_connection_to_player(pconn, NULL)) {
notify_conn(dest, _("Couldn't attach your connection to new player."));
freelog(LOG_VERBOSE, "%s is not attached to a player", pconn->username);
+ } else {
+ sz_strlcpy(pconn->player->name, pconn->username);
}
}
diff -Nur -Xsnap/diff_ignore snap/server/stdinhand.c snap-obs/server/stdinhand.c
--- snap/server/stdinhand.c 2003-07-25 23:21:29.000000000 -0500
+++ snap-obs/server/stdinhand.c 2003-08-03 14:37:08.000000000 -0500
@@ -75,7 +75,9 @@
static void set_away(struct connection *caller, char *name);
static void fix_command(struct connection *caller, char *str, int cmd_enum);
+static void observe_command(struct connection *caller, char *name);
static void take_command(struct connection *caller, char *name);
+static void detach_command(struct connection *caller, char *name);
static const char horiz_line[] =
"------------------------------------------------------------------------------";
@@ -981,6 +983,8 @@
CMD_METASERVER,
CMD_AITOGGLE,
CMD_TAKE,
+ CMD_OBSERVE,
+ CMD_DETACH,
CMD_CREATE,
CMD_AWAY,
CMD_NOVICE,
@@ -1160,6 +1164,21 @@
"other connections to take over a player. If you're not one of these, "
"only the <player-name> argument is allowed")
},
+ {"observe", ALLOW_INFO,
+ /* TRANS: translate text between [] and <> only */
+ N_("observe [connection-name] <player-name>"),
+ N_("Observe a player."),
+ N_("Only the console and connections with cmdlevel 'hack' can force "
+ "other connections to observe a player. If you're not one of these, "
+ "only the <player-name> argument is allowed")
+ },
+ {"detach", ALLOW_INFO,
+ /* TRANS: translate text between <> only */
+ N_("detach <connection-name>"),
+ N_("detach from a player."),
+ N_("Only the console and connections with cmdlevel 'hack' can force "
+ "other connections to detach from a player.")
+ },
{"create", ALLOW_CTRL,
/* TRANS: translate text between <> only */
N_("create <player-name>"),
@@ -1856,7 +1875,8 @@
pplayer = &game.players[game.nplayers];
server_player_init(pplayer, FALSE);
sz_strlcpy(pplayer->name, arg);
- sz_strlcpy(pplayer->username, "Unassigned");
+ sz_strlcpy(pplayer->username, ANON_USER_NAME);
+ pplayer->was_created = TRUE; /* must use /remove explicitly to remove */
game.nplayers++;
@@ -2789,7 +2809,7 @@
}
/******************************************************************
- ...
+...
******************************************************************/
static void team_command(struct connection *caller, char *str)
{
@@ -3068,26 +3088,166 @@
}
/**************************************************************************
- take over a player. If a connection already has control of that player,
- disallow unless the connection can multiconnect to the player
- (not implemented yet).
-
- if there are two arguments, treat the first as the connection name and the
- second as the player name. (only hack and the console can do this)
- otherwise, there should be one argument, that being the player that the
+ Observe another player. If we were already attached, detach
+ (see detach_command()). The console and those with ALLOW_HACK can
+ use the two-argument command and force others to observe.
+**************************************************************************/
+static void observe_command(struct connection *caller, char *str)
+{
+ int i = 0, ntokens = 0;
+ char buf[MAX_LEN_CONSOLE_LINE], *arg[2];
+ bool is_newgame = (server_state == PRE_GAME_STATE ||
+ server_state == SELECT_RACES_STATE) && game.is_new_game;
+
+ enum m_pre_result result;
+ struct connection *pconn = NULL;
+ struct player *pplayer = NULL;
+
+ /******** PART I: fill pconn and pplayer ********/
+
+ sz_strlcpy(buf, str);
+ ntokens = get_tokens(buf, arg, 2, TOKEN_DELIMITERS);
+
+ /* check syntax, only certain syntax if allowed depending on the caller */
+ if (!caller && ntokens < 1) {
+ cmd_reply(CMD_OBSERVE, caller, C_SYNTAX,
+ _("Usage: observe [connection-name [player-name]]"));
+ goto end;
+ }
+
+ if (ntokens == 2 && (caller && caller->access_level != ALLOW_HACK)) {
+ cmd_reply(CMD_OBSERVE, caller, C_SYNTAX,
+ _("Usage: observe [player-name]"));
+ goto end;
+ }
+
+#define NO_GLOBAL_OBS /* remove this construction when global obs is reality */
+
+#ifdef NO_GLOBAL_OBS
+ if ((caller && ntokens == 0) || (!caller && ntokens == 1)) {
+ cmd_reply(CMD_OBSERVE, caller, C_SYNTAX,
+ _("Usage: observe [connection-name [player-name]]"));
+ goto end;
+ }
+#endif
+
+ /* match connection if we're console, match a player if we're not */
+ if (ntokens == 1) {
+ if (!caller && !(pconn = find_conn_by_user_prefix(arg[0], &result))) {
+ cmd_reply_no_such_conn(CMD_OBSERVE, caller, arg[0], result);
+ goto end;
+ } else if (caller
+ && !(pplayer = find_player_by_name_prefix(arg[0], &result))) {
+ cmd_reply_no_such_player(CMD_OBSERVE, caller, arg[0], result);
+ goto end;
+ }
+ }
+
+ /* get connection name then player name */
+ if (ntokens == 2) {
+ if (!(pconn = find_conn_by_user_prefix(arg[0], &result))) {
+ cmd_reply_no_such_conn(CMD_OBSERVE, caller, arg[0], result);
+ goto end;
+ }
+ if (!(pplayer = find_player_by_name_prefix(arg[1], &result))) {
+ cmd_reply_no_such_player(CMD_OBSERVE, caller, arg[1], result);
+ goto end;
+ }
+ }
+
+ /* if we can't force other connections to observe, assign us to be pconn. */
+ if (!pconn) {
+ pconn = caller;
+ }
+
+#ifndef NO_GLOBAL_OBS
+ /* global observing needs some code here! */
+#endif
+
+ /******** PART II: do the observing ********/
+
+ /* observing your own player (during pregame) makes no sense. */
+ if (pconn->player == pplayer && !pconn->observer
+ && is_newgame && !pplayer->was_created) {
+ cmd_reply(CMD_OBSERVE, caller, C_FAIL,
+ _("%s already controls %s. Using 'observe' would remove %s"),
+ pconn->username, pplayer->name, pplayer->name);
+ goto end;
+ }
+
+ /* if we want to switch players, reset the client */
+ if (pconn->player && server_state == RUN_GAME_STATE) {
+ send_game_state(&pconn->self, CLIENT_PRE_GAME_STATE);
+ }
+
+ /* if the connection is already attached to a player,
+ * unattach and cleanup old player (rename, remove, etc) */
+ if (pconn->player) {
+ char name[MAX_LEN_NAME];
+
+ /* if a pconn->player is removed, we'll lose pplayer */
+ sz_strlcpy(name, pplayer->name);
+
+ detach_command(NULL, pconn->username);
+
+ /* find pplayer again, the pointer might have been changed */
+ pplayer = find_player_by_name(name);
+ }
+
+ /* we don't want the connection's username on another player */
+ players_iterate(aplayer) {
+ if (strncmp(aplayer->username, pconn->username, MAX_LEN_NAME) == 0) {
+ sz_strlcpy(aplayer->username, ANON_USER_NAME);
+ }
+ } players_iterate_end;
+
+ /* attach pconn to new player as an observer */
+ pconn->observer = TRUE; /* do this before attach! */
+ attach_connection_to_player(pconn, pplayer);
+
+ if (server_state == RUN_GAME_STATE) {
+ send_packet_generic_empty(pconn, PACKET_FREEZE_HINT);
+ send_rulesets(&pconn->self);
+ send_all_info(&pconn->self);
+ send_game_state(&pconn->self, CLIENT_GAME_RUNNING_STATE);
+ send_player_info(NULL, NULL);
+ send_diplomatic_meetings(pconn);
+ send_packet_generic_empty(pconn, PACKET_THAW_HINT);
+ send_packet_generic_empty(pconn, PACKET_START_TURN);
+ }
+
+ cmd_reply(CMD_OBSERVE, caller, C_OK, _("%s now observes %s"),
+ pconn->username, pplayer->name);
+
+ end:;
+ /* free our args */
+ for (i = 0; i < ntokens; i++) {
+ free(arg[i]);
+ }
+}
+
+/**************************************************************************
+ Take over a player. If a connection already has control of that player,
+ disallow it.
+
+ If there are two arguments, treat the first as the connection name and the
+ second as the player name (only hack and the console can do this).
+ Otherwise, there should be one argument, that being the player that the
caller wants to take.
**************************************************************************/
static void take_command(struct connection *caller, char *str)
{
int i = 0, ntokens = 0;
- char buf[MAX_LEN_CONSOLE_LINE];
- char *arg[2];
- bool was_connected;
+ char buf[MAX_LEN_CONSOLE_LINE], *arg[2];
+ bool is_newgame = (server_state == PRE_GAME_STATE ||
+ server_state == SELECT_RACES_STATE) && game.is_new_game;
enum m_pre_result match_result;
struct connection *pconn = NULL;
struct player *pplayer = NULL;
+ /******** PART I: fill pconn and pplayer ********/
+
sz_strlcpy(buf, str);
ntokens = get_tokens(buf, arg, 2, TOKEN_DELIMITERS);
@@ -3129,78 +3289,65 @@
pconn = caller;
}
- if (pconn->player == pplayer) {
- cmd_reply(CMD_TAKE, caller, C_FAIL,
- _("%s already controls or observes %s"),
+ /******** PART II: do the attaching ********/
+
+ /* taking your own player makes no sense. */
+ if (pconn->player == pplayer && !pconn->observer) {
+ cmd_reply(CMD_TAKE, caller, C_FAIL, _("%s already controls %s"),
pconn->username, pplayer->name);
goto end;
}
- was_connected = pplayer->is_connected;
+ /* if we want to switch players, reset the client if the game is running */
+ if (pconn->player && server_state == RUN_GAME_STATE) {
+ send_game_state(&pconn->self, CLIENT_PRE_GAME_STATE);
+ }
- /* FIXME: remove when multiconnect becomes mature */
- conn_list_iterate(pplayer->connections, aconn) {
- cmd_reply(CMD_TAKE, caller, C_FAIL,
- _("%s already controls or observes %s.\n"
- " multiple controlling connections aren't allowed yet."),
+ /* if we're taking another player with a user attached
+ * and we have the power, forcibly detach the user from the player. */
+ if (!caller || caller->access_level == ALLOW_HACK) {
+ conn_list_iterate(pplayer->connections, aconn) {
+ if (!aconn->observer) {
+ if (server_state == RUN_GAME_STATE) {
+ send_game_state(&aconn->self, CLIENT_PRE_GAME_STATE);\
+ }
+ notify_conn(&aconn->self, _("being detached from %s."), pplayer->name);
+ unattach_connection_from_player(aconn);
+ }
+ } conn_list_iterate_end;
+ } else {
+ cmd_reply(CMD_TAKE, caller, C_FAIL, _("%s already controls %s"),
pplayer->username, pplayer->name);
goto end;
- } conn_list_iterate_end;
-
- /* if we want to switch players, reset the client */
- if (pconn->player && server_state == RUN_GAME_STATE) {
- if (has_capability("retake", pconn->capability)) {
- send_game_state(&pconn->self, CLIENT_PRE_GAME_STATE);
- } else {
- cmd_reply(CMD_TAKE, caller, C_FAIL,
- _("You can't switch players with \"take\" "
- "after the game has started."));
- goto end;
- }
}
/* if the connection is already attached to a player,
* unattach and cleanup old player (rename, remove, etc) */
if (pconn->player) {
- struct player *old_plr = pconn->player;
+ char name[MAX_LEN_NAME];
- /* FIXME: need to reset the client somehow, if this
- * happens while the game is running */
+ /* if a pconn->player is removed, we'll lose pplayer */
+ sz_strlcpy(name, pplayer->name);
- unattach_connection_from_player(pconn);
+ detach_command(NULL, pconn->username);
- /* aitoggle the player if necessary */
- if (game.auto_ai_toggle && !old_plr->ai.control
- && !old_plr->is_connected) {
- toggle_ai_player_direct(NULL, old_plr);
- }
-
- /* need to reattach before we possibly remove the old player */
- attach_connection_to_player(pconn, pplayer);
-
- cmd_reply(CMD_TAKE, caller, C_COMMENT,
- _("%s detaching from %s"), pconn->username, old_plr->name);
-
- /* only remove the player if the game is new and in pregame, nobody
- * is connected to it anymore and it wasn't AI-controlled. */
- if (!old_plr->is_connected && game.is_new_game && !old_plr->ai.control
- && (server_state == PRE_GAME_STATE
- || server_state == SELECT_RACES_STATE)) {
-
- /* debugging, should we keep this around? */
- cmd_reply(CMD_TAKE, caller, C_COMMENT,
- _("removing %s [%d]"), old_plr->name, old_plr->player_no);
-
- game_remove_player(old_plr);
- game_renumber_players(old_plr->player_no);
+ /* find pplayer again, the pointer might have been changed */
+ pplayer = find_player_by_name(name);
+ }
- /* we screwed up the pplayer pointer by removing old_plr; get it back */
- pplayer = pconn->player;
- } else {
- sz_strlcpy(old_plr->username, ANON_USER_NAME);
+ /* we don't want the connection's username on another player */
+ players_iterate(aplayer) {
+ if (strncmp(aplayer->username, pconn->username, MAX_LEN_NAME) == 0) {
+ sz_strlcpy(aplayer->username, ANON_USER_NAME);
}
- } else {
- attach_connection_to_player(pconn, pplayer);
+ } players_iterate_end;
+
+ /* now attach to new player */
+ attach_connection_to_player(pconn, pplayer);
+
+ /* if pplayer wasn't /created, and we're still in pregame, change its name */
+ if (!pplayer->was_created && is_newgame) {
+ sz_strlcpy(pplayer->name, pconn->username);
}
if (server_state == RUN_GAME_STATE) {
@@ -3214,13 +3361,12 @@
send_packet_generic_empty(pconn, PACKET_START_TURN);
}
- /* if the player we're taking was already connected, then the primary
- * controller set that player to ai control. don't undo that decision */
- if (!was_connected && pplayer->ai.control && game.auto_ai_toggle) {
+ /* aitoggle the player back to human if necessary. */
+ if (pplayer->ai.control && game.auto_ai_toggle) {
toggle_ai_player_direct(NULL, pplayer);
}
- cmd_reply(CMD_TAKE, caller, C_OK, _("%s now controls %s"),
+ cmd_reply(CMD_TAKE, caller, C_OK, _("%s now controls %s"),
pconn->username, pplayer->name);
end:;
@@ -3231,6 +3377,103 @@
}
/**************************************************************************
+ Detach from a player. if that player wasn't /created and you were
+ controlling the player, remove it (and then detach any observers as well).
+**************************************************************************/
+static void detach_command(struct connection *caller, char *str)
+{
+ int i = 0, ntokens = 0;
+ char buf[MAX_LEN_CONSOLE_LINE], *arg[1];
+
+ enum m_pre_result match_result;
+ struct connection *pconn = NULL;
+ struct player *pplayer = NULL;
+ bool is_newgame = (server_state == PRE_GAME_STATE ||
+ server_state == SELECT_RACES_STATE) && game.is_new_game;
+
+ sz_strlcpy(buf, str);
+ ntokens = get_tokens(buf, arg, 1, TOKEN_DELIMITERS);
+
+ if (!caller && ntokens == 0) {
+ cmd_reply(CMD_DETACH, caller, C_SYNTAX,
+ _("Usage: detach <connection-name>"));
+ goto end;
+ }
+
+ /* match the connection if the argument was given */
+ if (ntokens == 1
+ && !(pconn = find_conn_by_user_prefix(arg[0], &match_result))) {
+ cmd_reply_no_such_conn(CMD_DETACH, caller, arg[0], match_result);
+ goto end;
+ }
+
+ /* if no argument is given, the caller wants to detach himself */
+ if (!pconn) {
+ pconn = caller;
+ }
+
+ /* if pconn and caller are not the same, only continue
+ * if we're console, or we have ALLOW_HACK */
+ if (pconn != caller && caller && caller->access_level != ALLOW_HACK) {
+ cmd_reply(CMD_DETACH, caller, C_FAIL,
+ _("You can not detach other users."));
+ goto end;
+ }
+
+ pplayer = pconn->player;
+
+ /* must have someone to detach from... */
+ if (!pplayer) {
+ cmd_reply(CMD_DETACH, caller, C_FAIL,
+ _("%s is not attached to any player."), pconn->username);
+ goto end;
+ }
+
+ /* if we want to detach while the game is running, reset the client */
+ if (server_state == RUN_GAME_STATE) {
+ send_game_state(&pconn->self, CLIENT_PRE_GAME_STATE);
+ }
+
+ /* actually do the detaching */
+ unattach_connection_from_player(pconn);
+ cmd_reply(CMD_DETACH, caller, C_COMMENT,
+ _("%s detaching from %s"), pconn->username, pplayer->name);
+
+ /* only remove the player if the game is new and in pregame,
+ * the player wasn't /created, and no one is controlling it */
+ if (!pplayer->is_connected && !pplayer->was_created && is_newgame) {
+ /* detach any observers */
+ conn_list_iterate(pplayer->connections, aconn) {
+ if (aconn->observer) {
+ unattach_connection_from_player(aconn);
+ notify_conn(&aconn->self, _("detaching from %s."), pplayer->name);
+ }
+ } conn_list_iterate_end;
+
+ /* actually do the removal */
+ game_remove_player(pplayer);
+ game_renumber_players(pplayer->player_no);
+ }
+
+ if (!pplayer->is_connected) {
+ /* aitoggle the player if no longer connected. */
+ if (game.auto_ai_toggle && !pplayer->ai.control) {
+ toggle_ai_player_direct(NULL, pplayer);
+ }
+ /* reset username if in pregame. */
+ if (is_newgame) {
+ sz_strlcpy(pplayer->username, ANON_USER_NAME);
+ }
+ }
+
+ end:;
+ /* free our args */
+ for (i = 0; i < ntokens; i++) {
+ free(arg[i]);
+ }
+}
+
+/**************************************************************************
...
**************************************************************************/
void load_command(struct connection *caller, char *arg)
@@ -3480,6 +3723,12 @@
case CMD_TAKE:
take_command(caller, arg);
break;
+ case CMD_OBSERVE:
+ observe_command(caller, arg);
+ break;
+ case CMD_DETACH:
+ detach_command(caller, arg);
+ break;
case CMD_CREATE:
create_ai_player(caller,arg);
break;
- [Freeciv-Dev] (PR#4746) new commands /observe and /detach and connection behaviour,
Mike Kaufman <=
|
|