/********************************************************************** Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ***********************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_LIBREADLINE #include #ifdef HAVE_NEWLIBREADLINE #define completion_matches(x,y) rl_completion_matches(x,y) #define filename_completion_function rl_filename_completion_function #endif #endif #include "astring.h" #include "fciconv.h" #include "fcintl.h" #include "log.h" #include "mem.h" #include "registry.h" #include "shared.h" /* fc__attribute, bool type, etc. */ #include "support.h" #include "timing.h" #include "capability.h" #include "events.h" #include "game.h" #include "map.h" #include "packets.h" #include "player.h" #include "unitlist.h" #include "version.h" #include "citytools.h" #include "commands.h" #include "connecthand.h" #include "console.h" #include "diplhand.h" #include "gamehand.h" #include "ggzserver.h" #include "mapgen.h" #include "maphand.h" #include "meta.h" #include "plrhand.h" #include "report.h" #include "ruleset.h" #include "sanitycheck.h" #include "savegame.h" #include "score.h" #include "sernet.h" #include "settings.h" #include "srv_main.h" #include "stdinhand.h" #include "advmilitary.h" /* assess_danger_player() */ #include "ailog.h" #define TOKEN_DELIMITERS " \t\n," static enum cmdlevel_id default_access_level = ALLOW_INFO; static enum cmdlevel_id first_access_level = ALLOW_CTRL; static bool cut_client_connection(struct connection *caller, char *name, bool check); static bool show_help(struct connection *caller, char *arg); static bool show_list(struct connection *caller, char *arg); static void show_teams(struct connection *caller); static void show_connections(struct connection *caller); static void show_scenarios(struct connection *caller); static bool set_ai_level(struct connection *caller, char *name, int level, bool check); static bool set_away(struct connection *caller, char *name, bool check); static bool observe_command(struct connection *caller, char *name, bool check); static bool take_command(struct connection *caller, char *name, bool check); static bool detach_command(struct connection *caller, char *name, bool check); static bool start_command(struct connection *caller, char *name, bool check); static bool end_command(struct connection *caller, char *str, bool check); static bool surrender_command(struct connection *caller, char *str, bool check); enum vote_type { VOTE_NONE, VOTE_YES, VOTE_NO }; struct voting { char command[MAX_LEN_CONSOLE_LINE]; /* [0] == \0 if none in action */ enum vote_type votes_cast[MAX_NUM_PLAYERS]; /* see enum above */ int vote_no; /* place in the queue */ int yes, no; }; static struct voting votes[MAX_NUM_PLAYERS]; static int last_vote; static const char horiz_line[] = "------------------------------------------------------------------------------"; /******************************************************************** Are we operating under a restricted security regime? For now this does not do much. *********************************************************************/ static bool is_restricted(struct connection *caller) { return (caller && caller->access_level != ALLOW_HACK); } /******************************************************************** Returns whether the specified server setting (option) should be sent to the client. *********************************************************************/ static bool sset_is_to_client(int idx) { assert(settings[idx].to_client == SSET_TO_CLIENT || settings[idx].to_client == SSET_SERVER_ONLY); return (settings[idx].to_client == SSET_TO_CLIENT); } typedef enum { PNameOk, PNameEmpty, PNameTooLong, PNameIllegal } PlayerNameStatus; /************************************************************************** ... **************************************************************************/ static PlayerNameStatus test_player_name(char* name) { size_t len = strlen(name); if (len == 0) { return PNameEmpty; } else if (len > MAX_LEN_NAME-1) { return PNameTooLong; } else if (mystrcasecmp(name, ANON_PLAYER_NAME) == 0) { return PNameIllegal; } else if (mystrcasecmp(name, "Observer") == 0) { /* "Observer" used to be illegal and we keep it that way for now. */ return PNameIllegal; } return PNameOk; } static const char *cmdname_accessor(int i) { return commands[i].name; } /************************************************************************** Convert a named command into an id. If accept_ambiguity is true, return the first command in the enum list which matches, else return CMD_AMBIGOUS on ambiguity. (This is a trick to allow ambiguity to be handled in a flexible way without importing notify_player() messages inside this routine - rp) **************************************************************************/ static enum command_id command_named(const char *token, bool accept_ambiguity) { enum m_pre_result result; int ind; result = match_prefix(cmdname_accessor, CMD_NUM, 0, mystrncasecmp, token, &ind); if (result < M_PRE_AMBIGUOUS) { return ind; } else if (result == M_PRE_AMBIGUOUS) { return accept_ambiguity ? ind : CMD_AMBIGUOUS; } else { return CMD_UNRECOGNIZED; } } /************************************************************************** Initialize stuff related to this code module. **************************************************************************/ void stdinhand_init(void) { int i; for (i = 0; i < MAX_NUM_PLAYERS; i++) { votes[i].command[0] = '\0'; memset(votes[i].votes_cast, 0, sizeof(votes[i].votes_cast)); votes[i].vote_no = -1; } last_vote = -1; } /************************************************************************** Check if we satisfy the criteria for resolving a vote, and resolve it if these critera are indeed met. Updates yes and no variables in voting struct as well. Criteria: Accepted immediately if: > 50% of votes for Rejected immediately if: >= 50% of votes against **************************************************************************/ static void check_vote(struct voting *vote) { int i, num_cast = 0, num_voters = 0; vote->yes = 0; vote->no = 0; for (i = 0; i < MAX_NUM_PLAYERS; i++) { if (game.players[i].is_alive && game.players[i].is_connected) { num_voters++; } else { /* Disqualify already given vote (eg if disconnected after voting) */ vote->votes_cast[i] = VOTE_NONE; } } for (i = 0; i < MAX_NUM_PLAYERS; i++) { num_cast = (vote->votes_cast[i] > VOTE_NONE) ? num_cast + 1 : num_cast; vote->yes = (vote->votes_cast[i] == VOTE_YES) ? vote->yes + 1 : vote->yes; vote->no = (vote->votes_cast[i] == VOTE_NO) ? vote->no + 1 : vote->no; } /* Check if we should resolve the vote */ if (vote->command[0] != '\0' && num_voters > 0 && (vote->yes > num_voters / 2 || vote->no >= (num_voters + 1) / 2)) { /* Yep, resolve this one */ vote->vote_no = -1; if (last_vote == vote->vote_no) { last_vote = -1; } if (vote->yes > num_voters / 2) { /* Do it! */ notify_conn(NULL, NULL, E_SETTING, _("Vote \"%s\" is passed %d to %d with %d " "abstentions."), vote->command, vote->yes, vote->no, num_voters - vote->yes - vote->no); handle_stdin_input((struct connection *)NULL, vote->command, FALSE); } else { notify_conn(NULL, NULL, E_SETTING, _("Vote \"%s\" failed with %d against, %d for " "and %d abstentions."), vote->command, vote->no, vote->yes, num_voters - vote->yes - vote->no); } vote->command[0] = '\0'; } } /************************************************************************** Update stuff every turn that is related to this code module. Run this on turn end. **************************************************************************/ void stdinhand_turn(void) { int i; /* Check if any votes have passed */ for (i = 0; i < MAX_NUM_PLAYERS; i++) { check_vote(&votes[i]); } } /************************************************************************** Deinitialize stuff related to this code module. **************************************************************************/ void stdinhand_free(void) { /* Nothing yet */ } /************************************************************************** Whether the caller can use the specified command. caller == NULL means console. **************************************************************************/ static bool may_use(struct connection *caller, enum command_id cmd) { if (!caller) { return TRUE; /* on the console, everything is allowed */ } return (caller->access_level >= commands[cmd].level); } /************************************************************************** Whether the caller cannot use any commands at all. caller == NULL means console. **************************************************************************/ static bool may_use_nothing(struct connection *caller) { if (!caller) { return FALSE; /* on the console, everything is allowed */ } return (caller->access_level == ALLOW_NONE); } /************************************************************************** Whether the caller can set the specified option (assuming that the state of the game would allow changing the option at all). caller == NULL means console. **************************************************************************/ static bool may_set_option(struct connection *caller, int option_idx) { if (!caller) { return TRUE; /* on the console, everything is allowed */ } else { int level = caller->access_level; return ((level == ALLOW_HACK) || (level == ALLOW_CTRL && sset_is_to_client(option_idx))); } } /************************************************************************** Whether the caller can set the specified option, taking into account access, and the game state. caller == NULL means console. **************************************************************************/ static bool may_set_option_now(struct connection *caller, int option_idx) { return (may_set_option(caller, option_idx) && setting_is_changeable(option_idx)); } /************************************************************************** Whether the caller can SEE the specified option. caller == NULL means console, which can see all. client players can see "to client" options, or if player has command access level to change option. **************************************************************************/ static bool may_view_option(struct connection *caller, int option_idx) { if (!caller) { return TRUE; /* on the console, everything is allowed */ } else { return sset_is_to_client(option_idx) || may_set_option(caller, option_idx); } } /************************************************************************** feedback related to server commands caller == NULL means console. No longer duplicate all output to console. This lowlevel function takes a single line; prefix is prepended to line. **************************************************************************/ static void cmd_reply_line(enum command_id cmd, struct connection *caller, enum rfc_status rfc_status, const char *prefix, const char *line) { const char *cmdname = cmd < CMD_NUM ? commands[cmd].name : cmd == CMD_AMBIGUOUS ? _("(ambiguous)") : cmd == CMD_UNRECOGNIZED ? _("(unknown)") : "(?!?)"; /* this case is a bug! */ if (caller) { notify_conn(caller->self, NULL, E_SETTING, "/%s: %s%s", cmdname, prefix, line); /* cc: to the console - testing has proved it's too verbose - rp con_write(rfc_status, "%s/%s: %s%s", caller->name, cmdname, prefix, line); */ } else { con_write(rfc_status, "%s%s", prefix, line); } if (rfc_status == C_OK) { conn_list_iterate(game.est_connections, pconn) { /* Do not tell caller, since he was told above! */ if (caller != pconn) { notify_conn(pconn->self, NULL, E_SETTING, "%s", line); } } conn_list_iterate_end; } } /************************************************************************** va_list version which allow embedded newlines, and each line is sent separately. 'prefix' is prepended to every line _after_ the first line. **************************************************************************/ static void vcmd_reply_prefix(enum command_id cmd, struct connection *caller, enum rfc_status rfc_status, const char *prefix, const char *format, va_list ap) { char buf[4096]; char *c0, *c1; my_vsnprintf(buf, sizeof(buf), format, ap); c0 = buf; while ((c1=strstr(c0, "\n"))) { *c1 = '\0'; cmd_reply_line(cmd, caller, rfc_status, (c0==buf?"":prefix), c0); c0 = c1+1; } cmd_reply_line(cmd, caller, rfc_status, (c0==buf?"":prefix), c0); } /************************************************************************** var-args version of above duplicate declaration required for attribute to work... **************************************************************************/ static void cmd_reply_prefix(enum command_id cmd, struct connection *caller, enum rfc_status rfc_status, const char *prefix, const char *format, ...) fc__attribute((__format__ (__printf__, 5, 6))); static void cmd_reply_prefix(enum command_id cmd, struct connection *caller, enum rfc_status rfc_status, const char *prefix, const char *format, ...) { va_list ap; va_start(ap, format); vcmd_reply_prefix(cmd, caller, rfc_status, prefix, format, ap); va_end(ap); } /************************************************************************** var-args version as above, no prefix **************************************************************************/ static void cmd_reply(enum command_id cmd, struct connection *caller, enum rfc_status rfc_status, const char *format, ...) fc__attribute((__format__ (__printf__, 4, 5))); static void cmd_reply(enum command_id cmd, struct connection *caller, enum rfc_status rfc_status, const char *format, ...) { va_list ap; va_start(ap, format); vcmd_reply_prefix(cmd, caller, rfc_status, "", format, ap); va_end(ap); } /************************************************************************** ... **************************************************************************/ static void cmd_reply_no_such_player(enum command_id cmd, struct connection *caller, char *name, enum m_pre_result match_result) { switch(match_result) { case M_PRE_EMPTY: cmd_reply(cmd, caller, C_SYNTAX, _("Name is empty, so cannot be a player.")); break; case M_PRE_LONG: cmd_reply(cmd, caller, C_SYNTAX, _("Name is too long, so cannot be a player.")); break; case M_PRE_AMBIGUOUS: cmd_reply(cmd, caller, C_FAIL, _("Player name prefix '%s' is ambiguous."), name); break; case M_PRE_FAIL: cmd_reply(cmd, caller, C_FAIL, _("No player by the name of '%s'."), name); break; default: cmd_reply(cmd, caller, C_FAIL, _("Unexpected match_result %d (%s) for '%s'."), match_result, _(m_pre_description(match_result)), name); freelog(LOG_ERROR, "Unexpected match_result %d (%s) for '%s'.", match_result, m_pre_description(match_result), name); } } /************************************************************************** ... **************************************************************************/ static void cmd_reply_no_such_conn(enum command_id cmd, struct connection *caller, const char *name, enum m_pre_result match_result) { switch(match_result) { case M_PRE_EMPTY: cmd_reply(cmd, caller, C_SYNTAX, _("Name is empty, so cannot be a connection.")); break; case M_PRE_LONG: cmd_reply(cmd, caller, C_SYNTAX, _("Name is too long, so cannot be a connection.")); break; case M_PRE_AMBIGUOUS: cmd_reply(cmd, caller, C_FAIL, _("Connection name prefix '%s' is ambiguous."), name); break; case M_PRE_FAIL: cmd_reply(cmd, caller, C_FAIL, _("No connection by the name of '%s'."), name); break; default: cmd_reply(cmd, caller, C_FAIL, _("Unexpected match_result %d (%s) for '%s'."), match_result, _(m_pre_description(match_result)), name); freelog(LOG_ERROR, "Unexpected match_result %d (%s) for '%s'.", match_result, m_pre_description(match_result), name); } } /************************************************************************** ... **************************************************************************/ static void open_metaserver_connection(struct connection *caller) { server_open_meta(); if (send_server_info_to_metaserver(META_INFO)) { notify_conn(NULL, NULL, E_CONNECTION, _("Open metaserver connection to [%s]."), meta_addr_port()); } } /************************************************************************** ... **************************************************************************/ static void close_metaserver_connection(struct connection *caller) { if (send_server_info_to_metaserver(META_GOODBYE)) { server_close_meta(); notify_conn(NULL, NULL, E_CONNECTION, _("Close metaserver connection to [%s]."), meta_addr_port()); } } /************************************************************************** ... **************************************************************************/ static bool metaconnection_command(struct connection *caller, char *arg, bool check) { if ((*arg == '\0') || (0 == strcmp (arg, "?"))) { if (is_metaserver_open()) { cmd_reply(CMD_METACONN, caller, C_COMMENT, _("Metaserver connection is open.")); } else { cmd_reply(CMD_METACONN, caller, C_COMMENT, _("Metaserver connection is closed.")); } } else if ((0 == mystrcasecmp(arg, "u")) || (0 == mystrcasecmp(arg, "up"))) { if (!is_metaserver_open()) { if (!check) { open_metaserver_connection(caller); } } else { cmd_reply(CMD_METACONN, caller, C_METAERROR, _("Metaserver connection is already open.")); return FALSE; } } else if ((0 == mystrcasecmp(arg, "d")) || (0 == mystrcasecmp(arg, "down"))) { if (is_metaserver_open()) { if (!check) { close_metaserver_connection(caller); } } else { cmd_reply(CMD_METACONN, caller, C_METAERROR, _("Metaserver connection is already closed.")); return FALSE; } } else { cmd_reply(CMD_METACONN, caller, C_METAERROR, _("Argument must be 'u', 'up', 'd', 'down', or '?'.")); return FALSE; } return TRUE; } /************************************************************************** ... **************************************************************************/ static bool metapatches_command(struct connection *caller, char *arg, bool check) { if (check) { return TRUE; } set_meta_patches_string(arg); if (is_metaserver_open()) { send_server_info_to_metaserver(META_INFO); notify_conn(NULL, NULL, E_SETTING, _("Metaserver patches string set to '%s'."), arg); } else { notify_conn(NULL, NULL, E_SETTING, _("Metaserver patches string set to '%s', " "not reporting to metaserver."), arg); } return TRUE; } /************************************************************************** ... **************************************************************************/ static bool metamessage_command(struct connection *caller, char *arg, bool check) { if (check) { return TRUE; } set_user_meta_message_string(arg); if (is_metaserver_open()) { send_server_info_to_metaserver(META_INFO); notify_conn(NULL, NULL, E_SETTING, _("Metaserver message string set to '%s'."), arg); } else { notify_conn(NULL, NULL, E_SETTING, _("Metaserver message string set to '%s', " "not reporting to metaserver."), arg); } return TRUE; } /************************************************************************** ... **************************************************************************/ static bool metaserver_command(struct connection *caller, char *arg, bool check) { if (check) { return TRUE; } close_metaserver_connection(caller); sz_strlcpy(srvarg.metaserver_addr, arg); notify_conn(NULL, NULL, E_SETTING, _("Metaserver is now [%s]."), meta_addr_port()); return TRUE; } /************************************************************************** Returns the serverid **************************************************************************/ static bool show_serverid(struct connection *caller, char *arg) { cmd_reply(CMD_SRVID, caller, C_COMMENT, _("Server id: %s"), srvarg.serverid); return TRUE; } /*************************************************************** This could be in common/player if the client ever gets told the ai player skill levels. ***************************************************************/ const char *name_of_skill_level(int level) { const char *nm[11] = { "UNUSED", "away", "novice", "easy", "UNKNOWN", "normal", "UNKNOWN", "hard", "UNKNOWN", "UNKNOWN", "experimental" }; assert(level>0 && level<=10); return nm[level]; } /*************************************************************** ... ***************************************************************/ static int handicap_of_skill_level(int level) { int h[11] = { -1, /* away */ H_AWAY | H_RATES | H_TARGETS | H_HUTS | H_FOG | H_MAP | H_REVOLUTION, /* novice */ H_RATES | H_TARGETS | H_HUTS | H_NOPLANES | H_DIPLOMAT | H_LIMITEDHUTS | H_DEFENSIVE | H_DIPLOMACY | H_REVOLUTION | H_EXPANSION | H_DANGER, /* easy */ H_RATES | H_TARGETS | H_HUTS | H_NOPLANES | H_DIPLOMAT | H_LIMITEDHUTS | H_DEFENSIVE | H_REVOLUTION | H_EXPANSION, H_NONE, /* medium */ H_RATES | H_TARGETS | H_HUTS | H_DIPLOMAT, H_NONE, /* hard */ H_NONE, H_NONE, H_NONE, /* testing */ H_EXPERIMENTAL, }; assert(level>0 && level<=10); return h[level]; } /************************************************************************** Return the AI fuzziness (0 to 1000) corresponding to a given skill level (1 to 10). See ai_fuzzy() in common/player.c **************************************************************************/ static int fuzzy_of_skill_level(int level) { int f[11] = { -1, 0, 400/*novice*/, 300/*easy*/, 0, 0, 0, 0, 0, 0, 0 }; assert(level>0 && level<=10); return f[level]; } /************************************************************************** Return the AI's science development cost; a science development cost of 100 means that the AI develops science at the same speed as a human; a science development cost of 200 means that the AI develops science at half the speed of a human, and a sceence development cost of 50 means that the AI develops science twice as fast as the human **************************************************************************/ static int science_cost_of_skill_level(int level) { int x[11] = { -1, 100, 250/*novice*/, 100/*easy*/, 100, 100, 100, 100, 100, 100, 100 }; assert(level>0 && level<=10); return x[level]; } /************************************************************************** Return the AI expansion tendency, a percentage factor to value new cities, compared to defaults. 0 means _never_ build new cities, > 100 means to (over?)value them even more than the default (already expansionistic) AI. **************************************************************************/ static int expansionism_of_skill_level(int level) { int x[11] = { -1, 100, 10/*novice*/, 10/*easy*/, 100, 100, 100, 100, 100, 100, 100 }; assert(level>0 && level<=10); return x[level]; } /************************************************************************** For command "save foo"; Save the game, with filename=arg, provided server state is ok. **************************************************************************/ static bool save_command(struct connection *caller, char *arg, bool check) { if (is_restricted(caller)) { cmd_reply(CMD_SAVE, caller, C_FAIL, _("You cannot save games manually on this server.")); return FALSE; } if (!check) { save_game(arg, "User request"); } return TRUE; } /************************************************************************** ... **************************************************************************/ void toggle_ai_player_direct(struct connection *caller, struct player *pplayer) { assert(pplayer != NULL); if (is_barbarian(pplayer)) { cmd_reply(CMD_AITOGGLE, caller, C_FAIL, _("Cannot toggle a barbarian player.")); return; } pplayer->ai.control = !pplayer->ai.control; if (pplayer->ai.control) { cmd_reply(CMD_AITOGGLE, caller, C_OK, _("%s is now under AI control."), pplayer->name); if (pplayer->ai.skill_level==0) { pplayer->ai.skill_level = game.info.skill_level; } /* Set the skill level explicitly, because eg: the player skill level could have been set as AI, then toggled, then saved, then reloaded. */ set_ai_level(caller, pplayer->name, pplayer->ai.skill_level, FALSE); /* the AI can't do active diplomacy */ cancel_all_meetings(pplayer); /* The following is sometimes necessary to avoid using uninitialized data... */ if (server_state == RUN_GAME_STATE) { assess_danger_player(pplayer); } /* In case this was last player who has not pressed turn done. */ check_for_full_turn_done(); } else { cmd_reply(CMD_AITOGGLE, caller, C_OK, _("%s is now under human control."), pplayer->name); /* because the hard AI `cheats' with government rates but humans shouldn't */ if (!game.info.is_new_game) { check_player_government_rates(pplayer); } /* Remove hidden dialogs from clients. This way the player can initiate * new meeting */ cancel_all_meetings(pplayer); } send_player_info(pplayer, NULL); } /************************************************************************** ... **************************************************************************/ static bool toggle_ai_player(struct connection *caller, char *arg, bool check) { enum m_pre_result match_result; struct player *pplayer; pplayer = find_player_by_name_prefix(arg, &match_result); if (!pplayer) { cmd_reply_no_such_player(CMD_AITOGGLE, caller, arg, match_result); return FALSE; } else if (!check) { toggle_ai_player_direct(caller, pplayer); } return TRUE; } /************************************************************************** ... **************************************************************************/ static bool create_ai_player(struct connection *caller, char *arg, bool check) { struct player *pplayer; PlayerNameStatus PNameStatus; bool ai_player_should_be_removed = FALSE; if (server_state!=PRE_GAME_STATE) { cmd_reply(CMD_CREATE, caller, C_SYNTAX, _("Can't add AI players once the game has begun.")); return FALSE; } /* game.info.max_players is a limit on the number of non-observer players. * MAX_NUM_PLAYERS is a limit on all players. */ if (game.info.nplayers >= MAX_NUM_PLAYERS) { /* Try emptying a slot if there is an ai player * created through the /aifill command */ players_iterate(eplayer) { if (eplayer->is_connected || eplayer->was_created) { continue; } ai_player_should_be_removed = TRUE; break; } players_iterate_end; if (!ai_player_should_be_removed) { cmd_reply(CMD_CREATE, caller, C_FAIL, _("Can't add more players, server is full.")); return FALSE; } } if ((PNameStatus = test_player_name(arg)) == PNameEmpty) { cmd_reply(CMD_CREATE, caller, C_SYNTAX, _("Can't use an empty name.")); return FALSE; } if (PNameStatus == PNameTooLong) { cmd_reply(CMD_CREATE, caller, C_SYNTAX, _("That name exceeds the maximum of %d chars."), MAX_LEN_NAME-1); return FALSE; } if (PNameStatus == PNameIllegal) { cmd_reply(CMD_CREATE, caller, C_SYNTAX, _("That name is not allowed.")); return FALSE; } if ((pplayer=find_player_by_name(arg))) { cmd_reply(CMD_CREATE, caller, C_BOUNCE, _("A player already exists by that name.")); return FALSE; } if ((pplayer = find_player_by_user(arg))) { cmd_reply(CMD_CREATE, caller, C_BOUNCE, _("A user already exists by that name.")); return FALSE; } if (check) { return TRUE; } if (ai_player_should_be_removed) { players_iterate(eplayer) { if (eplayer->is_connected || eplayer->was_created) { continue; } server_remove_player(eplayer); break; } players_iterate_end; } pplayer = &game.players[game.info.nplayers]; server_player_init(pplayer, FALSE, TRUE); sz_strlcpy(pplayer->name, arg); sz_strlcpy(pplayer->username, ANON_USER_NAME); pplayer->was_created = TRUE; /* must use /remove explicitly to remove */ game.info.nplayers++; notify_conn(NULL, NULL, E_SETTING, _("%s has been added as an AI-controlled player."), arg); pplayer = find_player_by_name(arg); if (!pplayer) { cmd_reply(CMD_CREATE, caller, C_FAIL, _("Error creating new AI player: %s."), arg); return FALSE; } pplayer->ai.control = TRUE; set_ai_level_directer(pplayer, game.info.skill_level); aifill(game.info.aifill); send_game_info(NULL); send_player_info(pplayer, NULL); reset_all_start_commands(); (void) send_server_info_to_metaserver(META_INFO); return TRUE; } /************************************************************************** ... **************************************************************************/ static bool remove_player(struct connection *caller, char *arg, bool check) { enum m_pre_result match_result; struct player *pplayer; char name[MAX_LEN_NAME]; pplayer=find_player_by_name_prefix(arg, &match_result); if(!pplayer) { cmd_reply_no_such_player(CMD_REMOVE, caller, arg, match_result); return FALSE; } if (!(game.info.is_new_game && server_state == PRE_GAME_STATE)) { cmd_reply(CMD_REMOVE, caller, C_FAIL, _("Players cannot be removed once the game has started.")); return FALSE; } if (check) { return TRUE; } sz_strlcpy(name, pplayer->name); server_remove_player(pplayer); if (!caller || caller->used) { /* may have removed self */ cmd_reply(CMD_REMOVE, caller, C_OK, _("Removed player %s from the game."), name); } aifill(game.info.aifill); return TRUE; } /************************************************************************** Returns FALSE iff there was an error. Security: We will look for a file with mandatory extension '.serv', and on public servers we will not look outside the data directories. As long as the user cannot create files with arbitrary names in the root of the data directories, this should ensure that we will not be tricked into loading non-approved content. The script is read with the permissions of the caller, so it will in any case not lead to elevated permissions unless there are other bugs. **************************************************************************/ bool read_init_script(struct connection *caller, char *script_filename, bool from_cmdline) { FILE *script_file; const char extension[] = ".serv"; char serv_filename[strlen(extension) + strlen(script_filename) + 2]; char tilde_filename[4096]; char *real_filename; /* abuse real_filename to find if we already have a .serv extension */ real_filename = script_filename + strlen(script_filename) - MIN(strlen(extension), strlen(script_filename)); if (strcmp(real_filename, extension) != 0) { my_snprintf(serv_filename, sizeof(serv_filename), "%s%s", script_filename, extension); } else { sz_strlcpy(serv_filename, script_filename); } if (is_restricted(caller) && !from_cmdline) { if (!is_safe_filename(serv_filename)) { cmd_reply(CMD_READ_SCRIPT, caller, C_FAIL, _("Name \"%s\" disallowed for security reasons."), serv_filename); return FALSE; } sz_strlcpy(tilde_filename, serv_filename); } else { interpret_tilde(tilde_filename, sizeof(tilde_filename), serv_filename); } real_filename = datafilename(tilde_filename); if (!real_filename) { if (is_restricted(caller) && !from_cmdline) { cmd_reply(CMD_READ_SCRIPT, caller, C_FAIL, _("No command script found by the name \"%s\"."), serv_filename); return FALSE; } /* File is outside data directories */ real_filename = tilde_filename; } freelog(LOG_NORMAL, _("Loading script file: %s"), real_filename); if (is_reg_file_for_access(real_filename, FALSE) && (script_file = fopen(real_filename, "r"))) { char buffer[MAX_LEN_CONSOLE_LINE]; /* the size is set as to not overflow buffer in handle_stdin_input */ while (fgets(buffer, MAX_LEN_CONSOLE_LINE - 1, script_file)) { /* Execute script contents with same permissions as caller */ handle_stdin_input(caller, buffer, FALSE); } fclose(script_file); return TRUE; } else { cmd_reply(CMD_READ_SCRIPT, caller, C_FAIL, _("Cannot read command line scriptfile '%s'."), real_filename); freelog(LOG_ERROR, _("Could not read script file '%s'."), real_filename); return FALSE; } } /************************************************************************** ... **************************************************************************/ static bool read_command(struct connection *caller, char *arg, bool check) { if (check) { return TRUE; /* FIXME: no actual checks done */ } /* warning: there is no recursion check! */ return read_init_script(caller, arg, FALSE); } /************************************************************************** ... (Should this take a 'caller' argument for output? --dwp) **************************************************************************/ static void write_init_script(char *script_filename) { char real_filename[1024]; FILE *script_file; interpret_tilde(real_filename, sizeof(real_filename), script_filename); if (is_reg_file_for_access(real_filename, TRUE) && (script_file = fopen(real_filename, "w"))) { int i; fprintf(script_file, "#FREECIV SERVER COMMAND FILE, version %s\n", VERSION_STRING); fputs("# These are server options saved from a running civserver.\n", script_file); /* first, some state info from commands (we can't save everything) */ fprintf(script_file, "cmdlevel %s new\n", cmdlevel_name(default_access_level)); fprintf(script_file, "cmdlevel %s first\n", cmdlevel_name(first_access_level)); fprintf(script_file, "%s\n", (game.info.skill_level == 1) ? "away" : (game.info.skill_level == 2) ? "novice" : (game.info.skill_level == 3) ? "easy" : (game.info.skill_level == 5) ? "medium" : (game.info.skill_level < 10) ? "hard" : "experimental"); if (*srvarg.metaserver_addr != '\0' && ((0 != strcmp(srvarg.metaserver_addr, DEFAULT_META_SERVER_ADDR)))) { fprintf(script_file, "metaserver %s\n", meta_addr_port()); } if (0 != strcmp(get_meta_patches_string(), default_meta_patches_string())) { fprintf(script_file, "metapatches %s\n", get_meta_patches_string()); } if (0 != strcmp(get_meta_message_string(), default_meta_message_string())) { fprintf(script_file, "metamessage %s\n", get_meta_message_string()); } /* then, the 'set' option settings */ for (i=0;settings[i].name;i++) { struct settings_s *op = &settings[i]; switch (op->type) { case SSET_INT: fprintf(script_file, "set %s %i\n", op->name, *op->int_value); break; case SSET_BOOL: fprintf(script_file, "set %s %i\n", op->name, (*op->bool_value) ? 1 : 0); break; case SSET_STRING: fprintf(script_file, "set %s %s\n", op->name, op->string_value); break; } } /* rulesetdir */ fprintf(script_file, "rulesetdir %s\n", game.rulesetdir); fclose(script_file); } else { freelog(LOG_ERROR, _("Could not write script file '%s'."), real_filename); } } /************************************************************************** ... ('caller' argument is unused) **************************************************************************/ static bool write_command(struct connection *caller, char *arg, bool check) { if (is_restricted(caller)) { cmd_reply(CMD_WRITE_SCRIPT, caller, C_OK, _("You cannot use the write " "command on this server for security reasons.")); return FALSE; } else if (!check) { write_init_script(arg); } return TRUE; } /************************************************************************** set ptarget's cmdlevel to level if caller is allowed to do so **************************************************************************/ static bool set_cmdlevel(struct connection *caller, struct connection *ptarget, enum cmdlevel_id level) { assert(ptarget != NULL); /* only ever call me for specific connection */ if (caller && ptarget->access_level > caller->access_level) { /* * This command is intended to be used at ctrl access level * and thus this if clause is needed. * (Imagine a ctrl level access player that wants to change * access level of a hack level access player) * At the moment it can be used only by hack access level * and thus this clause is never used. */ cmd_reply(CMD_CMDLEVEL, caller, C_FAIL, _("Cannot decrease command access level '%s' for connection '%s';" " you only have '%s'."), cmdlevel_name(ptarget->access_level), ptarget->username, cmdlevel_name(caller->access_level)); return FALSE; } else { ptarget->access_level = level; return TRUE; } } /******************************************************************** Returns true if there is at least one established connection. *********************************************************************/ static bool a_connection_exists(void) { return conn_list_size(game.est_connections) > 0; } /******************************************************************** ... *********************************************************************/ static bool first_access_level_is_taken(void) { conn_list_iterate(game.est_connections, pconn) { if (pconn->access_level >= first_access_level) { return TRUE; } } conn_list_iterate_end; return FALSE; } /******************************************************************** ... *********************************************************************/ enum cmdlevel_id access_level_for_next_connection(void) { if ((first_access_level > default_access_level) && !a_connection_exists()) { return first_access_level; } else { return default_access_level; } } /******************************************************************** ... *********************************************************************/ void notify_if_first_access_level_is_available(void) { if (first_access_level > default_access_level && !first_access_level_is_taken()) { notify_conn(NULL, NULL, E_SETTING, _("Anyone can now become game organizer " "'%s' by issuing the 'first' command."), cmdlevel_name(first_access_level)); } } /************************************************************************** Change command access level for individual player, or all, or new. **************************************************************************/ static bool cmdlevel_command(struct connection *caller, char *str, bool check) { char arg_level[MAX_LEN_CONSOLE_LINE]; /* info, ctrl etc */ char arg_name[MAX_LEN_CONSOLE_LINE]; /* a player name, or "new" */ char *cptr_s, *cptr_d; /* used for string ops */ enum m_pre_result match_result; enum cmdlevel_id level; struct connection *ptarget; /* find the start of the level: */ for (cptr_s = str; *cptr_s != '\0' && !my_isalnum(*cptr_s); cptr_s++) { /* nothing */ } /* copy the level into arg_level[] */ for(cptr_d=arg_level; *cptr_s != '\0' && my_isalnum(*cptr_s); cptr_s++, cptr_d++) { *cptr_d=*cptr_s; } *cptr_d='\0'; if (arg_level[0] == '\0') { /* no level name supplied; list the levels */ cmd_reply(CMD_CMDLEVEL, caller, C_COMMENT, _("Command access levels in effect:")); conn_list_iterate(game.est_connections, pconn) { cmd_reply(CMD_CMDLEVEL, caller, C_COMMENT, "cmdlevel %s %s", cmdlevel_name(pconn->access_level), pconn->username); } conn_list_iterate_end; cmd_reply(CMD_CMDLEVEL, caller, C_COMMENT, _("Command access level for new connections: %s"), cmdlevel_name(default_access_level)); cmd_reply(CMD_CMDLEVEL, caller, C_COMMENT, _("Command access level for first player to take it: %s"), cmdlevel_name(first_access_level)); return TRUE; } /* a level name was supplied; set the level */ if ((level = cmdlevel_named(arg_level)) == ALLOW_UNRECOGNIZED) { cmd_reply(CMD_CMDLEVEL, caller, C_SYNTAX, _("Error: command access level must be one of" " 'none', 'info', 'ctrl', or 'hack'.")); return FALSE; } else if (caller && level > caller->access_level) { cmd_reply(CMD_CMDLEVEL, caller, C_FAIL, _("Cannot increase command access level to '%s';" " you only have '%s' yourself."), arg_level, cmdlevel_name(caller->access_level)); return FALSE; } if (check) { return TRUE; /* looks good */ } /* find the start of the name: */ for (; *cptr_s != '\0' && !my_isalnum(*cptr_s); cptr_s++) { /* nothing */ } /* copy the name into arg_name[] */ for(cptr_d=arg_name; *cptr_s != '\0' && (*cptr_s == '-' || *cptr_s == ' ' || my_isalnum(*cptr_s)); cptr_s++ , cptr_d++) { *cptr_d=*cptr_s; } *cptr_d='\0'; if (arg_name[0] == '\0') { /* no playername supplied: set for all connections, and set the default */ conn_list_iterate(game.est_connections, pconn) { if (set_cmdlevel(caller, pconn, level)) { cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for connection %s."), cmdlevel_name(level), pconn->username); } else { cmd_reply(CMD_CMDLEVEL, caller, C_FAIL, _("Command access level could not be set to '%s' for " "connection %s."), cmdlevel_name(level), pconn->username); return FALSE; } } conn_list_iterate_end; default_access_level = level; cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for new players."), cmdlevel_name(level)); first_access_level = level; cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for first player to grab it."), cmdlevel_name(level)); } else if (strcmp(arg_name,"new") == 0) { default_access_level = level; cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for new players."), cmdlevel_name(level)); if (level > first_access_level) { first_access_level = level; cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for first player to grab it."), cmdlevel_name(level)); } } else if (strcmp(arg_name,"first") == 0) { first_access_level = level; cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for first player to grab it."), cmdlevel_name(level)); if (level < default_access_level) { default_access_level = level; cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for new players."), cmdlevel_name(level)); } } else if ((ptarget = find_conn_by_user_prefix(arg_name, &match_result))) { if (set_cmdlevel(caller, ptarget, level)) { cmd_reply(CMD_CMDLEVEL, caller, C_OK, _("Command access level set to '%s' for connection %s."), cmdlevel_name(level), ptarget->username); } else { cmd_reply(CMD_CMDLEVEL, caller, C_FAIL, _("Command access level could not be set to '%s'" " for connection %s."), cmdlevel_name(level), ptarget->username); return FALSE; } } else { cmd_reply_no_such_conn(CMD_CMDLEVEL, caller, arg_name, match_result); return FALSE; } return TRUE; } /************************************************************************** This special command to set the command access level is not included into cmdlevel_command because of its lower access level: it can be used to promote one's own connection to 'first come' cmdlevel if that isn't already taken. **************************************************************************/ static bool firstlevel_command(struct connection *caller, bool check) { if (!caller) { cmd_reply(CMD_FIRSTLEVEL, caller, C_FAIL, _("The 'first' command makes no sense from the server command line.")); return FALSE; } else if (caller->access_level >= first_access_level) { cmd_reply(CMD_FIRSTLEVEL, caller, C_FAIL, _("You already have command access level '%s' or better."), cmdlevel_name(first_access_level)); return FALSE; } else if (first_access_level_is_taken()) { cmd_reply(CMD_FIRSTLEVEL, caller, C_FAIL, _("Someone else already is game organizer.")); return FALSE; } else if (!check) { caller->access_level = first_access_level; cmd_reply(CMD_FIRSTLEVEL, caller, C_OK, _("Connection %s has opted to become the game organizer."), caller->username); } return TRUE; } /************************************************************************** Returns possible parameters for the commands that take server options as parameters (CMD_EXPLAIN and CMD_SET). **************************************************************************/ static const char *optname_accessor(int i) { return settings[i].name; } #if defined HAVE_LIBREADLINE || defined HAVE_NEWLIBREADLINE /************************************************************************** Returns possible parameters for the /show command. **************************************************************************/ static const char *olvlname_accessor(int i) { /* for 0->4, uses option levels, otherwise returns a setting name */ if (i < OLEVELS_NUM) { return sset_level_names[i]; } else { return settings[i-OLEVELS_NUM].name; } } #endif /************************************************************************** Set timeout options. **************************************************************************/ static bool timeout_command(struct connection *caller, char *str, bool check) { char buf[MAX_LEN_CONSOLE_LINE]; char *arg[4]; int i = 0, ntokens; int *timeouts[4]; timeouts[0] = &game.timeoutint; timeouts[1] = &game.timeoutintinc; timeouts[2] = &game.timeoutinc; timeouts[3] = &game.timeoutincmult; sz_strlcpy(buf, str); ntokens = get_tokens(buf, arg, 4, TOKEN_DELIMITERS); for (i = 0; i < ntokens; i++) { if (sscanf(arg[i], "%d", timeouts[i]) != 1) { cmd_reply(CMD_TIMEOUT, caller, C_FAIL, _("Invalid argument %d."), i + 1); } free(arg[i]); } if (ntokens == 0) { cmd_reply(CMD_TIMEOUT, caller, C_SYNTAX, _("Usage: timeoutincrease " " " " .")); return FALSE; } else if (check) { return TRUE; } cmd_reply(CMD_TIMEOUT, caller, C_OK, _("Dynamic timeout set to " "%d %d %d %d"), game.timeoutint, game.timeoutintinc, game.timeoutinc, game.timeoutincmult); /* if we set anything here, reset the counter */ game.timeoutcounter = 1; return TRUE; } /************************************************************************** Find option level number by name. **************************************************************************/ static enum sset_level lookup_option_level(const char *name) { enum sset_level i; for (i = SSET_ALL; i <= SSET_CHANGED; i++) { if (0 == mystrcasecmp(name, sset_level_names[i])) { return i; } } return SSET_NONE; } /************************************************************************** Find option index by name. Return index (>=0) on success, -1 if no suitable options were found, -2 if several matches were found. **************************************************************************/ static int lookup_option(const char *name) { enum m_pre_result result; int ind; /* Check for option levels, first off */ if (lookup_option_level(name) != SSET_NONE) { return -3; } result = match_prefix(optname_accessor, SETTINGS_NUM, 0, mystrncasecmp, name, &ind); return ((result < M_PRE_AMBIGUOUS) ? ind : (result == M_PRE_AMBIGUOUS) ? -2 : -1); } /************************************************************************** Show the caller detailed help for the single OPTION given by id. help_cmd is the command the player used. Only show option values for options which the caller can SEE. **************************************************************************/ static void show_help_option(struct connection *caller, enum command_id help_cmd, int id) { struct settings_s *op = &settings[id]; if (op->short_help) { cmd_reply(help_cmd, caller, C_COMMENT, "%s %s - %s", _("Option:"), op->name, _(op->short_help)); } else { cmd_reply(help_cmd, caller, C_COMMENT, "%s %s", _("Option:"), op->name); } if(op->extra_help && strcmp(op->extra_help,"")!=0) { static struct astring abuf = ASTRING_INIT; const char *help = _(op->extra_help); astr_minsize(&abuf, strlen(help)+1); strcpy(abuf.str, help); wordwrap_string(abuf.str, 76); cmd_reply(help_cmd, caller, C_COMMENT, _("Description:")); cmd_reply_prefix(help_cmd, caller, C_COMMENT, " ", " %s", abuf.str); } cmd_reply(help_cmd, caller, C_COMMENT, _("Status: %s"), (setting_is_changeable(id) ? _("changeable") : _("fixed"))); if (may_view_option(caller, id)) { switch (op->type) { case SSET_BOOL: cmd_reply(help_cmd, caller, C_COMMENT, _("Value: %d, Minimum: 0, Default: %d, Maximum: 1"), (*(op->bool_value)) ? 1 : 0, op->bool_default_value ? 1 : 0); break; case SSET_INT: cmd_reply(help_cmd, caller, C_COMMENT, _("Value: %d, Minimum: %d, Default: %d, Maximum: %d"), *(op->int_value), op->int_min_value, op->int_default_value, op->int_max_value); break; case SSET_STRING: cmd_reply(help_cmd, caller, C_COMMENT, _("Value: \"%s\", Default: \"%s\""), op->string_value, op->string_default_value); break; } } } /************************************************************************** Show the caller list of OPTIONS. help_cmd is the command the player used. Only show options which the caller can SEE. **************************************************************************/ static void show_help_option_list(struct connection *caller, enum command_id help_cmd) { int i, j; cmd_reply(help_cmd, caller, C_COMMENT, horiz_line); cmd_reply(help_cmd, caller, C_COMMENT, _("Explanations are available for the following server options:")); cmd_reply(help_cmd, caller, C_COMMENT, horiz_line); if(!caller && con_get_style()) { for (i=0; settings[i].name; i++) { cmd_reply(help_cmd, caller, C_COMMENT, "%s", settings[i].name); } } else { char buf[MAX_LEN_CONSOLE_LINE]; buf[0] = '\0'; for (i=0, j=0; settings[i].name; i++) { if (may_view_option(caller, i)) { cat_snprintf(buf, sizeof(buf), "%-19s", settings[i].name); if ((++j % 4) == 0) { cmd_reply(help_cmd, caller, C_COMMENT, buf); buf[0] = '\0'; } } } if (buf[0] != '\0') cmd_reply(help_cmd, caller, C_COMMENT, buf); } cmd_reply(help_cmd, caller, C_COMMENT, horiz_line); } /************************************************************************** ... **************************************************************************/ static bool explain_option(struct connection *caller, char *str, bool check) { char command[MAX_LEN_CONSOLE_LINE], *cptr_s, *cptr_d; int cmd; for (cptr_s = str; *cptr_s != '\0' && !my_isalnum(*cptr_s); cptr_s++) { /* nothing */ } for (cptr_d = command; *cptr_s != '\0' && my_isalnum(*cptr_s); cptr_s++, cptr_d++) *cptr_d=*cptr_s; *cptr_d='\0'; if (*command != '\0') { cmd=lookup_option(command); if (cmd >= 0 && cmd < SETTINGS_NUM) { show_help_option(caller, CMD_EXPLAIN, cmd); } else if (cmd == -1 || cmd == -3) { cmd_reply(CMD_EXPLAIN, caller, C_FAIL, _("No explanation for that yet.")); return FALSE; } else if (cmd == -2) { cmd_reply(CMD_EXPLAIN, caller, C_FAIL, _("Ambiguous option name.")); return FALSE; } else { freelog(LOG_ERROR, "Unexpected case %d in %s line %d", cmd, __FILE__, __LINE__); return FALSE; } } else { show_help_option_list(caller, CMD_EXPLAIN); } return TRUE; } /****************************************************************** Send a message to all players ******************************************************************/ static bool wall(char *str, bool check) { if (!check) { notify_conn(NULL, NULL, E_MESSAGE_WALL, _("Server Operator: %s"), str); } return TRUE; } /**************************************************************************** Tell the client about just one server setting. Call this after a setting is saved. ****************************************************************************/ static void send_server_setting(struct conn_list *dest, int setting_id) { struct packet_options_settable packet; struct settings_s *setting = &settings[setting_id]; if (!dest) { dest = game.est_connections; } conn_list_iterate(dest, pconn) { memset(&packet, 0, sizeof(packet)); packet.id = setting_id; sz_strlcpy(packet.name, setting->name); sz_strlcpy(packet.short_help, setting->short_help); sz_strlcpy(packet.extra_help, setting->extra_help); packet.category = setting->category; packet.type = setting->type; packet.class = setting->sclass; packet.is_visible = (sset_is_to_client(setting_id) || pconn->access_level == ALLOW_HACK); if (packet.is_visible) { switch (setting->type) { case SSET_STRING: strcpy(packet.strval, setting->string_value); strcpy(packet.default_strval, setting->string_default_value); break; case SSET_BOOL: packet.val = *(setting->bool_value); packet.default_val = setting->bool_default_value; break; case SSET_INT: packet.min = setting->int_min_value; packet.max = setting->int_max_value; packet.val = *(setting->int_value); packet.default_val = setting->int_default_value; break; } } send_packet_options_settable(pconn, &packet); } conn_list_iterate_end; } /**************************************************************************** Tell the client about all server settings. ****************************************************************************/ void send_server_settings(struct conn_list *dest) { struct packet_options_settable_control control; int i; if (!dest) { dest = game.est_connections; } /* count the number of settings */ control.num_settings = SETTINGS_NUM; /* fill in the category strings */ control.num_categories = SSET_NUM_CATEGORIES; for (i = 0; i < SSET_NUM_CATEGORIES; i++) { strcpy(control.category_names[i], sset_category_names[i]); } /* send off the control packet */ lsend_packet_options_settable_control(dest, &control); for (i = 0; i < SETTINGS_NUM; i++) { send_server_setting(dest, i); } } /****************************************************************** Set an AI level and related quantities, with no feedback. ******************************************************************/ void set_ai_level_directer(struct player *pplayer, int level) { pplayer->ai.handicap = handicap_of_skill_level(level); pplayer->ai.fuzzy = fuzzy_of_skill_level(level); pplayer->ai.expand = expansionism_of_skill_level(level); pplayer->ai.science_cost = science_cost_of_skill_level(level); pplayer->ai.skill_level = level; } /****************************************************************** Translate an AI level back to its CMD_* value. If we just used /set ailevel we wouldn't have to do this - rp ******************************************************************/ static enum command_id cmd_of_level(int level) { switch(level) { case 1 : return CMD_AWAY; case 2 : return CMD_NOVICE; case 3 : return CMD_EASY; case 5 : return CMD_NORMAL; case 7 : return CMD_HARD; case 10 : return CMD_EXPERIMENTAL; } assert(FALSE); return CMD_NORMAL; /* to satisfy compiler */ } /****************************************************************** Set an AI level from the server prompt. ******************************************************************/ void set_ai_level_direct(struct player *pplayer, int level) { set_ai_level_directer(pplayer,level); send_player_info(pplayer, NULL); cmd_reply(cmd_of_level(level), NULL, C_OK, _("Player '%s' now has AI skill level '%s'."), pplayer->name, name_of_skill_level(level)); } /****************************************************************** Handle a user command to set an AI level. ******************************************************************/ static bool set_ai_level(struct connection *caller, char *name, int level, bool check) { enum m_pre_result match_result; struct player *pplayer; assert(level > 0 && level < 11); pplayer=find_player_by_name_prefix(name, &match_result); if (pplayer) { if (pplayer->ai.control) { if (check) { return TRUE; } set_ai_level_directer(pplayer, level); send_player_info(pplayer, NULL); cmd_reply(cmd_of_level(level), caller, C_OK, _("Player '%s' now has AI skill level '%s'."), pplayer->name, name_of_skill_level(level)); } else { cmd_reply(cmd_of_level(level), caller, C_FAIL, _("%s is not controlled by the AI."), pplayer->name); return FALSE; } } else if(match_result == M_PRE_EMPTY) { if (check) { return TRUE; } players_iterate(pplayer) { if (pplayer->ai.control) { set_ai_level_directer(pplayer, level); send_player_info(pplayer, NULL); cmd_reply(cmd_of_level(level), caller, C_OK, _("Player '%s' now has AI skill level '%s'."), pplayer->name, name_of_skill_level(level)); } } players_iterate_end; game.info.skill_level = level; cmd_reply(cmd_of_level(level), caller, C_OK, _("Default AI skill level set to '%s'."), name_of_skill_level(level)); } else { cmd_reply_no_such_player(cmd_of_level(level), caller, name, match_result); return FALSE; } return TRUE; } /****************************************************************** Set user to away mode. ******************************************************************/ static bool set_away(struct connection *caller, char *name, bool check) { if (caller == NULL) { cmd_reply(CMD_AWAY, caller, C_FAIL, _("This command is client only.")); return FALSE; } else if (name && strlen(name) > 0) { notify_conn(caller->self, NULL, E_SETTING, _("Usage: away")); return FALSE; } else if (!caller->player || caller->observer) { /* This happens for detached or observer connections. */ notify_conn(caller->self, NULL, E_SETTING, _("Only players may use the away command.")); return FALSE; } else if (!caller->player->ai.control && !check) { notify_conn(game.est_connections, NULL, E_SETTING, _("%s set to away mode."), caller->player->name); send_player_info(caller->player, NULL); set_ai_level_directer(caller->player, 1); caller->player->ai.control = TRUE; cancel_all_meetings(caller->player); } else if (!check) { notify_conn(game.est_connections, NULL, E_SETTING, _("%s returned to game."), caller->player->name); caller->player->ai.control = FALSE; /* We have to do it, because the client doesn't display * dialogs for meetings in AI mode. */ cancel_all_meetings(caller->player); } return TRUE; } /****************************************************************** Print a summary of the settings and their values. Note that most values are at most 4 digits, except seeds, which we let overflow their columns, plus a sign character. Only show options which the caller can SEE. ******************************************************************/ static bool show_command(struct connection *caller, char *str, bool check) { char buf[MAX_LEN_CONSOLE_LINE]; char command[MAX_LEN_CONSOLE_LINE], *cptr_s, *cptr_d; bool is_changed; int cmd,i,len1; enum sset_level level = SSET_VITAL; size_t clen = 0; for (cptr_s = str; *cptr_s != '\0' && !my_isalnum(*cptr_s); cptr_s++) { /* nothing */ } for (cptr_d = command; *cptr_s != '\0' && my_isalnum(*cptr_s); cptr_s++, cptr_d++) *cptr_d=*cptr_s; *cptr_d='\0'; if (*command != '\0') { /* In "/show forests", figure out that it's the forests option we're * looking at. */ cmd=lookup_option(command); if (cmd >= 0) { /* Ignore levels when a particular option is specified. */ level = SSET_NONE; if (!may_view_option(caller, cmd)) { cmd_reply(CMD_SHOW, caller, C_FAIL, _("Sorry, you do not have access to view option '%s'."), command); return FALSE; } } if (cmd == -1) { cmd_reply(CMD_SHOW, caller, C_FAIL, _("Unknown option '%s'."), command); return FALSE; } if (cmd == -2) { /* allow ambiguous: show all matching */ clen = strlen(command); } if (cmd == -3) { /* Option level */ level = lookup_option_level(command); } } else { cmd = -1; /* to indicate that no comannd was specified */ } #define cmd_reply_show(string) cmd_reply(CMD_SHOW, caller, C_COMMENT, string) #define OPTION_NAME_SPACE 13 /* under 16, so it fits into 80 cols more easily - rp */ cmd_reply_show(horiz_line); switch(level) { case SSET_NONE: break; case SSET_CHANGED: cmd_reply_show(_("All options with non-default values")); break; case SSET_ALL: cmd_reply_show(_("All options")); break; case SSET_VITAL: cmd_reply_show(_("Vital options")); break; case SSET_SITUATIONAL: cmd_reply_show(_("Situational options")); break; case SSET_RARE: cmd_reply_show(_("Rarely used options")); break; } cmd_reply_show(_("+ means you may change the option")); cmd_reply_show(_("= means the option is on its default value")); cmd_reply_show(horiz_line); len1 = my_snprintf(buf, sizeof(buf), _("%-*s value (min,max) "), OPTION_NAME_SPACE, _("Option")); if (len1 == -1) len1 = sizeof(buf) -1; sz_strlcat(buf, _("description")); cmd_reply_show(buf); cmd_reply_show(horiz_line); buf[0] = '\0'; for (i = 0; settings[i].name; i++) { is_changed = FALSE; if (may_view_option(caller, i) && (cmd == -1 || cmd == -3 || level == SSET_CHANGED || cmd == i || (cmd == -2 && mystrncasecmp(settings[i].name, command, clen) == 0))) { /* in the cmd==i case, this loop is inefficient. never mind - rp */ struct settings_s *op = &settings[i]; int len = 0; if ((level == SSET_ALL || op->level == level || cmd >= 0 || level == SSET_CHANGED)) { switch (op->type) { case SSET_BOOL: if (*op->bool_value != op->bool_default_value) { is_changed = TRUE; } len = my_snprintf(buf, sizeof(buf), "%-*s %c%c%-5d (0,1)", OPTION_NAME_SPACE, op->name, may_set_option_now(caller, i) ? '+' : ' ', ((*op->bool_value == op->bool_default_value) ? '=' : ' '), (*op->bool_value) ? 1 : 0); break; case SSET_INT: if (*op->int_value != op->int_default_value) { is_changed = TRUE; } len = my_snprintf(buf, sizeof(buf), "%-*s %c%c%-5d (%d,%d)", OPTION_NAME_SPACE, op->name, may_set_option_now(caller, i) ? '+' : ' ', ((*op->int_value == op->int_default_value) ? '=' : ' '), *op->int_value, op->int_min_value, op->int_max_value); break; case SSET_STRING: if (strcmp(op->string_value, op->string_default_value) != 0) { is_changed = TRUE; } len = my_snprintf(buf, sizeof(buf), "%-*s %c%c\"%s\"", OPTION_NAME_SPACE, op->name, may_set_option_now(caller, i) ? '+' : ' ', ((strcmp(op->string_value, op->string_default_value) == 0) ? '=' : ' '), op->string_value); break; } if (len == -1) { len = sizeof(buf) - 1; } /* Line up the descriptions: */ if (len < len1) { cat_snprintf(buf, sizeof(buf), "%*s", (len1-len), " "); } else { sz_strlcat(buf, " "); } sz_strlcat(buf, _(op->short_help)); if ((is_changed) || (level != SSET_CHANGED)) { cmd_reply_show(buf); } } } } cmd_reply_show(horiz_line); if (level == SSET_VITAL) { cmd_reply_show(_("Try 'show situational' or 'show rare' to show " "more options.\n" "Try 'show changed' to show settings with " "non-default values.")); cmd_reply_show(horiz_line); } return TRUE; #undef cmd_reply_show #undef OPTION_NAME_SPACE } /****************************************************************** Which characters are allowed within option names: (for 'set') ******************************************************************/ static bool is_ok_opt_name_char(char c) { return my_isalnum(c); } /****************************************************************** Which characters are allowed within option values: (for 'set') ******************************************************************/ static bool is_ok_opt_value_char(char c) { return (c == '-') || (c == '*') || (c == '+') || (c == '=') || my_isalnum(c); } /****************************************************************** Which characters are allowed between option names and values: (for 'set') ******************************************************************/ static bool is_ok_opt_name_value_sep_char(char c) { return (c == '=') || my_isspace(c); } /****************************************************************** ... ******************************************************************/ static bool team_command(struct connection *caller, char *str, bool check) { struct player *pplayer; enum m_pre_result match_result; char buf[MAX_LEN_CONSOLE_LINE]; char *arg[2]; int ntokens = 0, i; bool res = FALSE; struct team *pteam; if (server_state != PRE_GAME_STATE || !game.info.is_new_game) { cmd_reply(CMD_TEAM, caller, C_SYNTAX, _("Cannot change teams once game has begun.")); return FALSE; } if (str != NULL || strlen(str) > 0) { sz_strlcpy(buf, str); ntokens = get_tokens(buf, arg, 2, TOKEN_DELIMITERS); } if (ntokens != 2) { cmd_reply(CMD_TEAM, caller, C_SYNTAX, _("Undefined argument. Usage: team .")); goto cleanup; } pplayer = find_player_by_name_prefix(arg[0], &match_result); if (pplayer == NULL) { cmd_reply_no_such_player(CMD_TEAM, caller, arg[0], match_result); goto cleanup; } pteam = team_find_by_name(arg[1]); if (!pteam) { int teamno; if (sscanf(arg[1], "%d", &teamno) == 1) { pteam = team_get_by_id(teamno); } } if (!pteam) { cmd_reply(CMD_TEAM, caller, C_SYNTAX, _("No such team %s. Please give a " "valid team name or number."), arg[1]); goto cleanup; } if (is_barbarian(pplayer)) { /* This can happen if we change team settings on a loaded game. */ cmd_reply(CMD_TEAM, caller, C_SYNTAX, _("Cannot team a barbarian.")); goto cleanup; } if (!check) { team_add_player(pplayer, pteam); send_player_info(pplayer, NULL); cmd_reply(CMD_TEAM, caller, C_OK, _("Player %s set to team %s."), pplayer->name, team_get_name(pteam)); } res = TRUE; cleanup: for (i = 0; i < ntokens; i++) { free(arg[i]); } return res; } /****************************************************************** Make or participate in a vote. ******************************************************************/ static bool vote_command(struct connection *caller, char *str, bool check) { char buf[MAX_LEN_CONSOLE_LINE]; char *arg[3]; int ntokens = 0, i; const char *usage = _("Undefined arguments. Usage: vote yes|no " "[vote number]."); int idx; bool res = FALSE; if (caller == NULL || caller->player == NULL) { cmd_reply(CMD_VOTE, caller, C_FAIL, _("This command is client only.")); return FALSE; } else if (caller->observer) { cmd_reply(CMD_VOTE, caller, C_FAIL, _("Observers cannot vote.")); return FALSE; } else if (server_state != RUN_GAME_STATE) { cmd_reply(CMD_VOTE, caller, C_FAIL, _("You can only vote in a " "running game. Use 'first' to become the game organizer " "if there currently is none.")); return FALSE; } else if (!str || strlen(str) == 0) { int j = 0; for (i = 0; i < MAX_NUM_PLAYERS; i++) { struct voting *vote = &votes[i]; if (vote->command[0] != '\0') { j++; cmd_reply(CMD_VOTE, caller, C_COMMENT, _("Vote %d \"%s\": %d for, %d against"), vote->vote_no, vote->command, vote->yes, vote->no); } } if (j == 0) { cmd_reply(CMD_VOTE, caller, C_COMMENT, _("There are no votes going on.")); } return FALSE; /* see below */ } if (check) { return FALSE; /* cannot vote over having vote! */ } idx = caller->player->player_no; sz_strlcpy(buf, str); ntokens = get_tokens(buf, arg, 2, TOKEN_DELIMITERS); if (strcmp(arg[0], "yes") == 0 || strcmp(arg[0], "no") == 0) { int which = -1; struct voting *vote = NULL; if (ntokens == 1) { /* Applies to last vote */ if (last_vote > -1) { which = last_vote; } else { cmd_reply(CMD_VOTE, caller, C_FAIL, _("No legal last vote.")); goto cleanup; } } else { if (sscanf(arg[1], "%d", &which) <= 0) { cmd_reply(CMD_VOTE, caller, C_SYNTAX, _("Value must be integer.")); goto cleanup; } } /* Ok, now try to find this vote */ for (i = 0; i < MAX_NUM_PLAYERS; i++) { if (votes[i].vote_no == which) { vote = &votes[i]; } } if (which > last_vote || !vote || vote->command[0] == '\0') { cmd_reply(CMD_VOTE, caller, C_FAIL, _("No such vote (%d)."), which); goto cleanup; } if (strcmp(arg[0], "yes") == 0) { cmd_reply(CMD_VOTE, caller, C_COMMENT, _("You voted for \"%s\""), vote->command); vote->votes_cast[caller->player->player_no] = VOTE_YES; } else if (strcmp(arg[0], "no") == 0) { cmd_reply(CMD_VOTE, caller, C_COMMENT, _("You voted against \"%s\""), vote->command); vote->votes_cast[caller->player->player_no] = VOTE_NO; } check_vote(vote); } else { cmd_reply(CMD_VOTE, caller, C_SYNTAX, usage); goto cleanup; } res = TRUE; cleanup: for (i = 0; i < ntokens; i++) { free(arg[i]); } return res; } /****************************************************************** ... ******************************************************************/ static bool debug_command(struct connection *caller, char *str, bool check) { char buf[MAX_LEN_CONSOLE_LINE]; char *arg[3]; int ntokens = 0, i; const char *usage = _("Undefined arguments. Usage: debug | city | units | " "unit | tech | timing | info>."); if (game.info.is_new_game) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Can only use this command once game has begun.")); return FALSE; } if (check) { return TRUE; /* whatever! */ } if (str != NULL && strlen(str) > 0) { sz_strlcpy(buf, str); ntokens = get_tokens(buf, arg, 3, TOKEN_DELIMITERS); } else { ntokens = 0; } if (ntokens > 0 && strcmp(arg[0], "diplomacy") == 0) { struct player *pplayer; enum m_pre_result match_result; if (ntokens != 2) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, usage); goto cleanup; } pplayer = find_player_by_name_prefix(arg[1], &match_result); if (pplayer == NULL) { cmd_reply_no_such_player(CMD_DEBUG, caller, arg[1], match_result); goto cleanup; } if (BV_ISSET(pplayer->debug, PLAYER_DEBUG_DIPLOMACY)) { BV_CLR(pplayer->debug, PLAYER_DEBUG_DIPLOMACY); cmd_reply(CMD_DEBUG, caller, C_OK, _("%s diplomacy no longer debugged"), pplayer->name); } else { BV_SET(pplayer->debug, PLAYER_DEBUG_DIPLOMACY); cmd_reply(CMD_DEBUG, caller, C_OK, _("%s diplomacy debugged"), pplayer->name); /* TODO: print some info about the player here */ } } else if (ntokens > 0 && strcmp(arg[0], "tech") == 0) { struct player *pplayer; enum m_pre_result match_result; if (ntokens != 2) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, usage); goto cleanup; } pplayer = find_player_by_name_prefix(arg[1], &match_result); if (pplayer == NULL) { cmd_reply_no_such_player(CMD_DEBUG, caller, arg[1], match_result); goto cleanup; } if (BV_ISSET(pplayer->debug, PLAYER_DEBUG_TECH)) { BV_CLR(pplayer->debug, PLAYER_DEBUG_TECH); cmd_reply(CMD_DEBUG, caller, C_OK, _("%s tech no longer debugged"), pplayer->name); } else { BV_SET(pplayer->debug, PLAYER_DEBUG_TECH); cmd_reply(CMD_DEBUG, caller, C_OK, _("%s tech debugged"), pplayer->name); /* TODO: print some info about the player here */ } } else if (ntokens > 0 && strcmp(arg[0], "info") == 0) { int cities = 0, players = 0, units = 0, citizens = 0; players_iterate(plr) { players++; city_list_iterate(plr->cities, pcity) { cities++; citizens += pcity->size; } city_list_iterate_end; unit_list_iterate(plr->units, punit) { units++; } unit_list_iterate_end; } players_iterate_end; freelog(LOG_NORMAL, "players=%d cities=%d citizens=%d units=%d", players, cities, citizens, units); notify_conn(game.est_connections, NULL, E_AI_DEBUG, "players=%d cities=%d citizens=%d units=%d", players, cities, citizens, units); } else if (ntokens > 0 && strcmp(arg[0], "city") == 0) { int x, y; struct tile *ptile; struct city *pcity; if (ntokens != 3) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, usage); goto cleanup; } if (sscanf(arg[1], "%d", &x) != 1 || sscanf(arg[2], "%d", &y) != 1) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Value 2 & 3 must be integer.")); goto cleanup; } if (!(ptile = map_pos_to_tile(x, y))) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Bad map coordinates.")); goto cleanup; } pcity = ptile->city; if (!pcity) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("No city at this coordinate.")); goto cleanup; } if (pcity->debug) { pcity->debug = FALSE; cmd_reply(CMD_DEBUG, caller, C_OK, _("%s no longer debugged"), pcity->name); } else { pcity->debug = TRUE; CITY_LOG(LOG_NORMAL, pcity, "debugged"); pcity->ai.next_recalc = 0; /* force recalc of city next turn */ } } else if (ntokens > 0 && strcmp(arg[0], "units") == 0) { int x, y; struct tile *ptile; if (ntokens != 3) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, usage); goto cleanup; } if (sscanf(arg[1], "%d", &x) != 1 || sscanf(arg[2], "%d", &y) != 1) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Value 2 & 3 must be integer.")); goto cleanup; } if (!(ptile = map_pos_to_tile(x, y))) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Bad map coordinates.")); goto cleanup; } unit_list_iterate(ptile->units, punit) { if (punit->debug) { punit->debug = FALSE; cmd_reply(CMD_DEBUG, caller, C_OK, _("%s's %s no longer debugged."), unit_owner(punit)->name, unit_name_translation(punit)); } else { punit->debug = TRUE; UNIT_LOG(LOG_NORMAL, punit, _("%s's %s debugged."), unit_owner(punit)->name, unit_name_translation(punit)); } } unit_list_iterate_end; } else if (ntokens > 0 && strcmp(arg[0], "timing") == 0) { TIMING_RESULTS(); } else if (ntokens > 0 && strcmp(arg[0], "unit") == 0) { int id; struct unit *punit; if (ntokens != 2) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, usage); goto cleanup; } if (sscanf(arg[1], "%d", &id) != 1) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Value 2 must be integer.")); goto cleanup; } if (!(punit = find_unit_by_id(id))) { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("Unit %d does not exist."), id); goto cleanup; } if (punit->debug) { punit->debug = FALSE; cmd_reply(CMD_DEBUG, caller, C_OK, _("%s's %s no longer debugged."), unit_owner(punit)->name, unit_name_translation(punit)); } else { punit->debug = TRUE; UNIT_LOG(LOG_NORMAL, punit, _("%s's %s debugged."), unit_owner(punit)->name, unit_name_translation(punit)); } } else { cmd_reply(CMD_DEBUG, caller, C_SYNTAX, usage); } cleanup: for (i = 0; i < ntokens; i++) { free(arg[i]); } return TRUE; } /****************************************************************** ... ******************************************************************/ static bool set_command(struct connection *caller, char *str, bool check) { char command[MAX_LEN_CONSOLE_LINE], arg[MAX_LEN_CONSOLE_LINE], *cptr_s, *cptr_d; int val, cmd, i; struct settings_s *op; bool do_update; char buffer[500]; for (cptr_s = str; *cptr_s != '\0' && !is_ok_opt_name_char(*cptr_s); cptr_s++) { /* nothing */ } for(cptr_d=command; *cptr_s != '\0' && is_ok_opt_name_char(*cptr_s); cptr_s++, cptr_d++) { *cptr_d=*cptr_s; } *cptr_d='\0'; for (; *cptr_s != '\0' && is_ok_opt_name_value_sep_char(*cptr_s); cptr_s++) { /* nothing */ } for (cptr_d = arg; *cptr_s != '\0' && is_ok_opt_value_char(*cptr_s); cptr_s++, cptr_d++) *cptr_d=*cptr_s; *cptr_d='\0'; cmd = lookup_option(command); if (cmd==-1) { cmd_reply(CMD_SET, caller, C_SYNTAX, _("Undefined argument. Usage: set