Make wesnothd use iterators instead of socket_ptrs to keep track of players (#5413)
This commit is contained in:
parent
51511e582e
commit
8058daba84
5 changed files with 499 additions and 581 deletions
File diff suppressed because it is too large
Load diff
|
@ -20,6 +20,8 @@
|
|||
#include "server/common/simple_wml.hpp"
|
||||
#include "utils/make_enum.hpp"
|
||||
|
||||
#include "utils/optional_fwd.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
|
@ -27,8 +29,8 @@
|
|||
|
||||
namespace wesnothd
|
||||
{
|
||||
typedef std::vector<socket_ptr> user_vector;
|
||||
typedef std::vector<socket_ptr> side_vector;
|
||||
typedef std::vector<player_iterator> user_vector;
|
||||
typedef std::vector<utils::optional<player_iterator>> side_vector;
|
||||
class server;
|
||||
|
||||
class game
|
||||
|
@ -41,7 +43,7 @@ public:
|
|||
);
|
||||
|
||||
game(wesnothd::server& server, player_connections& player_connections,
|
||||
const socket_ptr& host,
|
||||
player_iterator host,
|
||||
const std::string& name = "",
|
||||
bool save_replays = false,
|
||||
const std::string& replay_save_path = "");
|
||||
|
@ -68,25 +70,25 @@ public:
|
|||
return name_;
|
||||
}
|
||||
|
||||
bool is_owner(const socket_ptr& player) const
|
||||
bool is_owner(player_iterator player) const
|
||||
{
|
||||
return (player == owner_);
|
||||
}
|
||||
|
||||
bool is_member(const socket_ptr& player) const
|
||||
bool is_member(player_iterator player) const
|
||||
{
|
||||
return is_player(player) || is_observer(player);
|
||||
}
|
||||
|
||||
bool allow_observers() const;
|
||||
bool is_observer(const socket_ptr& player) const;
|
||||
bool is_player(const socket_ptr& player) const;
|
||||
bool is_observer(player_iterator player) const;
|
||||
bool is_player(player_iterator player) const;
|
||||
|
||||
/** Checks whether the connection's ip address or username is banned. */
|
||||
bool player_is_banned(const socket_ptr& player, const std::string& name) const;
|
||||
bool player_is_banned(player_iterator player, const std::string& name) const;
|
||||
|
||||
/** when the host sends the new scenario of a mp campaign */
|
||||
void new_scenario(const socket_ptr& player);
|
||||
void new_scenario(player_iterator player);
|
||||
|
||||
bool level_init() const
|
||||
{
|
||||
|
@ -153,36 +155,36 @@ public:
|
|||
* Mute an observer or give a message of all currently muted observers if no
|
||||
* name is given.
|
||||
*/
|
||||
void mute_observer(const simple_wml::node& mute, const socket_ptr& muter);
|
||||
void mute_observer(const simple_wml::node& mute, player_iterator muter);
|
||||
|
||||
void unmute_observer(const simple_wml::node& unmute, const socket_ptr& unmuter);
|
||||
void unmute_observer(const simple_wml::node& unmute, player_iterator unmuter);
|
||||
|
||||
/**
|
||||
* Kick a member by name.
|
||||
*
|
||||
* @return The network handle of the removed member if
|
||||
* successful, null pointer otherwise.
|
||||
* @return The iterator to the removed member if
|
||||
* successful, empty optional otherwise.
|
||||
*/
|
||||
socket_ptr kick_member(const simple_wml::node& kick, const socket_ptr& kicker);
|
||||
utils::optional<player_iterator> kick_member(const simple_wml::node& kick, player_iterator kicker);
|
||||
|
||||
/**
|
||||
* Ban and kick a user by name.
|
||||
*
|
||||
* The user does not need to be in this game but logged in.
|
||||
*
|
||||
* @return The network handle of the banned player if he
|
||||
* was in this game, null pointer otherwise.
|
||||
* @return The iterator to the banned player if he
|
||||
* was in this game, empty optional otherwise.
|
||||
*/
|
||||
socket_ptr ban_user(const simple_wml::node& ban, const socket_ptr& banner);
|
||||
utils::optional<player_iterator> ban_user(const simple_wml::node& ban, player_iterator banner);
|
||||
|
||||
void unban_user(const simple_wml::node& unban, const socket_ptr& unbanner);
|
||||
void unban_user(const simple_wml::node& unban, player_iterator unbanner);
|
||||
|
||||
/**
|
||||
* Add a user to the game.
|
||||
*
|
||||
* @return True iff the user successfully joined the game.
|
||||
*/
|
||||
bool add_player(const socket_ptr& player, bool observer = false);
|
||||
bool add_player(player_iterator player, bool observer = false);
|
||||
|
||||
/**
|
||||
* Removes a user from the game.
|
||||
|
@ -191,12 +193,12 @@ public:
|
|||
* no more players or the host left on a not yet
|
||||
* started game.
|
||||
*/
|
||||
bool remove_player(const socket_ptr& player, const bool disconnect = false, const bool destruct = false);
|
||||
bool remove_player(player_iterator player, const bool disconnect = false, const bool destruct = false);
|
||||
|
||||
/** Adds players and observers into one vector and returns that. */
|
||||
const user_vector all_game_users() const;
|
||||
|
||||
void start_game(const socket_ptr& starter);
|
||||
void start_game(player_iterator starter);
|
||||
|
||||
// this is performed just before starting and before [start_game] signal
|
||||
// send scenario_diff's specific to each client so that they locally
|
||||
|
@ -206,7 +208,7 @@ public:
|
|||
void update_game();
|
||||
|
||||
/** A user (player only?) asks for the next scenario to advance to. */
|
||||
void load_next_scenario(const socket_ptr& user); // const
|
||||
void load_next_scenario(player_iterator user); // const
|
||||
|
||||
// iceiceice: I unmarked this const because I want to send and record server messages when I fail to tweak sides
|
||||
// properly
|
||||
|
@ -215,9 +217,9 @@ public:
|
|||
void update_side_data();
|
||||
|
||||
/** Let's a player owning a side give it to another player or observer. */
|
||||
void transfer_side_control(const socket_ptr& sock, const simple_wml::node& cfg);
|
||||
void transfer_side_control(player_iterator player, const simple_wml::node& cfg);
|
||||
|
||||
void process_message(simple_wml::document& data, const socket_ptr& user);
|
||||
void process_message(simple_wml::document& data, player_iterator);
|
||||
|
||||
/**
|
||||
* Handles [end_turn], repackages [commands] with private [speak]s in them
|
||||
|
@ -228,12 +230,12 @@ public:
|
|||
*
|
||||
* @returns True if the turn ended.
|
||||
*/
|
||||
bool process_turn(simple_wml::document& data, const socket_ptr& user);
|
||||
bool process_turn(simple_wml::document& data, player_iterator user);
|
||||
|
||||
/** Handles incoming [whiteboard] data. */
|
||||
void process_whiteboard(simple_wml::document& data, const socket_ptr& user);
|
||||
void process_whiteboard(simple_wml::document& data, player_iterator user);
|
||||
/** Handles incoming [change_turns_wml] data. */
|
||||
void process_change_turns_wml(simple_wml::document& data, const socket_ptr& user);
|
||||
void process_change_turns_wml(simple_wml::document& data, player_iterator user);
|
||||
|
||||
/**
|
||||
* Set the description to the number of available slots.
|
||||
|
@ -242,30 +244,30 @@ public:
|
|||
*/
|
||||
bool describe_slots();
|
||||
|
||||
void send_server_message_to_all(const char* message, const socket_ptr& exclude = socket_ptr());
|
||||
void send_server_message_to_all(const std::string& message, const socket_ptr& exclude = socket_ptr())
|
||||
void send_server_message_to_all(const char* message, utils::optional<player_iterator> exclude = {});
|
||||
void send_server_message_to_all(const std::string& message, utils::optional<player_iterator> exclude = {})
|
||||
{
|
||||
send_server_message_to_all(message.c_str(), exclude);
|
||||
}
|
||||
|
||||
void send_server_message(
|
||||
const char* message, const socket_ptr& sock = socket_ptr(), simple_wml::document* doc = nullptr) const;
|
||||
const char* message, utils::optional<player_iterator> player = {}, simple_wml::document* doc = nullptr) const;
|
||||
void send_server_message(
|
||||
const std::string& message, const socket_ptr& sock = socket_ptr(), simple_wml::document* doc = nullptr) const
|
||||
const std::string& message, utils::optional<player_iterator> player = {}, simple_wml::document* doc = nullptr) const
|
||||
{
|
||||
send_server_message(message.c_str(), sock, doc);
|
||||
send_server_message(message.c_str(), player, doc);
|
||||
}
|
||||
|
||||
/** Send data to all players in this game except 'exclude'. */
|
||||
void send_and_record_server_message(const char* message, const socket_ptr& exclude = socket_ptr());
|
||||
void send_and_record_server_message(const std::string& message, const socket_ptr& exclude = socket_ptr())
|
||||
void send_and_record_server_message(const char* message, utils::optional<player_iterator> exclude = {});
|
||||
void send_and_record_server_message(const std::string& message, utils::optional<player_iterator> exclude = {})
|
||||
{
|
||||
send_and_record_server_message(message.c_str(), exclude);
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
void send_to_players(simple_wml::document& data, const Container& players, socket_ptr exclude = socket_ptr());
|
||||
void send_data(simple_wml::document& data, const socket_ptr& exclude = socket_ptr(), std::string packet_type = "");
|
||||
void send_to_players(simple_wml::document& data, const Container& players, utils::optional<player_iterator> exclude = {});
|
||||
void send_data(simple_wml::document& data, utils::optional<player_iterator> exclude = {}, std::string packet_type = "");
|
||||
|
||||
void clear_history();
|
||||
void record_data(std::unique_ptr<simple_wml::document> data);
|
||||
|
@ -318,7 +320,7 @@ public:
|
|||
|
||||
void set_termination_reason(const std::string& reason);
|
||||
|
||||
void handle_choice(const simple_wml::node& data, const socket_ptr& user);
|
||||
void handle_choice(const simple_wml::node& data, player_iterator user);
|
||||
|
||||
void handle_random_choice(const simple_wml::node& data);
|
||||
|
||||
|
@ -334,7 +336,7 @@ public:
|
|||
/**
|
||||
* Function which returns true iff 'player' controls any of the sides spcified in 'sides'.
|
||||
*/
|
||||
bool controls_side(const std::vector<int>& sides, const socket_ptr& player) const;
|
||||
bool controls_side(const std::vector<int>& sides, player_iterator player) const;
|
||||
|
||||
bool is_reload() const;
|
||||
|
||||
|
@ -348,23 +350,23 @@ private:
|
|||
return nsides_ ? (current_side_index_ % nsides_) : 0;
|
||||
}
|
||||
|
||||
const socket_ptr current_player() const
|
||||
utils::optional<player_iterator> current_player() const
|
||||
{
|
||||
return (nsides_ ? sides_[current_side()] : socket_ptr());
|
||||
return sides_[current_side()];
|
||||
}
|
||||
|
||||
bool is_current_player(const socket_ptr& player) const
|
||||
bool is_current_player(player_iterator player) const
|
||||
{
|
||||
return (current_player() == player);
|
||||
}
|
||||
|
||||
bool is_muted_observer(const socket_ptr& player) const;
|
||||
bool is_muted_observer(player_iterator player) const;
|
||||
bool all_observers_muted() const
|
||||
{
|
||||
return all_observers_muted_;
|
||||
}
|
||||
|
||||
void send_muted_observers(const socket_ptr& user) const;
|
||||
void send_muted_observers(player_iterator user) const;
|
||||
|
||||
bool send_taken_side(simple_wml::document& cfg, const simple_wml::node* side) const;
|
||||
|
||||
|
@ -377,21 +379,21 @@ private:
|
|||
* First we look for a side where save_id= or current_player= matches the
|
||||
* new user's name then we search for the first controller="network" side.
|
||||
*/
|
||||
bool take_side(const socket_ptr& user);
|
||||
bool take_side(player_iterator user);
|
||||
|
||||
/**
|
||||
* Send [change_controller] message to tell all clients the new controller's name
|
||||
* or controller type (human or ai).
|
||||
*/
|
||||
void change_controller(const std::size_t side_num,
|
||||
const socket_ptr& sock,
|
||||
player_iterator sock,
|
||||
const std::string& player_name,
|
||||
const bool player_left = true);
|
||||
std::unique_ptr<simple_wml::document> change_controller_type(const std::size_t side_num,
|
||||
const socket_ptr& sock,
|
||||
player_iterator player,
|
||||
const std::string& player_name);
|
||||
void transfer_ai_sides(const socket_ptr& player);
|
||||
void send_leave_game(const socket_ptr& user) const;
|
||||
void transfer_ai_sides(player_iterator player);
|
||||
void send_leave_game(player_iterator user) const;
|
||||
|
||||
/**
|
||||
* @param data the data to be sent to the sides.
|
||||
|
@ -400,24 +402,24 @@ private:
|
|||
*/
|
||||
void send_data_sides(simple_wml::document& data,
|
||||
const simple_wml::string_span& sides,
|
||||
const socket_ptr& exclude = socket_ptr());
|
||||
utils::optional<player_iterator> exclude = {});
|
||||
|
||||
void send_data_observers(
|
||||
simple_wml::document& data, const socket_ptr& exclude = socket_ptr(), std::string packet_type = "") const;
|
||||
simple_wml::document& data, utils::optional<player_iterator> exclude = {}, std::string packet_type = "") const;
|
||||
|
||||
/**
|
||||
* Send [observer] tags of all the observers in the game to the user or
|
||||
* everyone if none given.
|
||||
*/
|
||||
void send_observerjoins(const socket_ptr& sock = socket_ptr());
|
||||
void send_observerquit(const socket_ptr& observer);
|
||||
void send_history(const socket_ptr& sock) const;
|
||||
void send_observerjoins(utils::optional<player_iterator> player = {});
|
||||
void send_observerquit(player_iterator observer);
|
||||
void send_history(player_iterator sock) const;
|
||||
|
||||
/** In case of a host transfer, notify the new host about its status. */
|
||||
void notify_new_host();
|
||||
|
||||
/** Shortcut to a convenience function for finding a user by name. */
|
||||
socket_ptr find_user(const simple_wml::string_span& name);
|
||||
utils::optional<player_iterator> find_user(const simple_wml::string_span& name);
|
||||
|
||||
bool observers_can_label() const
|
||||
{
|
||||
|
@ -429,13 +431,13 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool is_legal_command(const simple_wml::node& command, const socket_ptr& user);
|
||||
bool is_legal_command(const simple_wml::node& command, player_iterator user);
|
||||
|
||||
/**
|
||||
* Checks whether a user has the same IP as any other members of this game.
|
||||
* @return A comma separated string of members with matching IPs.
|
||||
*/
|
||||
std::string has_same_ip(const socket_ptr& user) const;
|
||||
std::string has_same_ip(player_iterator user) const;
|
||||
|
||||
/**
|
||||
* Function which should be called every time a player ends their turn
|
||||
|
@ -451,16 +453,13 @@ private:
|
|||
*
|
||||
* Only sends data if the game is initialized but not yet started.
|
||||
*/
|
||||
void send_user_list(const socket_ptr& exclude = socket_ptr());
|
||||
void send_user_list(utils::optional<player_iterator> exclude = {});
|
||||
|
||||
/** Returns the name of the user or "(unfound)". */
|
||||
std::string username(const socket_ptr& pl) const;
|
||||
std::string username(player_iterator pl) const;
|
||||
|
||||
/** Returns a comma separated list of user names. */
|
||||
std::string list_users(user_vector users, const std::string& func) const;
|
||||
|
||||
/** Function to log when we don't find a connection in player_info_. */
|
||||
void missing_user(socket_ptr socket, const std::string& func) const;
|
||||
std::string list_users(user_vector users) const;
|
||||
|
||||
/** calculates the initial value for sides_, side_controllerds_, nsides_*/
|
||||
void reset_sides();
|
||||
|
@ -489,7 +488,7 @@ private:
|
|||
std::string password_;
|
||||
|
||||
/** The game host or later owner (if the host left). */
|
||||
socket_ptr owner_;
|
||||
player_iterator owner_;
|
||||
|
||||
/** A vector of players (members owning a side). */
|
||||
user_vector players_;
|
||||
|
@ -544,7 +543,7 @@ private:
|
|||
* keep track of those players because processing certain
|
||||
* input from those side wil lead to error (oos)
|
||||
*/
|
||||
std::set<socket_ptr> players_not_advanced_;
|
||||
std::set<const player_record*> players_not_advanced_;
|
||||
|
||||
std::string termination_;
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
return socket_;
|
||||
}
|
||||
|
||||
std::string saved_client_ip() const
|
||||
std::string client_ip() const
|
||||
{
|
||||
return ip_address;
|
||||
}
|
||||
|
@ -91,4 +91,6 @@ using player_connections = bmi::multi_index_container<player_record, bmi::indexe
|
|||
bmi::const_mem_fun<player_record, int, &player_record::game_id>>
|
||||
>>;
|
||||
|
||||
typedef player_connections::const_iterator player_iterator;
|
||||
|
||||
} // namespace wesnothd
|
||||
|
|
|
@ -189,7 +189,7 @@ static bool make_change_diff(const simple_wml::node& src,
|
|||
static std::string player_status(const wesnothd::player_record& player)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "'" << player.name() << "' @ " << client_address(player.socket());
|
||||
out << "'" << player.name() << "' @ " << player.client_ip();
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
@ -996,37 +996,38 @@ void server::send_password_request(socket_ptr socket,
|
|||
async_send_doc_queued(socket, doc);
|
||||
}
|
||||
|
||||
void server::handle_player(boost::asio::yield_context yield, socket_ptr socket, const player& player)
|
||||
void server::handle_player(boost::asio::yield_context yield, socket_ptr socket, const player& player_data)
|
||||
{
|
||||
if(lan_server_)
|
||||
abort_lan_server_timer();
|
||||
|
||||
bool inserted;
|
||||
std::tie(std::ignore, inserted) = player_connections_.insert(player_connections::value_type(socket, player));
|
||||
player_iterator player;
|
||||
std::tie(player, inserted) = player_connections_.insert(player_connections::value_type(socket, player_data));
|
||||
assert(inserted);
|
||||
|
||||
BOOST_SCOPE_EXIT_ALL(this, &socket) {
|
||||
remove_player(socket);
|
||||
BOOST_SCOPE_EXIT_ALL(this, &player) {
|
||||
remove_player(player);
|
||||
};
|
||||
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
|
||||
if(!motd_.empty()) {
|
||||
send_server_message(socket, motd_+'\n'+announcements_+tournaments_, "motd");
|
||||
send_server_message(player, motd_+'\n'+announcements_+tournaments_, "motd");
|
||||
}
|
||||
send_server_message(socket, information_, "server_info");
|
||||
send_server_message(socket, announcements_+tournaments_, "announcements");
|
||||
if(version_info(player.version()) < secure_version ){
|
||||
send_server_message(socket, "You are using version " + player.version() + " which has known security issues that can be used to compromise your computer. We strongly recommend updating to a Wesnoth version " + secure_version.str() + " or newer!", "alert");
|
||||
send_server_message(player, information_, "server_info");
|
||||
send_server_message(player, announcements_+tournaments_, "announcements");
|
||||
if(version_info(player_data.version()) < secure_version ){
|
||||
send_server_message(player, "You are using version " + player_data.version() + " which has known security issues that can be used to compromise your computer. We strongly recommend updating to a Wesnoth version " + secure_version.str() + " or newer!", "alert");
|
||||
}
|
||||
if(version_info(player.version()) < version_info(recommended_version_)) {
|
||||
send_server_message(socket, "A newer Wesnoth version, " + recommended_version_ + ", is out!", "alert");
|
||||
if(version_info(player_data.version()) < version_info(recommended_version_)) {
|
||||
send_server_message(player, "A newer Wesnoth version, " + recommended_version_ + ", is out!", "alert");
|
||||
}
|
||||
|
||||
// Send other players in the lobby the update that the player has joined
|
||||
simple_wml::document diff;
|
||||
make_add_diff(games_and_users_list_.root(), nullptr, "user", diff);
|
||||
send_to_lobby(diff, socket);
|
||||
send_to_lobby(diff, player);
|
||||
|
||||
while(true) {
|
||||
boost::system::error_code ec;
|
||||
|
@ -1039,44 +1040,44 @@ void server::handle_player(boost::asio::yield_context yield, socket_ptr socket,
|
|||
}
|
||||
|
||||
if(simple_wml::node* whisper = doc->child("whisper")) {
|
||||
handle_whisper(socket, *whisper);
|
||||
handle_whisper(player, *whisper);
|
||||
}
|
||||
|
||||
if(simple_wml::node* query = doc->child("query")) {
|
||||
handle_query(socket, *query);
|
||||
handle_query(player, *query);
|
||||
}
|
||||
|
||||
if(simple_wml::node* nickserv = doc->child("nickserv")) {
|
||||
handle_nickserv(socket, *nickserv);
|
||||
handle_nickserv(player, *nickserv);
|
||||
}
|
||||
|
||||
if(!player_is_in_game(socket)) {
|
||||
handle_player_in_lobby(socket, *doc);
|
||||
if(!player_is_in_game(player)) {
|
||||
handle_player_in_lobby(player, *doc);
|
||||
} else {
|
||||
handle_player_in_game(socket, *doc);
|
||||
handle_player_in_game(player, *doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void server::handle_player_in_lobby(socket_ptr socket, simple_wml::document& data)
|
||||
void server::handle_player_in_lobby(player_iterator player, simple_wml::document& data)
|
||||
{
|
||||
if(simple_wml::node* message = data.child("message")) {
|
||||
handle_message(socket, *message);
|
||||
handle_message(player, *message);
|
||||
return;
|
||||
}
|
||||
|
||||
if(simple_wml::node* create_game = data.child("create_game")) {
|
||||
handle_create_game(socket, *create_game);
|
||||
handle_create_game(player, *create_game);
|
||||
return;
|
||||
}
|
||||
|
||||
if(simple_wml::node* join = data.child("join")) {
|
||||
handle_join_game(socket, *join);
|
||||
handle_join_game(player, *join);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void server::handle_whisper(socket_ptr socket, simple_wml::node& whisper)
|
||||
void server::handle_whisper(player_iterator player, simple_wml::node& whisper)
|
||||
{
|
||||
if((whisper["receiver"].empty()) || (whisper["message"].empty())) {
|
||||
static simple_wml::document data(
|
||||
|
@ -1087,21 +1088,21 @@ void server::handle_whisper(socket_ptr socket, simple_wml::node& whisper)
|
|||
simple_wml::INIT_COMPRESSED
|
||||
);
|
||||
|
||||
async_send_doc_queued(socket, data);
|
||||
async_send_doc_queued(player->socket(), data);
|
||||
return;
|
||||
}
|
||||
|
||||
whisper.set_attr_dup("sender", player_connections_.find(socket)->name().c_str());
|
||||
whisper.set_attr_dup("sender", player->name().c_str());
|
||||
|
||||
auto receiver_iter = player_connections_.get<name_t>().find(whisper["receiver"].to_string());
|
||||
if(receiver_iter == player_connections_.get<name_t>().end()) {
|
||||
send_server_message(socket, "Can't find '" + whisper["receiver"].to_string() + "'.", "error");
|
||||
send_server_message(player, "Can't find '" + whisper["receiver"].to_string() + "'.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
auto g = player_connections_.find(socket)->get_game();
|
||||
if(g && g->started() && g->is_player(receiver_iter->socket())) {
|
||||
send_server_message(socket, "You cannot send private messages to players in a running game you observe.", "error");
|
||||
auto g = player->get_game();
|
||||
if(g && g->started() && g->is_player(player_connections_.project<0>(receiver_iter))) {
|
||||
send_server_message(player, "You cannot send private messages to players in a running game you observe.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1116,13 +1117,8 @@ void server::handle_whisper(socket_ptr socket, simple_wml::node& whisper)
|
|||
async_send_doc_queued(receiver_iter->socket(), cwhisper);
|
||||
}
|
||||
|
||||
void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
||||
void server::handle_query(player_iterator iter, simple_wml::node& query)
|
||||
{
|
||||
auto iter = player_connections_.find(socket);
|
||||
if(iter == player_connections_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wesnothd::player& player = iter->info();
|
||||
|
||||
const std::string command(query["type"].to_string());
|
||||
|
@ -1152,7 +1148,7 @@ void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
|||
response << process_command(command, player.name());
|
||||
} else if(player.is_moderator()) {
|
||||
if(command == "signout") {
|
||||
LOG_SERVER << "Admin signed out: IP: " << client_address(socket) << "\tnick: " << player.name()
|
||||
LOG_SERVER << "Admin signed out: IP: " << iter->client_ip() << "\tnick: " << player.name()
|
||||
<< std::endl;
|
||||
player.set_moderator(false);
|
||||
// This string is parsed by the client!
|
||||
|
@ -1161,7 +1157,7 @@ void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
|||
user_handler_->set_is_moderator(player.name(), false);
|
||||
}
|
||||
} else {
|
||||
LOG_SERVER << "Admin Command: type: " << command << "\tIP: " << client_address(socket)
|
||||
LOG_SERVER << "Admin Command: type: " << command << "\tIP: " << iter->client_ip()
|
||||
<< "\tnick: " << player.name() << std::endl;
|
||||
response << process_command(command, player.name());
|
||||
LOG_SERVER << response.str() << std::endl;
|
||||
|
@ -1170,7 +1166,7 @@ void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
|||
response << query_help_msg;
|
||||
} else if(command == "admin" || command.compare(0, 6, "admin ") == 0) {
|
||||
if(admin_passwd_.empty()) {
|
||||
send_server_message(socket, "No password set.", "error");
|
||||
send_server_message(iter, "No password set.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1180,7 +1176,7 @@ void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
|||
}
|
||||
|
||||
if(passwd == admin_passwd_) {
|
||||
LOG_SERVER << "New Admin recognized: IP: " << client_address(socket) << "\tnick: " << player.name()
|
||||
LOG_SERVER << "New Admin recognized: IP: " << iter->client_ip() << "\tnick: " << player.name()
|
||||
<< std::endl;
|
||||
player.set_moderator(true);
|
||||
// This string is parsed by the client!
|
||||
|
@ -1190,7 +1186,7 @@ void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
|||
user_handler_->set_is_moderator(player.name(), true);
|
||||
}
|
||||
} else {
|
||||
WRN_SERVER << "FAILED Admin attempt with password: '" << passwd << "'\tIP: " << client_address(socket)
|
||||
WRN_SERVER << "FAILED Admin attempt with password: '" << passwd << "'\tIP: " << iter->client_ip()
|
||||
<< "\tnick: " << player.name() << std::endl;
|
||||
response << "Error: wrong password";
|
||||
}
|
||||
|
@ -1198,14 +1194,14 @@ void server::handle_query(socket_ptr socket, simple_wml::node& query)
|
|||
response << "Error: unrecognized query: '" << command << "'\n" << query_help_msg;
|
||||
}
|
||||
|
||||
send_server_message(socket, response.str(), "info");
|
||||
send_server_message(iter, response.str(), "info");
|
||||
}
|
||||
|
||||
void server::handle_nickserv(socket_ptr socket, simple_wml::node& nickserv)
|
||||
void server::handle_nickserv(player_iterator player, simple_wml::node& nickserv)
|
||||
{
|
||||
// Check if this server allows nick registration at all
|
||||
if(!user_handler_) {
|
||||
send_server_message(socket, "This server does not allow username registration.", "error");
|
||||
send_server_message(player, "This server does not allow username registration.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1213,9 +1209,9 @@ void server::handle_nickserv(socket_ptr socket, simple_wml::node& nickserv)
|
|||
if(nickserv.child("info")) {
|
||||
try {
|
||||
std::string res = user_handler_->user_info((*nickserv.child("info"))["name"].to_string());
|
||||
send_server_message(socket, res, "info");
|
||||
send_server_message(player, res, "info");
|
||||
} catch(const user_handler::error& e) {
|
||||
send_server_message(socket,
|
||||
send_server_message(player,
|
||||
"There was an error looking up the details of the user '"
|
||||
+ (*nickserv.child("info"))["name"].to_string() + "'. "
|
||||
+ " The error message was: " + e.message, "error"
|
||||
|
@ -1226,11 +1222,10 @@ void server::handle_nickserv(socket_ptr socket, simple_wml::node& nickserv)
|
|||
}
|
||||
}
|
||||
|
||||
void server::handle_message(socket_ptr socket, simple_wml::node& message)
|
||||
void server::handle_message(player_iterator user, simple_wml::node& message)
|
||||
{
|
||||
auto user = player_connections_.find(socket);
|
||||
if(user->info().is_message_flooding()) {
|
||||
send_server_message(socket,
|
||||
send_server_message(user,
|
||||
"Warning: you are sending too many messages too fast. Your message has not been relayed.", "error");
|
||||
return;
|
||||
}
|
||||
|
@ -1245,52 +1240,46 @@ void server::handle_message(socket_ptr socket, simple_wml::node& message)
|
|||
chat_message::truncate_message(msg, trunc_message);
|
||||
|
||||
if(msg.size() >= 3 && simple_wml::string_span(msg.begin(), 4) == "/me ") {
|
||||
LOG_SERVER << client_address(socket) << "\t<" << user->name()
|
||||
LOG_SERVER << user->client_ip() << "\t<" << user->name()
|
||||
<< simple_wml::string_span(msg.begin() + 3, msg.size() - 3) << ">\n";
|
||||
} else {
|
||||
LOG_SERVER << client_address(socket) << "\t<" << user->name() << "> " << msg << "\n";
|
||||
LOG_SERVER << user->client_ip() << "\t<" << user->name() << "> " << msg << "\n";
|
||||
}
|
||||
|
||||
send_to_lobby(relay_message, socket);
|
||||
send_to_lobby(relay_message, user);
|
||||
}
|
||||
|
||||
void server::handle_create_game(socket_ptr socket, simple_wml::node& create_game)
|
||||
void server::handle_create_game(player_iterator player, simple_wml::node& create_game)
|
||||
{
|
||||
if(graceful_restart) {
|
||||
static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
|
||||
async_send_doc_queued(socket, leave_game_doc);
|
||||
async_send_doc_queued(player->socket(), leave_game_doc);
|
||||
|
||||
send_server_message(socket,
|
||||
send_server_message(player,
|
||||
"This server is shutting down. You aren't allowed to make new games. Please "
|
||||
"reconnect to the new server.", "error");
|
||||
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(player->socket(), games_and_users_list_);
|
||||
return;
|
||||
}
|
||||
|
||||
player_connections_.modify(
|
||||
player_connections_.find(socket), std::bind(&server::create_game, this, std::placeholders::_1, std::ref(create_game)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void server::create_game(player_record& host_record, simple_wml::node& create_game)
|
||||
{
|
||||
const std::string game_name = create_game["name"].to_string();
|
||||
const std::string game_password = create_game["password"].to_string();
|
||||
const std::string initial_bans = create_game["ignored"].to_string();
|
||||
|
||||
DBG_SERVER << client_address(host_record.socket()) << "\t" << host_record.info().name()
|
||||
DBG_SERVER << player->client_ip() << "\t" << player->info().name()
|
||||
<< "\tcreates a new game: \"" << game_name << "\".\n";
|
||||
|
||||
// Create the new game, remove the player from the lobby
|
||||
// and set the player as the host/owner.
|
||||
host_record.get_game().reset(
|
||||
new wesnothd::game(*this, player_connections_, host_record.socket(), game_name, save_replays_, replay_save_path_),
|
||||
std::bind(&server::cleanup_game, this, std::placeholders::_1)
|
||||
);
|
||||
player_connections_.modify(player, [this, player, &game_name](player_record& host_record) {
|
||||
host_record.get_game().reset(
|
||||
new wesnothd::game(*this, player_connections_, player, game_name, save_replays_, replay_save_path_),
|
||||
std::bind(&server::cleanup_game, this, std::placeholders::_1)
|
||||
);
|
||||
});
|
||||
|
||||
wesnothd::game& g = *host_record.get_game();
|
||||
wesnothd::game& g = *player->get_game();
|
||||
|
||||
DBG_SERVER << "initial bans: " << initial_bans << "\n";
|
||||
if(initial_bans != "") {
|
||||
|
@ -1336,7 +1325,7 @@ void server::cleanup_game(game* game_ptr)
|
|||
delete game_ptr;
|
||||
}
|
||||
|
||||
void server::handle_join_game(socket_ptr socket, simple_wml::node& join)
|
||||
void server::handle_join_game(player_iterator player, simple_wml::node& join)
|
||||
{
|
||||
const bool observer = join.attr("observe").to_bool();
|
||||
const std::string& password = join["password"].to_string();
|
||||
|
@ -1351,55 +1340,55 @@ void server::handle_join_game(socket_ptr socket, simple_wml::node& join)
|
|||
|
||||
static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
|
||||
if(!g) {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player_connections_.find(socket)->info().name()
|
||||
WRN_SERVER << player->client_ip() << "\t" << player->info().name()
|
||||
<< "\tattempted to join unknown game:\t" << game_id << ".\n";
|
||||
async_send_doc_queued(socket, leave_game_doc);
|
||||
send_server_message(socket, "Attempt to join unknown game.", "error");
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(player->socket(), leave_game_doc);
|
||||
send_server_message(player, "Attempt to join unknown game.", "error");
|
||||
async_send_doc_queued(player->socket(), games_and_users_list_);
|
||||
return;
|
||||
} else if(!g->level_init()) {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player_connections_.find(socket)->info().name()
|
||||
WRN_SERVER << player->client_ip() << "\t" << player->info().name()
|
||||
<< "\tattempted to join uninitialized game:\t\"" << g->name() << "\" (" << game_id << ").\n";
|
||||
async_send_doc_queued(socket, leave_game_doc);
|
||||
send_server_message(socket, "Attempt to join an uninitialized game.", "error");
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(player->socket(), leave_game_doc);
|
||||
send_server_message(player, "Attempt to join an uninitialized game.", "error");
|
||||
async_send_doc_queued(player->socket(), games_and_users_list_);
|
||||
return;
|
||||
} else if(player_connections_.find(socket)->info().is_moderator()) {
|
||||
} else if(player->info().is_moderator()) {
|
||||
// Admins are always allowed to join.
|
||||
} else if(g->player_is_banned(socket, player_connections_.find(socket)->info().name())) {
|
||||
DBG_SERVER << client_address(socket)
|
||||
<< "\tReject banned player: " << player_connections_.find(socket)->info().name()
|
||||
} else if(g->player_is_banned(player, player->info().name())) {
|
||||
DBG_SERVER << player->client_ip()
|
||||
<< "\tReject banned player: " << player->info().name()
|
||||
<< "\tfrom game:\t\"" << g->name() << "\" (" << game_id << ").\n";
|
||||
async_send_doc_queued(socket, leave_game_doc);
|
||||
send_server_message(socket, "You are banned from this game.", "error");
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(player->socket(), leave_game_doc);
|
||||
send_server_message(player, "You are banned from this game.", "error");
|
||||
async_send_doc_queued(player->socket(), games_and_users_list_);
|
||||
return;
|
||||
} else if(!g->password_matches(password)) {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player_connections_.find(socket)->info().name()
|
||||
WRN_SERVER << player->client_ip() << "\t" << player->info().name()
|
||||
<< "\tattempted to join game:\t\"" << g->name() << "\" (" << game_id << ") with bad password\n";
|
||||
async_send_doc_queued(socket, leave_game_doc);
|
||||
send_server_message(socket, "Incorrect password.", "error");
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(player->socket(), leave_game_doc);
|
||||
send_server_message(player, "Incorrect password.", "error");
|
||||
async_send_doc_queued(player->socket(), games_and_users_list_);
|
||||
return;
|
||||
}
|
||||
|
||||
bool joined = g->add_player(socket, observer);
|
||||
bool joined = g->add_player(player, observer);
|
||||
if(!joined) {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player_connections_.find(socket)->info().name()
|
||||
WRN_SERVER << player->client_ip() << "\t" << player->info().name()
|
||||
<< "\tattempted to observe game:\t\"" << g->name() << "\" (" << game_id
|
||||
<< ") which doesn't allow observers.\n";
|
||||
async_send_doc_queued(socket, leave_game_doc);
|
||||
async_send_doc_queued(player->socket(), leave_game_doc);
|
||||
|
||||
send_server_message(socket,
|
||||
send_server_message(player,
|
||||
"Attempt to observe a game that doesn't allow observers. (You probably joined the "
|
||||
"game shortly after it filled up.)", "error");
|
||||
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(player->socket(), games_and_users_list_);
|
||||
return;
|
||||
}
|
||||
|
||||
player_connections_.modify(player_connections_.find(socket),
|
||||
std::bind(&player_record::set_game, std::placeholders::_1, player_connections_.get<game_t>().find(game_id)->get_game()));
|
||||
player_connections_.modify(player,
|
||||
std::bind(&player_record::set_game, std::placeholders::_1, g));
|
||||
|
||||
g->describe_slots();
|
||||
|
||||
|
@ -1407,26 +1396,25 @@ void server::handle_join_game(socket_ptr socket, simple_wml::node& join)
|
|||
simple_wml::document diff;
|
||||
bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->description(), diff);
|
||||
bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user",
|
||||
player_connections_.find(socket)->info().config_address(), diff);
|
||||
player->info().config_address(), diff);
|
||||
|
||||
if(diff1 || diff2) {
|
||||
send_to_lobby(diff);
|
||||
}
|
||||
}
|
||||
|
||||
void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data)
|
||||
void server::handle_player_in_game(player_iterator p, simple_wml::document& data)
|
||||
{
|
||||
DBG_SERVER << "in process_data_game...\n";
|
||||
|
||||
auto p = player_connections_.find(socket);
|
||||
wesnothd::player& player = p->info();
|
||||
wesnothd::player& player { p->info() };
|
||||
|
||||
game& g = *(p->get_game());
|
||||
std::weak_ptr<game> g_ptr{p->get_game()};
|
||||
|
||||
// If this is data describing the level for a game.
|
||||
if(data.child("snapshot") || data.child("scenario")) {
|
||||
if(!g.is_owner(socket)) {
|
||||
if(!g.is_owner(p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1437,7 +1425,7 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
// place a pointer to that summary in the game's description.
|
||||
// g.level() should then receive the full data for the game.
|
||||
if(!g.level_init()) {
|
||||
LOG_SERVER << client_address(socket) << "\t" << player.name() << "\tcreated game:\t\"" << g.name() << "\" ("
|
||||
LOG_SERVER << p->client_ip() << "\t" << player.name() << "\tcreated game:\t\"" << g.name() << "\" ("
|
||||
<< g.id() << ", " << g.db_id() << ").\n";
|
||||
// Update our config object which describes the open games,
|
||||
// and save a pointer to the description in the new game.
|
||||
|
@ -1450,13 +1438,13 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
if(const simple_wml::node* m = data.child("multiplayer")) {
|
||||
m->copy_into(desc);
|
||||
} else {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player.name() << "\tsent scenario data in game:\t\""
|
||||
WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
|
||||
<< g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.\n";
|
||||
// Set the description so it can be removed in delete_game().
|
||||
g.set_description(&desc);
|
||||
delete_game(g.id());
|
||||
|
||||
send_server_message(socket,
|
||||
send_server_message(p,
|
||||
"The scenario data is missing the [multiplayer] tag which contains the "
|
||||
"game settings. Game aborted.", "error");
|
||||
return;
|
||||
|
@ -1465,7 +1453,7 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
g.set_description(&desc);
|
||||
desc.set_attr_dup("id", std::to_string(g.id()).c_str());
|
||||
} else {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player.name() << "\tsent scenario data in game:\t\""
|
||||
WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
|
||||
<< g.name() << "\" (" << g.id() << ", " << g.db_id() << ") although it's already initialized.\n";
|
||||
return;
|
||||
}
|
||||
|
@ -1518,7 +1506,7 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
// Send the update of the game description to the lobby.
|
||||
simple_wml::document diff;
|
||||
make_add_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", diff);
|
||||
make_change_diff(games_and_users_list_.root(), nullptr, "user", player_connections_.find(socket)->info().config_address(), diff);
|
||||
make_change_diff(games_and_users_list_.root(), nullptr, "user", p->info().config_address(), diff);
|
||||
|
||||
send_to_lobby(diff);
|
||||
|
||||
|
@ -1526,18 +1514,18 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
return;
|
||||
// Everything below should only be processed if the game is already initialized.
|
||||
} else if(!g.level_init()) {
|
||||
WRN_SERVER << client_address(socket) << "\tReceived unknown data from: " << player.name()
|
||||
<< " (socket:" << socket << ") while the scenario wasn't yet initialized.\n"
|
||||
WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
|
||||
<< " (socket:" << p->socket() << ") while the scenario wasn't yet initialized.\n"
|
||||
<< data.output();
|
||||
return;
|
||||
// If the host is sending the next scenario data.
|
||||
} else if(const simple_wml::node* scenario = data.child("store_next_scenario")) {
|
||||
if(!g.is_owner(socket)) {
|
||||
if(!g.is_owner(p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!g.level_init()) {
|
||||
WRN_SERVER << client_address(socket) << "\tWarning: " << player.name()
|
||||
WRN_SERVER << p->client_ip() << "\tWarning: " << player.name()
|
||||
<< "\tsent [store_next_scenario] in game:\t\"" << g.name() << "\" (" << g.id()
|
||||
<< ", " << g.db_id() << ") while the scenario is not yet initialized.";
|
||||
return;
|
||||
|
@ -1548,7 +1536,7 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
user_handler_->db_update_game_end(uuid_, g.db_id(), g.get_replay_filename());
|
||||
}
|
||||
|
||||
g.new_scenario(socket);
|
||||
g.new_scenario(p);
|
||||
g.reset_last_synced_context_id();
|
||||
|
||||
// Record the full scenario in g.level()
|
||||
|
@ -1557,7 +1545,7 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
g.next_db_id();
|
||||
|
||||
if(g.description() == nullptr) {
|
||||
ERR_SERVER << client_address(socket) << "\tERROR: \"" << g.name() << "\" (" << g.id()
|
||||
ERR_SERVER << p->client_ip() << "\tERROR: \"" << g.name() << "\" (" << g.id()
|
||||
<< ", " << g.db_id() << ") is initialized but has no description_.\n";
|
||||
return;
|
||||
}
|
||||
|
@ -1568,12 +1556,12 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
if(const simple_wml::node* m = scenario->child("multiplayer")) {
|
||||
m->copy_into(desc);
|
||||
} else {
|
||||
WRN_SERVER << client_address(socket) << "\t" << player.name() << "\tsent scenario data in game:\t\""
|
||||
WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
|
||||
<< g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.\n";
|
||||
|
||||
delete_game(g.id());
|
||||
|
||||
send_server_message(socket,
|
||||
send_server_message(p,
|
||||
"The scenario data is missing the [multiplayer] tag which contains the game "
|
||||
"settings. Game aborted.", "error");
|
||||
return;
|
||||
|
@ -1597,17 +1585,17 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
// Tell everyone that the next scenario data is available.
|
||||
static simple_wml::document notify_next_scenario(
|
||||
"[notify_next_scenario]\n[/notify_next_scenario]\n", simple_wml::INIT_COMPRESSED);
|
||||
g.send_data(notify_next_scenario, socket);
|
||||
g.send_data(notify_next_scenario, p);
|
||||
|
||||
// Send the update of the game description to the lobby.
|
||||
update_game_in_lobby(g);
|
||||
return;
|
||||
// A mp client sends a request for the next scenario of a mp campaign.
|
||||
} else if(data.child("load_next_scenario")) {
|
||||
g.load_next_scenario(socket);
|
||||
g.load_next_scenario(p);
|
||||
return;
|
||||
} else if(data.child("start_game")) {
|
||||
if(!g.is_owner(socket)) {
|
||||
if(!g.is_owner(p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1617,8 +1605,8 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
// Send notification of the game starting immediately.
|
||||
// g.start_game() will send data that assumes
|
||||
// the [start_game] message has been sent
|
||||
g.send_data(data, socket);
|
||||
g.start_game(socket);
|
||||
g.send_data(data, p);
|
||||
g.start_game(p);
|
||||
|
||||
if(user_handler_) {
|
||||
const simple_wml::node& m = *g.level().root().child("multiplayer");
|
||||
|
@ -1659,13 +1647,13 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
update_game_in_lobby(g);
|
||||
return;
|
||||
} else if(data.child("leave_game")) {
|
||||
if(g.remove_player(socket)) {
|
||||
if(g.remove_player(p)) {
|
||||
delete_game(g.id());
|
||||
} else {
|
||||
auto description = g.description();
|
||||
|
||||
// After this line, the game object may be destroyed. Don't use `g`!
|
||||
player_connections_.modify(player_connections_.find(socket), std::bind(&player_record::enter_lobby, std::placeholders::_1));
|
||||
player_connections_.modify(p, std::bind(&player_record::enter_lobby, std::placeholders::_1));
|
||||
|
||||
// Only run this if the game object is still valid
|
||||
if(auto gStrong = g_ptr.lock()) {
|
||||
|
@ -1678,17 +1666,17 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
|
||||
|
||||
if(diff1 || diff2) {
|
||||
send_to_lobby(diff, socket);
|
||||
send_to_lobby(diff, p);
|
||||
}
|
||||
|
||||
// Send the player who has quit the gamelist.
|
||||
async_send_doc_queued(socket, games_and_users_list_);
|
||||
async_send_doc_queued(p->socket(), games_and_users_list_);
|
||||
}
|
||||
|
||||
return;
|
||||
// If this is data describing side changes by the host.
|
||||
} else if(const simple_wml::node* scenario_diff = data.child("scenario_diff")) {
|
||||
if(!g.is_owner(socket)) {
|
||||
if(!g.is_owner(p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1705,15 +1693,15 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
update_game_in_lobby(g);
|
||||
}
|
||||
|
||||
g.send_data(data, socket);
|
||||
g.send_data(data, p);
|
||||
return;
|
||||
// If a player changes his faction.
|
||||
} else if(data.child("change_faction")) {
|
||||
g.send_data(data, socket);
|
||||
g.send_data(data, p);
|
||||
return;
|
||||
// If the owner of a side is changing the controller.
|
||||
} else if(const simple_wml::node* change = data.child("change_controller")) {
|
||||
g.transfer_side_control(socket, *change);
|
||||
g.transfer_side_control(p, *change);
|
||||
if(g.describe_slots()) {
|
||||
update_game_in_lobby(g);
|
||||
}
|
||||
|
@ -1721,8 +1709,8 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
return;
|
||||
// If all observers should be muted. (toggles)
|
||||
} else if(data.child("muteall")) {
|
||||
if(!g.is_owner(socket)) {
|
||||
g.send_server_message("You cannot mute: not the game host.", socket);
|
||||
if(!g.is_owner(p)) {
|
||||
g.send_server_message("You cannot mute: not the game host.", p);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1730,21 +1718,21 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
return;
|
||||
// If an observer should be muted.
|
||||
} else if(const simple_wml::node* mute = data.child("mute")) {
|
||||
g.mute_observer(*mute, socket);
|
||||
g.mute_observer(*mute, p);
|
||||
return;
|
||||
// If an observer should be unmuted.
|
||||
} else if(const simple_wml::node* unmute = data.child("unmute")) {
|
||||
g.unmute_observer(*unmute, socket);
|
||||
g.unmute_observer(*unmute, p);
|
||||
return;
|
||||
// The owner is kicking/banning someone from the game.
|
||||
} else if(data.child("kick") || data.child("ban")) {
|
||||
bool ban = (data.child("ban") != nullptr);
|
||||
const socket_ptr user = (ban
|
||||
? g.ban_user(*data.child("ban"), socket)
|
||||
: g.kick_member(*data.child("kick"), socket));
|
||||
auto user { ban
|
||||
? g.ban_user(*data.child("ban"), p)
|
||||
: g.kick_member(*data.child("kick"), p)};
|
||||
|
||||
if(user) {
|
||||
player_connections_.modify(player_connections_.find(user), std::bind(&player_record::enter_lobby, std::placeholders::_1));
|
||||
player_connections_.modify(user.value(), std::bind(&player_record::enter_lobby, std::placeholders::_1));
|
||||
if(g.describe_slots()) {
|
||||
update_game_in_lobby(g, user);
|
||||
}
|
||||
|
@ -1752,21 +1740,21 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
// Send all other players in the lobby the update to the gamelist.
|
||||
simple_wml::document gamelist_diff;
|
||||
make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), gamelist_diff);
|
||||
make_change_diff(games_and_users_list_.root(), nullptr, "user", player_connections_.find(user)->info().config_address(), gamelist_diff);
|
||||
make_change_diff(games_and_users_list_.root(), nullptr, "user", user.value()->info().config_address(), gamelist_diff);
|
||||
|
||||
send_to_lobby(gamelist_diff, socket);
|
||||
send_to_lobby(gamelist_diff, p);
|
||||
|
||||
// Send the removed user the lobby game list.
|
||||
async_send_doc_queued(user, games_and_users_list_);
|
||||
async_send_doc_queued(user.value()->socket(), games_and_users_list_);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if(const simple_wml::node* unban = data.child("unban")) {
|
||||
g.unban_user(*unban, socket);
|
||||
g.unban_user(*unban, p);
|
||||
return;
|
||||
// If info is being provided about the game state.
|
||||
} else if(const simple_wml::node* info = data.child("info")) {
|
||||
if(!g.is_player(socket)) {
|
||||
if(!g.is_player(p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1785,26 +1773,26 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
// Notify the game of the commands, and if it changes
|
||||
// the description, then sync the new description
|
||||
// to players in the lobby.
|
||||
if(g.process_turn(data, socket)) {
|
||||
if(g.process_turn(data, p)) {
|
||||
update_game_in_lobby(g);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if(data.child("whiteboard")) {
|
||||
g.process_whiteboard(data, socket);
|
||||
g.process_whiteboard(data, p);
|
||||
return;
|
||||
} else if(data.child("change_turns_wml")) {
|
||||
g.process_change_turns_wml(data, socket);
|
||||
g.process_change_turns_wml(data, p);
|
||||
update_game_in_lobby(g);
|
||||
return;
|
||||
} else if(simple_wml::node* sch = data.child("request_choice")) {
|
||||
g.handle_choice(*sch, socket);
|
||||
g.handle_choice(*sch, p);
|
||||
return;
|
||||
} else if(data.child("message")) {
|
||||
g.process_message(data, socket);
|
||||
g.process_message(data, p);
|
||||
return;
|
||||
} else if(data.child("stop_updates")) {
|
||||
g.send_data(data, socket);
|
||||
g.send_data(data, p);
|
||||
return;
|
||||
} else if(simple_wml::node* request = data.child("game_history_request")) {
|
||||
if(user_handler_) {
|
||||
|
@ -1842,7 +1830,7 @@ void server::handle_player_in_game(socket_ptr socket, simple_wml::document& data
|
|||
return;
|
||||
}
|
||||
|
||||
WRN_SERVER << client_address(socket) << "\tReceived unknown data from: " << player.name() << " (socket:" << socket
|
||||
WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name() << " (socket:" << p->socket()
|
||||
<< ") in game: \"" << g.name() << "\" (" << g.id() << ", " << g.db_id() << ")\n"
|
||||
<< data.output();
|
||||
}
|
||||
|
@ -1858,23 +1846,19 @@ void server::send_server_message(socket_ptr socket, const std::string& message,
|
|||
async_send_doc_queued(socket, server_message);
|
||||
}
|
||||
|
||||
void server::remove_player(socket_ptr socket)
|
||||
void server::disconnect_player(player_iterator player)
|
||||
{
|
||||
std::string ip;
|
||||
player->socket()->shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
|
||||
}
|
||||
|
||||
auto iter = player_connections_.find(socket);
|
||||
if(iter == player_connections_.end()) {
|
||||
return;
|
||||
} else {
|
||||
// client_address() is very likely to return <unknown address> at this point
|
||||
// so we remember ip in player_connections_
|
||||
ip = iter->saved_client_ip();
|
||||
}
|
||||
void server::remove_player(player_iterator iter)
|
||||
{
|
||||
std::string ip = iter->client_ip();
|
||||
|
||||
const std::shared_ptr<game> g = iter->get_game();
|
||||
bool game_ended = false;
|
||||
if(g) {
|
||||
game_ended = g->remove_player(socket, true, false);
|
||||
game_ended = g->remove_player(iter, true, false);
|
||||
}
|
||||
|
||||
const simple_wml::node::child_list& users = games_and_users_list_.root().children("user");
|
||||
|
@ -1884,7 +1868,7 @@ void server::remove_player(socket_ptr socket)
|
|||
// Notify other players in lobby
|
||||
simple_wml::document diff;
|
||||
if(make_delete_diff(games_and_users_list_.root(), nullptr, "user", iter->info().config_address(), diff)) {
|
||||
send_to_lobby(diff, socket);
|
||||
send_to_lobby(diff, iter);
|
||||
}
|
||||
|
||||
games_and_users_list_.root().remove_child("user", index);
|
||||
|
@ -1902,39 +1886,37 @@ void server::remove_player(socket_ptr socket)
|
|||
|
||||
player_connections_.erase(iter);
|
||||
|
||||
if(socket->is_open()) {
|
||||
socket->close();
|
||||
}
|
||||
|
||||
if(lan_server_ && player_connections_.size() == 0)
|
||||
start_lan_server_timer();
|
||||
|
||||
if(game_ended) delete_game(g->id());
|
||||
}
|
||||
|
||||
void server::send_to_lobby(simple_wml::document& data, socket_ptr exclude)
|
||||
void server::send_to_lobby(simple_wml::document& data, utils::optional<player_iterator> exclude)
|
||||
{
|
||||
for(const auto& player : player_connections_.get<game_t>().equal_range(0)) {
|
||||
if(player.socket() != exclude) {
|
||||
async_send_doc_queued(player.socket(), data);
|
||||
for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
|
||||
auto player { player_connections_.iterator_to(p) };
|
||||
if(player != exclude) {
|
||||
async_send_doc_queued(player->socket(), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void server::send_server_message_to_lobby(const std::string& message, socket_ptr exclude)
|
||||
void server::send_server_message_to_lobby(const std::string& message, utils::optional<player_iterator> exclude)
|
||||
{
|
||||
for(const auto& player : player_connections_.get<game_t>().equal_range(0)) {
|
||||
if(player.socket() != exclude) {
|
||||
send_server_message(player.socket(), message, "alert");
|
||||
for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
|
||||
auto player { player_connections_.iterator_to(p) };
|
||||
if(player != exclude) {
|
||||
send_server_message(player, message, "alert");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void server::send_server_message_to_all(const std::string& message, socket_ptr exclude)
|
||||
void server::send_server_message_to_all(const std::string& message, utils::optional<player_iterator> exclude)
|
||||
{
|
||||
for(const auto& player : player_connections_) {
|
||||
if(player.socket() != exclude) {
|
||||
send_server_message(player.socket(), message, "alert");
|
||||
for(auto player = player_connections_.begin(); player != player_connections_.end(); ++player) {
|
||||
if(player != exclude) {
|
||||
send_server_message(player, message, "alert");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2172,7 +2154,7 @@ void server::roll_handler(const std::string& issuer_name,
|
|||
|
||||
auto g_ptr = player_ptr->get_game();
|
||||
if(g_ptr) {
|
||||
g_ptr->send_server_message_to_all(issuer_name + " rolled a die [1 - " + parameters + "] and got a " + value + ".", player_ptr->socket());
|
||||
g_ptr->send_server_message_to_all(issuer_name + " rolled a die [1 - " + parameters + "] and got a " + value + ".", player_connections_.project<0>(player_ptr));
|
||||
} else {
|
||||
*out << " (The result is shown to others only in a game.)";
|
||||
}
|
||||
|
@ -2361,7 +2343,7 @@ void server::status_handler(
|
|||
if(utils::isvalid_username(parameters)) {
|
||||
for(const auto& player : player_connections_) {
|
||||
if(utf8::lowercase(parameters) == utf8::lowercase(player.info().name())) {
|
||||
parameters = client_address(player.socket());
|
||||
parameters = player.client_ip();
|
||||
found_something = true;
|
||||
break;
|
||||
}
|
||||
|
@ -2378,7 +2360,7 @@ void server::status_handler(
|
|||
const bool match_ip = (std::count(parameters.begin(), parameters.end(), '.') >= 1);
|
||||
for(const auto& player : player_connections_) {
|
||||
if(parameters.empty() || parameters == "*" ||
|
||||
(match_ip && utils::wildcard_string_match(client_address(player.socket()), parameters)) ||
|
||||
(match_ip && utils::wildcard_string_match(player.client_ip(), parameters)) ||
|
||||
(!match_ip && utils::wildcard_string_match(utf8::lowercase(player.info().name()), utf8::lowercase(parameters)))
|
||||
) {
|
||||
found_something = true;
|
||||
|
@ -2402,16 +2384,16 @@ void server::clones_handler(const std::string& /*issuer_name*/,
|
|||
std::set<std::string> clones;
|
||||
|
||||
for(auto it = player_connections_.begin(); it != player_connections_.end(); ++it) {
|
||||
if(clones.find(client_address(it->socket())) != clones.end()) {
|
||||
if(clones.find(it->client_ip()) != clones.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for(auto clone = std::next(it); clone != player_connections_.end(); ++clone) {
|
||||
if(client_address(it->socket()) == client_address(clone->socket())) {
|
||||
if(it->client_ip() == clone->client_ip()) {
|
||||
if(!found) {
|
||||
found = true;
|
||||
clones.insert(client_address(it->socket()));
|
||||
clones.insert(it->client_ip());
|
||||
*out << std::endl << player_status(*it);
|
||||
}
|
||||
|
||||
|
@ -2502,7 +2484,7 @@ void server::ban_handler(
|
|||
banned = true;
|
||||
}
|
||||
|
||||
const std::string ip = client_address(player.socket());
|
||||
const std::string ip = player.client_ip();
|
||||
*out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
|
||||
}
|
||||
}
|
||||
|
@ -2564,7 +2546,7 @@ void server::kickban_handler(
|
|||
}
|
||||
|
||||
std::string dummy_group;
|
||||
std::vector<socket_ptr> users_to_kick;
|
||||
std::vector<player_iterator> users_to_kick;
|
||||
|
||||
// if we find a '.' consider it an ip mask
|
||||
/** @todo FIXME: make a proper check for valid IPs. */
|
||||
|
@ -2573,23 +2555,23 @@ void server::kickban_handler(
|
|||
|
||||
*out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
|
||||
|
||||
for(const auto& player : player_connections_) {
|
||||
if(utils::wildcard_string_match(client_address(player.socket()), target)) {
|
||||
users_to_kick.push_back(player.socket());
|
||||
for(player_iterator player = player_connections_.begin(); player != player_connections_.end(); ++player) {
|
||||
if(utils::wildcard_string_match(player->client_ip(), target)) {
|
||||
users_to_kick.push_back(player);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(const auto& player : player_connections_) {
|
||||
if(utils::wildcard_string_match(player.info().name(), target)) {
|
||||
for(player_iterator player = player_connections_.begin(); player != player_connections_.end(); ++player) {
|
||||
if(utils::wildcard_string_match(player->info().name(), target)) {
|
||||
if(banned) {
|
||||
*out << "\n";
|
||||
} else {
|
||||
banned = true;
|
||||
}
|
||||
|
||||
const std::string ip = client_address(player.socket());
|
||||
const std::string ip = player->client_ip();
|
||||
*out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
|
||||
users_to_kick.push_back(player.socket());
|
||||
users_to_kick.push_back(player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2613,10 +2595,10 @@ void server::kickban_handler(
|
|||
}
|
||||
}
|
||||
|
||||
for(const auto& user : users_to_kick) {
|
||||
*out << "\nKicked " << player_connections_.find(user)->info().name() << " (" << client_address(user) << ").";
|
||||
async_send_error(user, "You have been banned. Reason: " + reason);
|
||||
remove_player(user);
|
||||
for(auto user : users_to_kick) {
|
||||
*out << "\nKicked " << user->info().name() << " (" << user->client_ip() << ").";
|
||||
async_send_error(user->socket(), "You have been banned. Reason: " + reason);
|
||||
disconnect_player(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2674,7 +2656,7 @@ void server::gban_handler(
|
|||
banned = true;
|
||||
}
|
||||
|
||||
const std::string ip = client_address(player.socket());
|
||||
const std::string ip = player.client_ip();
|
||||
*out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, group, target);
|
||||
}
|
||||
}
|
||||
|
@ -2753,27 +2735,27 @@ void server::kick_handler(const std::string& /*issuer_name*/,
|
|||
// if we find a '.' consider it an ip mask
|
||||
const bool match_ip = (std::count(kick_mask.begin(), kick_mask.end(), '.') >= 1);
|
||||
|
||||
std::vector<socket_ptr> users_to_kick;
|
||||
for(const auto& player : player_connections_) {
|
||||
if((match_ip && utils::wildcard_string_match(client_address(player.socket()), kick_mask)) ||
|
||||
(!match_ip && utils::wildcard_string_match(player.info().name(), kick_mask))
|
||||
std::vector<player_iterator> users_to_kick;
|
||||
for(player_iterator player = player_connections_.begin(); player != player_connections_.end(); ++player) {
|
||||
if((match_ip && utils::wildcard_string_match(player->client_ip(), kick_mask)) ||
|
||||
(!match_ip && utils::wildcard_string_match(player->info().name(), kick_mask))
|
||||
) {
|
||||
users_to_kick.push_back(player.socket());
|
||||
users_to_kick.push_back(player);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& socket : users_to_kick) {
|
||||
for(const auto& player : users_to_kick) {
|
||||
if(kicked) {
|
||||
*out << "\n";
|
||||
} else {
|
||||
kicked = true;
|
||||
}
|
||||
|
||||
*out << "Kicked " << player_connections_.find(socket)->name() << " (" << client_address(socket) << "). '"
|
||||
*out << "Kicked " << player->name() << " (" << player->client_ip() << "). '"
|
||||
<< kick_message << "'";
|
||||
|
||||
async_send_error(socket, kick_message);
|
||||
remove_player(socket);
|
||||
async_send_error(player->socket(), kick_message);
|
||||
disconnect_player(player);
|
||||
}
|
||||
|
||||
if(!kicked) {
|
||||
|
@ -2832,7 +2814,7 @@ void server::searchlog_handler(const std::string& /*issuer_name*/,
|
|||
found_something = true;
|
||||
auto player = player_connections_.get<name_t>().find(username);
|
||||
|
||||
if(player != player_connections_.get<name_t>().end() && client_address(player->socket()) == ip) {
|
||||
if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
|
||||
*out << std::endl << player_status(*player);
|
||||
} else {
|
||||
*out << "\n'" << username << "' @ " << ip
|
||||
|
@ -2932,7 +2914,7 @@ void server::delete_game(int gameid, const std::string& reason)
|
|||
}
|
||||
}
|
||||
|
||||
void server::update_game_in_lobby(const wesnothd::game& g, const socket_ptr& exclude)
|
||||
void server::update_game_in_lobby(const wesnothd::game& g, utils::optional<player_iterator> exclude)
|
||||
{
|
||||
simple_wml::document diff;
|
||||
if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), diff)) {
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "server/common/server_base.hpp"
|
||||
#include "server/wesnothd/player_connection.hpp"
|
||||
|
||||
#include "utils/optional_fwd.hpp"
|
||||
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <random>
|
||||
|
@ -46,26 +48,28 @@ private:
|
|||
bool accepting_connections() const { return !graceful_restart; }
|
||||
|
||||
void handle_player(boost::asio::yield_context yield, socket_ptr socket, const player& player);
|
||||
void handle_player_in_lobby(socket_ptr socket, simple_wml::document& data);
|
||||
void handle_player_in_game(socket_ptr socket, simple_wml::document& data);
|
||||
void handle_whisper(socket_ptr socket, simple_wml::node& whisper);
|
||||
void handle_query(socket_ptr socket, simple_wml::node& query);
|
||||
void handle_nickserv(socket_ptr socket, simple_wml::node& nickserv);
|
||||
void handle_message(socket_ptr socket, simple_wml::node& message);
|
||||
void handle_create_game(socket_ptr socket, simple_wml::node& create_game);
|
||||
void create_game(player_record& host, simple_wml::node& create_game);
|
||||
void handle_player_in_lobby(player_iterator player, simple_wml::document& doc);
|
||||
void handle_player_in_game(player_iterator player, simple_wml::document& doc);
|
||||
void handle_whisper(player_iterator player, simple_wml::node& whisper);
|
||||
void handle_query(player_iterator player, simple_wml::node& query);
|
||||
void handle_nickserv(player_iterator player, simple_wml::node& nickserv);
|
||||
void handle_message(player_iterator player, simple_wml::node& message);
|
||||
void handle_create_game(player_iterator player, simple_wml::node& create_game);
|
||||
void cleanup_game(game*); // deleter for shared_ptr
|
||||
void handle_join_game(socket_ptr socket, simple_wml::node& join);
|
||||
void remove_player(socket_ptr socket);
|
||||
void handle_join_game(player_iterator player, simple_wml::node& join);
|
||||
void disconnect_player(player_iterator player);
|
||||
void remove_player(player_iterator player);
|
||||
|
||||
void send_server_message(socket_ptr socket, const std::string& message, const std::string& type);
|
||||
void send_to_lobby(simple_wml::document& data, socket_ptr exclude = socket_ptr());
|
||||
void send_server_message_to_lobby(const std::string& message, socket_ptr exclude = socket_ptr());
|
||||
void send_server_message_to_all(const std::string& message, socket_ptr exclude = socket_ptr());
|
||||
void send_server_message(player_iterator player, const std::string& message, const std::string& type) {
|
||||
send_server_message(player->socket(), message, type);
|
||||
}
|
||||
void send_to_lobby(simple_wml::document& data, utils::optional<player_iterator> exclude = {});
|
||||
void send_server_message_to_lobby(const std::string& message, utils::optional<player_iterator> exclude = {});
|
||||
void send_server_message_to_all(const std::string& message, utils::optional<player_iterator> exclude = {});
|
||||
|
||||
bool player_is_in_game(socket_ptr socket) const
|
||||
{
|
||||
return player_connections_.find(socket)->get_game() != nullptr;
|
||||
bool player_is_in_game(player_iterator player) const {
|
||||
return player->get_game() != nullptr;
|
||||
}
|
||||
|
||||
wesnothd::ban_manager ban_manager_;
|
||||
|
@ -190,7 +194,7 @@ private:
|
|||
|
||||
void delete_game(int, const std::string& reason="");
|
||||
|
||||
void update_game_in_lobby(const wesnothd::game& g, const socket_ptr& exclude=socket_ptr());
|
||||
void update_game_in_lobby(const game& g, utils::optional<player_iterator> exclude = {});
|
||||
|
||||
void start_new_server();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue