Initial step for splitting mp::connect into gui and engine.

This commit is contained in:
Andrius Silinskas 2013-07-21 19:40:12 +01:00
parent 77523d86d6
commit 13e22fd6f1
15 changed files with 1676 additions and 1381 deletions

View file

@ -18970,6 +18970,14 @@
RelativePath="..\..\src\multiplayer_connect.hpp"
>
</File>
<File
RelativePath="..\..\src\multiplayer_connect_engine.cpp"
>
</File>
<File
RelativePath="..\..\src\multiplayer_connect_engine.hpp"
>
</File>
<File
RelativePath="..\..\src\multiplayer_create.cpp"
>

View file

@ -772,6 +772,7 @@ set(wesnoth-main_SRC
multiplayer.cpp
multiplayer_configure.cpp
multiplayer_connect.cpp
multiplayer_connect_engine.cpp
multiplayer_create.cpp
multiplayer_create_engine.cpp
multiplayer_lobby.cpp

View file

@ -440,6 +440,7 @@ wesnoth_sources = Split("""
multiplayer.cpp
multiplayer_configure.cpp
multiplayer_connect.cpp
multiplayer_connect_engine.cpp
multiplayer_create.cpp
multiplayer_create_engine.cpp
multiplayer_lobby.cpp

View file

@ -37,6 +37,13 @@ leader_list_manager::leader_list_manager(gui::combo* leader_combo,
{
}
void leader_list_manager::init_combos(gui::combo* leader_combo,
gui::combo* gender_combo)
{
leader_combo_ = leader_combo;
gender_combo_ = gender_combo;
}
void leader_list_manager::set_side_list(
const std::vector<const config *> &side_list)
{

View file

@ -29,6 +29,7 @@ public:
leader_list_manager(gui::combo* leader_combo = NULL,
gui::combo* gender_combo = NULL);
void init_combos(gui::combo* leader_combo, gui::combo* gender_combo);
void set_side_list(const std::vector<const config *> &side_list);
void set_leader_combo(gui::combo* combo);
void set_gender_combo(gui::combo* combo);

View file

@ -32,7 +32,7 @@ static lg::log_domain log_config("config");
namespace mp {
config initial_level_config(game_display& disp, mp_game_settings& params,
config initial_level_config(game_display& disp, const mp_game_settings& params,
game_state& state)
{
config level;
@ -48,8 +48,6 @@ config initial_level_config(game_display& disp, mp_game_settings& params,
return config();
}
} else {
level.clear();
params.mp_scenario = params.scenario_data["id"].str();
level.merge_with(params.scenario_data);
level["turns"] = params.num_turns;
level.add_child("multiplayer", params.to_config());
@ -60,7 +58,6 @@ config initial_level_config(game_display& disp, mp_game_settings& params,
// params.options.find_child("multiplayer", "id",
// params.mp_scenario)), 0);
params.hash = level.hash();
level["next_underlying_unit_id"] = 0;
n_unit::id_manager::instance().clear();

View file

@ -22,7 +22,7 @@ class game_state;
namespace mp {
config initial_level_config(game_display& disp, mp_game_settings& params,
config initial_level_config(game_display& disp, const mp_game_settings& params,
game_state& state);
config next_level_config(const config& level, game_state& state);

File diff suppressed because it is too large Load diff

View file

@ -17,13 +17,14 @@
#ifndef MULTIPLAYER_CONNECT_H_INCLUDED
#define MULTIPLAYER_CONNECT_H_INCLUDED
#include "commandline_options.hpp"
#include "gamestatus.hpp"
#include "leader_list.hpp"
#include "multiplayer_connect_engine.hpp"
#include "multiplayer_ui.hpp"
#include "widgets/combo_drag.hpp"
#include "widgets/scrollpane.hpp"
#include "widgets/slider.hpp"
#include "widgets/combo_drag.hpp"
#include "commandline_options.hpp"
namespace ai {
struct description;
@ -34,25 +35,10 @@ namespace mp {
class connect : public mp::ui
{
public:
struct connected_user {
connected_user(const std::string& name, mp::controller controller,
network::connection connection) :
name(name), controller(controller), connection(connection)
{};
std::string name;
mp::controller controller;
network::connection connection;
operator std::string() const
{
return name;
}
};
typedef std::vector<connected_user> connected_user_list;
class side {
public:
side(connect& parent, const config& cfg, int index);
side(connect& parent, side_engine_ptr engine);
side(const side& a);
@ -63,52 +49,8 @@ public:
/** Returns true if this side changed since last call to changed(). */
bool changed();
/**
* Gets a config object representing this side.
*
* If include_leader is set to true, the config objects include the
* "type=" defining the leader type, else it does not.
*/
config get_config() const;
/**
* Returns true if this side is waiting for a network player and
* players allowed.
*/
bool available(const std::string& name = "") const;
/** Returns true, if the player has chosen his leader and this side is ready for the game to start */
bool ready_for_start() const;
/** Return true if players are allowed to take this side. */
bool allow_player() const;
/** Sets the controller of a side. */
void set_controller(mp::controller controller);
mp::controller get_controller() const;
/** Adds an user to the user list combo. */
void update_user_list();
/** Returns the username of this side. */
const std::string& get_current_player() const
{ return current_player_; }
int get_index();
void set_index(int index);
const std::string& get_player_id() const;
/** Sets the username of this side. */
void set_player_id(const std::string& player_id);
/** Sets if the joining player has chosen his leader. */
void set_ready_for_start(bool ready_for_start);
int get_team();
void set_team(int team);
void update_user_list(const std::vector<std::string>& name_list);
/**
* Imports data from the network into this side, and updates the UI
@ -119,12 +61,11 @@ public:
/** Resets this side to its default state, and updates the UI accordingly. */
void reset(mp::controller controller);
/** Resolves the random leader / factions. */
void resolve_random();
void set_faction_commandline(std::string faction_name);
void set_controller_commandline(std::string controller_name);
void set_ai_algorithm_commandline(std::string algorithm_name);
void hide_ai_algorithm_combo(bool invis);
side_engine_ptr engine() { return engine_; }
const side_engine_ptr engine() const { return engine_; }
private:
void init_ai_algorithm_combo();
void update_ai_algorithm_combo() {hide_ai_algorithm_combo(parent_->hidden());}
@ -134,10 +75,6 @@ public:
void update_controller_ui();
void update_ui();
int selected_faction_index() const;
config& init_side_config(config& side);
/**
* The mp::connect widget owning this mp::connect::side.
*
@ -145,34 +82,7 @@ public:
*/
connect* parent_;
/**
* A non-const config. Be careful not to insert keys when only reading.
*
* (Use cfg_.get_attribute().)
*/
config cfg_;
// All factions which could be played by a side (including Random).
std::vector<const config*> available_factions_;
// All factions which a side can choose.
std::vector<const config*> choosable_factions_;
const config* current_faction_;
// Configurable variables
int index_;
std::string id_;
std::string player_id_;
std::string save_id_;
std::string current_player_;
mp::controller controller_;
int team_;
int color_;
int gold_;
int income_;
std::string leader_;
std::string gender_;
std::string ai_algorithm_;
bool ready_for_start_;
side_engine_ptr engine_;
// Flags for controlling the dialog widgets of the game lobby
bool gold_lock_;
@ -195,22 +105,14 @@ public:
gui::label label_gold_;
gui::label label_income_;
bool allow_player_;
bool allow_changes_;
bool enabled_;
bool changed_;
leader_list_manager llm_;
};
friend class side;
typedef std::vector<side> side_list;
/**
* Pointer to the display
*/
game_display* disp_;
connect(game_display& disp, const config& game_config, chat& c,
config& gamelist, const mp_game_settings& params,
mp::controller default_controller, bool local_players_only = false);
@ -223,14 +125,15 @@ public:
* Returns the game state, which contains all information about the current
* scenario.
*/
const game_state& get_state();
const game_state& get_state() const { return engine_.state(); }
/**
* Updates the current game state, resolves random factions, and sends a
* "start game" message to the network.
*/
void start_game();
void start_game_commandline(const commandline_options& cmdline_opts);
void start_game() { engine_.start_game(); }
void start_game_commandline(const commandline_options& cmdline_opts)
{ engine_.start_game_commandline(cmdline_opts); }
protected:
virtual void layout_children(const SDL_Rect& rect);
@ -243,64 +146,20 @@ protected:
virtual void hide_children(bool hide=true);
private:
// Those 2 functions are actually the steps of the (complex)
// construction of this class.
/**
* Called by the constructor to initialize the game from a
* create::parameters structure.
*/
void load_game();
void lists_init();
/** Convenience function. */
config* current_config();
/** Updates the level_ variable to reflect the sides in the sides_ vector. */
void update_level();
/** Updates the level, and send a diff to the clients. */
void update_and_send_diff(bool update_time_of_day = false);
/** Returns true if there still are sides available for this game. */
bool sides_available() const;
/** Returns true if all sides are ready to start the game. */
bool sides_ready() const;
/**
* Validates whether the game can be started.
*
* returns Can the game be started?
*/
bool can_start_game() const;
/**
* Updates the state of the player list, the launch button and of the start
* game label, to reflect the actual state.
*/
void update_playerlist_state(bool silent=true);
/** Returns the index of a player, from its id, or -1 if the player was not found. */
connected_user_list::iterator find_player(const std::string& id);
/** Returns the side which is taken by a given player, or -1 if none was found. */
int find_player_side(const std::string& id) const;
/** Adds a player. */
void update_user_combos();
bool local_only_;
config level_;
/** This is the "game state" object which is created by this dialog. */
game_state state_;
mp_game_settings params_;
/** The list of available sides for the current era. */
std::vector<const config *> era_sides_;
const mp_game_settings params_;
// Lists used for combos
std::vector<std::string> player_types_;
@ -308,18 +167,12 @@ private:
std::vector<std::string> player_colors_;
std::vector<ai::description*> ai_algorithms_;
// team_name list and "Team" prefix
std::vector<std::string> team_names_;
std::vector<std::string> user_team_names_;
const std::string team_prefix_;
side_list sides_;
connected_user_list users_;
gui::label waiting_label_;
controller default_controller_;
// Widgets
gui::scrollpane scroll_pane_;
@ -336,6 +189,7 @@ private:
gui::drop_group_manager_ptr combo_control_group_;
connect_engine engine_;
}; // end class connect
} // end namespace mp

View file

@ -0,0 +1,971 @@
/*
Copyright (C) 2013 by Andrius Silinskas <silinskas.andrius@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
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 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "multiplayer_connect_engine.hpp"
#include "ai/configuration.hpp"
#include "formula_string_utils.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "log.hpp"
#include "multiplayer_ui.hpp"
#include "mp_game_utils.hpp"
#include "tod_manager.hpp"
#include <boost/foreach.hpp>
static lg::log_domain log_config("config");
#define LOG_CF LOG_STREAM(info, log_config)
#define WRN_CF LOG_STREAM(warn, log_config)
#define ERR_CF LOG_STREAM(err, log_config)
static lg::log_domain log_mp_connect_engine("mp/connect/engine");
#define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
#define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
namespace {
const char* controller_names[] = {
"network",
"human",
"ai",
"null",
"reserved"
};
}
namespace mp {
connect_engine::connect_engine(game_display& disp, controller mp_controller,
const mp_game_settings& params) :
level_(),
state_(),
disp_(disp),
params_(params),
side_engines_(),
era_factions_(),
users_(),
mp_controller_(mp_controller),
team_names_(),
user_team_names_()
{
level_ = initial_level_config(disp, params, state_);
if (level_.empty()) {
return;
}
BOOST_FOREACH(const config &era,
level_.child("era").child_range("multiplayer_side")) {
era_factions_.push_back(&era);
}
// Adds the current user as default user.
users_.push_back(connected_user(preferences::login(), CNTR_LOCAL, 0));
}
connect_engine::~connect_engine()
{
}
config* connect_engine::current_config() {
config* cfg_level = NULL;
// It might make sense to invent a mechanism of some sort to check
// whether a config node contains information
// that you can load from(side information, specifically).
config &snapshot = level_.child("snapshot");
if (snapshot && snapshot.child("side")) {
// Savegame.
cfg_level = &snapshot;
} else if (!level_.child("side")) {
// Start-of-scenario save,
// the info has to be taken from the starting_pos.
cfg_level = &state_.replay_start();
} else {
// Fresh game, no snapshot available.
cfg_level = &level_;
}
return cfg_level;
}
void connect_engine::add_side_engine(side_engine_ptr engine)
{
side_engines_.push_back(engine);
}
void connect_engine::update_level()
{
DBG_MP << "updating level" << std::endl;
level_.clear_children("side");
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
level_.add_child("side", side->new_config());
}
}
void connect_engine::update_and_send_diff(bool update_time_of_day)
{
config old_level = level_;
update_level();
if (update_time_of_day) {
// Set random start ToD.
tod_manager tod_mng(level_, level_["turns"]);
}
config diff = level_.get_diff(old_level);
if (!diff.empty()) {
config scenario_diff;
scenario_diff.add_child("scenario_diff", diff);
network::send_data(scenario_diff, 0);
}
}
bool connect_engine::sides_available() const
{
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
if (side->available()) {
return true;
}
}
return false;
}
bool connect_engine::can_start_game() const
{
// First check if all sides are ready to start the game.
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
if (!side->ready_for_start()) {
DBG_MP << "not all sides are ready, side " <<
side->new_config().get("side")->str() << " not ready" <<
std::endl;
return false;
}
}
DBG_MP << "all sides are ready" << std::endl;
/*
* If at least one human player is slotted with a player/ai we're allowed
* to start. Before used a more advanced test but it seems people are
* creative in what is used in multiplayer [1] so use a simpler test now.
* [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
*/
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
if (side->mp_controller() != CNTR_EMPTY) {
if (side->allow_player()) {
return true;
}
}
}
return false;
}
void connect_engine::start_game()
{
DBG_MP << "starting a new game" << std::endl;
// Resolves the "random faction", "random gender" and "random message"
// Must be done before shuffle sides, or some cases will cause errors
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
side->resolve_random();
}
// Shuffle sides (check preferences and if it is a re-loaded game).
// Must be done after resolve_random() or shuffle sides, or they won't work.
if (preferences::shuffle_sides() && !(level_.child("snapshot") &&
level_.child("snapshot").child("side"))) {
// Only playable sides should be shuffled.
std::vector<int> playable_sides;
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
if (side->allow_player()) {
playable_sides.push_back(side->index());
}
}
// Fisher-Yates shuffle.
for (int i = playable_sides.size(); i > 1; i--)
{
int j_side = playable_sides[get_random() % i];
int i_side = playable_sides[i - 1];
int tmp_index = side_engines_[j_side]->index();
side_engines_[j_side]->set_index(side_engines_[i_side]->index());
side_engines_[i_side]->set_index(tmp_index);
int tmp_team = side_engines_[j_side]->team();
side_engines_[j_side]->set_team(side_engines_[i_side]->team());
side_engines_[i_side]->set_team(tmp_team);
// This is needed otherwise fog bugs will appear.
side_engine_ptr tmp_side = side_engines_[j_side];
side_engines_[j_side] = side_engines_[i_side];
side_engines_[i_side] = tmp_side;
}
}
// Make other clients not show the results of resolve_random().
config lock("stop_updates");
network::send_data(lock, 0);
update_and_send_diff(true);
// Build the gamestate object after updating the level.
level_to_gamestate(level_, state_);
network::send_data(config("start_game"), 0);
}
void connect_engine::start_game_commandline(
const commandline_options& cmdline_opts)
{
DBG_MP << "starting a new game in commandline mode" << std::endl;
typedef boost::tuple<unsigned int, std::string> mp_option;
unsigned num = 0;
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
num++;
// Set the faction, if commandline option is given.
if (cmdline_opts.multiplayer_side) {
BOOST_FOREACH(const mp_option& option,
*cmdline_opts.multiplayer_side) {
if (option.get<0>() == num) {
DBG_MP << "\tsetting side " << option.get<0>() <<
"\tfaction: " << option.get<1>() << std::endl;
side->set_faction_commandline(option.get<1>());
}
}
}
// Set the controller, if commandline option is given.
if (cmdline_opts.multiplayer_controller) {
BOOST_FOREACH(const mp_option& option,
*cmdline_opts.multiplayer_controller) {
if (option.get<0>() == num) {
DBG_MP << "\tsetting side " << option.get<0>() <<
"\tfaction: " << option.get<1>() << std::endl;
side->set_controller_commandline(option.get<1>());
}
}
}
// Set AI algorithm to RCA AI for all sides,
// then override if commandline option was given.
side->set_ai_algorithm_commandline("ai_default_rca");
if (cmdline_opts.multiplayer_algorithm) {
BOOST_FOREACH(const mp_option& option,
*cmdline_opts.multiplayer_algorithm) {
if (option.get<0>() == num) {
DBG_MP << "\tsetting side " << option.get<0>() <<
"\tfaction: " << option.get<1>() << std::endl;
side->set_ai_algorithm_commandline(option.get<1>());
}
}
}
// Finally, resolve "random faction",
// "random gender" and "random message", if any remains unresolved.
side->resolve_random();
} // end top-level loop
update_and_send_diff(true);
// Update sides with commandline parameters.
if (cmdline_opts.multiplayer_turns) {
DBG_MP << "\tsetting turns: " << cmdline_opts.multiplayer_turns <<
std::endl;
level_["turns"] = *cmdline_opts.multiplayer_turns;
}
BOOST_FOREACH(config &side, level_.child_range("side"))
{
if (cmdline_opts.multiplayer_ai_config) {
BOOST_FOREACH(const mp_option& option,
*cmdline_opts.multiplayer_ai_config) {
if (option.get<0>() == side["side"].to_unsigned()) {
DBG_MP << "\tsetting side " << side["side"] <<
"\tai_config: " << option.get<1>() << std::endl;
side["ai_config"] = option.get<1>();
}
}
}
// Having hard-coded values here is undesirable,
// but that's how it is done in the MP lobby
// part of the code also. Should be replaced by settings/constants in both places
if (cmdline_opts.multiplayer_ignore_map_settings) {
side["gold"] = 100;
side["income"] = 1;
}
typedef boost::tuple<unsigned int, std::string, std::string>
mp_parameter;
if (cmdline_opts.multiplayer_parm) {
BOOST_FOREACH(const mp_parameter& parameter,
*cmdline_opts.multiplayer_parm) {
if (parameter.get<0>() == side["side"].to_unsigned()) {
DBG_MP << "\tsetting side " << side["side"] << " " <<
parameter.get<1>() << ": " << parameter.get<2>() << std::endl;
side[parameter.get<1>()] = parameter.get<2>();
}
}
}
}
// Build the gamestate object after updating the level
level_to_gamestate(level_, state_);
network::send_data(config("start_game"), 0);
}
void connect_engine::process_network_connection(const network::connection sock)
{
network::send_data(config("join_game"), 0);
network::send_data(level_, sock);
}
connected_user_list::iterator connect_engine::find_player(const std::string& id)
{
connected_user_list::iterator itor;
for (itor = users_.begin(); itor != users_.end(); ++itor) {
if (itor->name == id) {
break;
}
}
return itor;
}
int connect_engine::find_player_side(const std::string& id) const
{
size_t i = 0;
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
if (side->player_id() == id) {
break;
}
i++;
}
if (i >= side_engines_.size()) {
return -1;
}
return i;
}
side_engine::side_engine(const config& cfg, connect_engine& parent_engine,
const int index) :
cfg_(cfg),
parent_(parent_engine),
llm_(NULL, NULL),
available_factions_(),
choosable_factions_(),
current_faction_(),
mp_controller_(CNTR_NETWORK),
index_(index),
team_(0),
color_(index),
gold_(cfg["gold"].to_int(100)),
income_(cfg["income"]),
id_(cfg["id"]),
player_id_(cfg["player_id"]),
save_id_(cfg["save_id"]),
current_player_(cfg["current_player"]),
leader_(),
gender_(),
ai_algorithm_(),
ready_for_start_(false),
allow_player_(cfg["controller"] == "ai" && cfg["allow_player"].empty() ?
false : cfg["allow_player"].to_bool(true)),
allow_changes_(cfg["allow_changes"].to_bool(true))
{
// Tweak the controllers.
if (cfg["controller"] == "human_ai" ||
cfg["controller"] == "network_ai") {
cfg_["controller"] = "ai";
}
if (allow_player_ && !parent_.params_.saved_game) {
mp_controller_ = parent_.mp_controller_;
} else {
size_t i = CNTR_NETWORK;
if (!allow_player_) {
if (cfg["controller"] == "null") {
mp_controller_ = CNTR_EMPTY;
} else {
cfg_["controller"] = controller_names[CNTR_COMPUTER];
mp_controller_ = CNTR_COMPUTER;
}
} else {
if (cfg["controller"] == "network" ||
cfg["controller"] == "human") {
cfg_["controller"] = "reserved";
}
for (; i != CNTR_LAST; ++i) {
if (cfg["controller"] == controller_names[i]) {
mp_controller_ = static_cast<mp::controller>(i);
break;
}
}
}
}
// Set faction lock for custom recruit list.
if (!cfg["recruit"].empty() && parent_.params_.use_map_settings) {
cfg_["faction"] = "Custom";
}
// Initialize faction lists.
available_factions_ = init_available_factions(parent_.era_factions(), cfg_);
choosable_factions_ = init_choosable_factions(available_factions_, cfg_,
parent_.params_.use_map_settings);
current_faction_ = choosable_factions_[0];
// Initialize team and color.
std::vector<std::string>::const_iterator itor =
std::find(parent_.team_names_.begin(), parent_.team_names_.end(),
cfg["team_name"].str());
if (itor == parent_.team_names_.end()) {
assert(!parent_.team_names_.empty());
team_ = 0;
} else {
team_ = itor - parent_.team_names_.begin();
}
if (!cfg["color"].empty()) {
color_ = game_config::color_info(cfg["color"]).index() - 1;
}
// Initialize ai algorithm.
if (const config &ai = cfg.child("ai")) {
ai_algorithm_ = ai["ai_algorithm"].str();
}
}
side_engine::~side_engine()
{
}
config side_engine::new_config() const
{
config res = cfg_;
// If the user is allowed to change type, faction, leader etc,
// then import their new values in the config.
if (!parent_.params_.saved_game && !choosable_factions_.empty()) {
// Merge the faction data to res.
res.append(*current_faction_);
res["faction_name"] = res["name"];
}
if (!cfg_.has_attribute("side") || cfg_["side"].to_int() != index_ + 1) {
res["side"] = index_ + 1;
}
res["controller"] = controller_names[mp_controller_];
res["current_player"] = player_id_.empty() ? current_player_ : player_id_;
res["id"] = id_;
if (player_id_.empty()) {
std::string description;
switch(mp_controller_) {
case CNTR_NETWORK:
description = N_("(Vacant slot)");
break;
case CNTR_LOCAL:
if (!parent_.params_.saved_game && !cfg_.has_attribute("save_id")) {
res["save_id"] = preferences::login() + res["side"].str();
}
res["player_id"] = preferences::login() + res["side"].str();
res["current_player"] = preferences::login();
description = N_("Anonymous local player");
break;
case CNTR_COMPUTER: {
if (!parent_.params_.saved_game && !cfg_.has_attribute("saved_id")) {
res["save_id"] = "ai" + res["side"].str();
}
utils::string_map symbols;
if (allow_player_) {
const config &ai_cfg =
ai::configuration::get_ai_config_for(ai_algorithm_);
res.add_child("ai", ai_cfg);
symbols["playername"] = ai_cfg["description"];
} else {
// Do not import default ai cfg here -
// all is set by scenario config.
symbols["playername"] = _("Computer Player");
}
symbols["side"] = res["side"].str();
description = vgettext("$playername $side", symbols);
break;
}
case CNTR_EMPTY:
description = N_("(Empty slot)");
res["no_leader"] = true;
break;
case CNTR_RESERVED: {
utils::string_map symbols;
symbols["playername"] = current_player_;
description = vgettext("(Reserved for $playername)",symbols);
break;
}
case CNTR_LAST:
default:
description = N_("(empty)");
assert(false);
break;
} // end switch
res["user_description"] = t_string(description, "wesnoth");
} else {
res["player_id"] = player_id_ + res["side"];
if (!parent_.params_.saved_game && !cfg_.has_attribute("save_id")) {
res["save_id"] = player_id_ + res["side"];
}
res["user_description"] = player_id_;
}
res["name"] = res["user_description"];
res["allow_changes"] = !parent_.params_.saved_game && allow_changes_;
if (!parent_.params_.saved_game) {
if (leader_.empty()) {
res["type"] = llm_.get_leader();
} else {
res["type"] = leader_;
}
if (gender_.empty()) {
std::string dummy = llm_.get_gender();
if (!dummy.empty() && dummy != "null" && dummy != "?")
res["gender"] = dummy;
} else {
// If no genders could be resolved, let the unit engine use
// the default gender.
if (gender_ != "null") {
res["gender"] = gender_;
}
}
res["team_name"] = parent_.team_names_[team_];
res["user_team_name"] = parent_.user_team_names_[team_];
res["allow_player"] = allow_player_;
res["color"] = color_ + 1;
res["gold"] = gold_;
res["income"] = income_;
if (!parent_.params_.use_map_settings || res["fog"].empty() ||
(res["fog"] != "yes" && res["fog"] != "no")) {
res["fog"] = parent_.params_.fog_game;
}
if (!parent_.params_.use_map_settings || res["shroud"].empty() ||
(res["shroud"] != "yes" && res["shroud"] != "no")) {
res["shroud"] = parent_.params_.shroud_game;
}
res["share_maps"] = parent_.params_.share_maps;
res["share_view"] = parent_.params_.share_view;
if (!parent_.params_.use_map_settings || res["village_gold"].empty()) {
res["village_gold"] = parent_.params_.village_gold;
}
if (!parent_.params_.use_map_settings ||
res["village_support"].empty()) {
res["village_support"] =
lexical_cast<std::string>(parent_.params_.village_support);
}
}
if (parent_.params_.use_map_settings && !parent_.params_.saved_game) {
config trimmed = cfg_;
static char const *attrs[] = {"side", "controller", "id",
"team_name", "user_team_name", "color", "gold",
"income", "allow_changes"};
BOOST_FOREACH(const char *attr, attrs) {
trimmed.remove_attribute(attr);
}
if (mp_controller_ != CNTR_COMPUTER) {
// Only override names for computer controlled players.
trimmed.remove_attribute("user_description");
}
res.merge_with(trimmed);
}
return res;
}
void side_engine::import_network_user(const config& data, bool faction_enabled,
bool leader_enabled, bool gender_enabled)
{
if (mp_controller_ == CNTR_RESERVED || parent_.params_.saved_game) {
ready_for_start_ = true;
}
player_id_ = data["name"].str();
mp_controller_ = CNTR_NETWORK;
if (!parent_.params_.saved_game && !choosable_factions_.empty()) {
if (faction_enabled) {
BOOST_FOREACH(const config* faction, choosable_factions_) {
if ((*faction)["id"] == data["faction"]) {
current_faction_ = faction;
}
}
update_llm_lists(selected_faction_index());
}
if (leader_enabled) {
llm_.set_leader(data["leader"]);
// FIXME: not optimal, but this hack is necessary to do
// after updating the leader selection.
// Otherwise, gender gets always forced to "male".
llm_.update_gender_list(llm_.get_leader());
}
if (gender_enabled) {
llm_.set_gender(data["gender"]);
}
}
}
bool side_engine::ready_for_start() const
{
// Sides without players are always ready.
if (!allow_player_) {
return true;
}
// The host and the AI are always ready.
if ((mp_controller_ == mp::CNTR_COMPUTER) ||
(mp_controller_ == mp::CNTR_EMPTY) ||
(mp_controller_== mp::CNTR_LOCAL)) {
return true;
}
return ready_for_start_;
}
bool side_engine::available(const std::string& name) const
{
if (name.empty()) {
return allow_player_ && ((mp_controller_ == CNTR_NETWORK &&
player_id_.empty()) || mp_controller_ == CNTR_RESERVED);
}
return allow_player_ &&
((mp_controller_ == CNTR_NETWORK && player_id_.empty()) ||
(mp_controller_ == CNTR_RESERVED && current_player_ == name));
}
void side_engine::reset(mp::controller controller, bool factions_enabled,
bool leaders_enabled, bool genders_enabled)
{
player_id_.clear();
mp_controller_ = controller;
if ((controller == mp::CNTR_NETWORK) || (controller == mp::CNTR_RESERVED)) {
ready_for_start_ = false;
}
if (!parent_.params_.saved_game && !choosable_factions_.empty()) {
if (factions_enabled) {
current_faction_ = choosable_factions_[0];
}
if (leaders_enabled) {
llm_.update_leader_list(selected_faction_index());
}
if (genders_enabled) {
llm_.update_gender_list(llm_.get_leader());
}
}
}
void side_engine::resolve_random()
{
if (parent_.params_.saved_game || choosable_factions_.empty()) {
return;
}
if ((*current_faction_)["random_faction"].to_bool()) {
std::vector<std::string> faction_choices, faction_excepts;
faction_choices = utils::split((*current_faction_)["choices"]);
if (faction_choices.size() == 1 && faction_choices.front() == "") {
faction_choices.clear();
}
faction_excepts = utils::split((*current_faction_)["except"]);
if (faction_excepts.size() == 1 && faction_excepts.front() == "") {
faction_excepts.clear();
}
// Builds the list of sides eligible for choice (nonrandom factions).
std::vector<int> nonrandom_sides;
int num = -1;
BOOST_FOREACH(const config* i, available_factions_) {
++num;
if (!(*i)["random_faction"].to_bool()) {
const std::string& faction_id = (*i)["id"];
if (!faction_choices.empty() &&
std::find(faction_choices.begin(), faction_choices.end(),
faction_id) == faction_choices.end()) {
continue;
}
if (!faction_excepts.empty() &&
std::find(faction_excepts.begin(), faction_excepts.end(),
faction_id) != faction_excepts.end()) {
continue;
}
nonrandom_sides.push_back(num);
}
}
if (nonrandom_sides.empty()) {
throw config::error(_("Only random sides in the current era."));
}
const int faction_index =
nonrandom_sides[rand() % nonrandom_sides.size()];
current_faction_ = available_factions_[faction_index];
}
LOG_MP << "FACTION" << (index_ + 1) << ": " << (*current_faction_)["name"]
<< std::endl;
bool solved_random_leader = false;
if (llm_.get_leader() == "random") {
// Choose a random leader type, and force gender to be random.
llm_.set_gender("random");
std::vector<std::string> types =
utils::split((*current_faction_)["random_leader"]);
if (!types.empty()) {
const int lchoice = rand() % types.size();
leader_ = types[lchoice];
} else {
// If 'random_leader' doesn't exist, we use 'leader'.
types = utils::split((*current_faction_)["leader"]);
if (!types.empty()) {
const int lchoice = rand() % types.size();
leader_ = types[lchoice];
} else {
utils::string_map i18n_symbols;
i18n_symbols["faction"] = (*current_faction_)["name"];
throw config::error(vgettext(
"Unable to find a leader type for faction $faction",
i18n_symbols));
}
}
solved_random_leader = true;
}
// Resolve random genders "very much" like standard unit code.
if (llm_.get_gender() == "random" || solved_random_leader) {
const unit_type *ut =
unit_types.find(leader_.empty() ? llm_.get_leader() : leader_);
if (ut) {
const std::vector<unit_race::GENDER> glist = ut->genders();
const int gchoice = rand() % glist.size();
// Pick up a gender, using the random 'gchoice' index.
unit_race::GENDER sgender = glist[gchoice];
switch (sgender) {
case unit_race::FEMALE:
gender_ = unit_race::s_female;
break;
case unit_race::MALE:
gender_ = unit_race::s_male;
break;
default:
gender_ = "null";
}
} else {
ERR_CF << "cannot obtain genders for invalid leader '" <<
(leader_.empty() ? llm_.get_leader() : leader_) << "'.\n";
gender_ = "null";
}
}
}
int side_engine::selected_faction_index() const
{
int index = 0;
BOOST_FOREACH(const config* faction, choosable_factions_) {
if ((*faction)["id"] == (*current_faction_)["id"]) {
return index;
}
index++;
}
return 0;
}
void side_engine::set_player_from_users_list(const std::string& player_id)
{
connected_user_list::iterator i = parent_.find_player(player_id);
if (i != parent_.users_.end()) {
player_id_ = player_id;
mp_controller_ = i->controller;
}
}
void side_engine::set_faction_commandline(const std::string& faction_name)
{
BOOST_FOREACH(const config* faction, choosable_factions_) {
if ((*faction)["name"] == faction_name) {
current_faction_ = faction;
break;
}
}
}
void side_engine::set_controller_commandline(const std::string& controller_name)
{
mp_controller_ = CNTR_LOCAL;
if (controller_name == "ai") {
mp_controller_ = CNTR_COMPUTER;
}
if (controller_name == "null") {
mp_controller_ = CNTR_EMPTY;
}
player_id_ = "";
}
void side_engine::set_ai_algorithm_commandline(
const std::string& algorithm_name)
{
ai_algorithm_ = algorithm_name;
}
void side_engine::assign_sides_on_drop_target(const int drop_target) {
const std::string target_id = parent_.side_engines_[drop_target]->player_id_;
const mp::controller target_controller =
parent_.side_engines_[drop_target]->mp_controller_;
const std::string target_ai =
parent_.side_engines_[drop_target]->ai_algorithm_;
parent_.side_engines_[drop_target]->ai_algorithm_ = ai_algorithm_;
if (player_id_.empty()) {
parent_.side_engines_[drop_target]->mp_controller_ = mp_controller_;
} else {
parent_.side_engines_[drop_target]->
set_player_from_users_list(player_id_);
}
ai_algorithm_ = target_ai;
if (target_id.empty())
{
mp_controller_ = target_controller;
player_id_ = "";
} else {
set_player_from_users_list(target_id);
}
}
void side_engine::update_llm()
{
llm_.set_side_list(choosable_factions_);
llm_.set_color(color_);
}
void side_engine::invalidate_llm_combos()
{
llm_.set_leader_combo(NULL);
llm_.set_gender_combo(NULL);
}
void side_engine::init_llm_combos(gui::combo* combo_leader,
gui::combo* combo_gender)
{
llm_.init_combos(combo_leader, combo_gender);
}
void side_engine::set_llm_combos(gui::combo* combo_leader,
gui::combo* combo_gender)
{
llm_.set_leader_combo(combo_leader);
if (combo_gender != NULL) {
llm_.set_gender_combo(combo_gender);
}
}
void side_engine::update_llm_lists(const int faction_index)
{
update_llm_leader_list(faction_index);
update_llm_gender_list();
}
void side_engine::update_llm_leader_list(const int faction_index)
{
llm_.update_leader_list(faction_index);
}
void side_engine::update_llm_gender_list()
{
llm_.update_gender_list(llm_.get_leader());
}
void side_engine::update_llm_leader_selection(const std::string& leader)
{
llm_.set_leader(leader);
}
void side_engine::update_llm_gender_selection(const std::string& gender)
{
llm_.set_gender(gender);
}
} // end namespace mp

View file

@ -0,0 +1,235 @@
/*
Copyright (C) 2013 by Andrius Silinskas <silinskas.andrius@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
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 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef MULTIPLAYER_CONNECT_ENGINE_HPP_INCLUDED
#define MULTIPLAYER_CONNECT_ENGINE_HPP_INCLUDED
#include "commandline_options.hpp"
#include "config.hpp"
#include "gamestatus.hpp"
#include "leader_list.hpp"
#include "multiplayer_ui.hpp"
namespace mp {
class side_engine;
struct connected_user
{
connected_user(const std::string& name, mp::controller controller,
network::connection connection) :
name(name),
controller(controller),
connection(connection)
{};
std::string name;
mp::controller controller;
network::connection connection;
operator std::string() const
{
return name;
}
};
typedef boost::shared_ptr<side_engine> side_engine_ptr;
typedef std::vector<connected_user> connected_user_list;
class connect_engine
{
public:
connect_engine(game_display& disp, controller mp_controller,
const mp_game_settings& params);
~connect_engine();
config* current_config();
void add_side_engine(side_engine_ptr engine);
// Import all sides into the level.
void update_level();
// Updates the level and sends a diff to the clients.
void update_and_send_diff(bool update_time_of_day = false);
// Returns true if there are still sides available for this game.
bool sides_available() const;
bool can_start_game() const;
void start_game();
void start_game_commandline(const commandline_options& cmdline_opts);
// Network methods.
void process_network_connection(const network::connection sock);
// Returns the index of a player, from its id,
// or -1 if the player was not found.
connected_user_list::iterator find_player(const std::string& id);
// Returns the side which is taken by a given player,
// or -1 if none was found.
int find_player_side(const std::string& id) const;
/* Setters & Getters */
const config& level() const { return level_; }
const game_state& state() const { return state_; }
const std::vector<const config *>& era_factions() const
{ return era_factions_; }
private:
connect_engine(const connect_engine&);
void operator=(const connect_engine&);
friend side_engine;
config level_;
game_state state_;
game_display& disp_;
const mp_game_settings& params_;
std::vector<side_engine_ptr> side_engines_;
std::vector<const config *> era_factions_;
public:
connected_user_list users_;
controller mp_controller_;
std::vector<std::string> team_names_;
std::vector<std::string> user_team_names_;
};
class side_engine
{
public:
side_engine(const config& cfg, connect_engine& parent_engine,
const int index);
~side_engine();
// Sets a new config representing this side.
config new_config() const;
void import_network_user(const config& data, bool faction_enabled,
bool leader_enabled, bool gender_enabled);
// Returns true, if the player has chosen his/her leader and this side
// is ready for the game to start.
bool ready_for_start() const;
// Returns true if this side is waiting for a network player and
// players are allowed.
bool available(const std::string& name = "") const;
void set_player_from_users_list(const std::string& player_id);
void reset(mp::controller controller, bool factions_enabled,
bool leaders_enabled, bool genders_enabled);
void resolve_random();
int selected_faction_index() const;
void set_faction_commandline(const std::string& faction_name);
void set_controller_commandline(const std::string& controller_name);
void set_ai_algorithm_commandline(const std::string& algorithm_name);
void assign_sides_on_drop_target(const int drop_target);
// LLM management.
void update_llm();
void invalidate_llm_combos();
void init_llm_combos(gui::combo* combo_leader, gui::combo* combo_gender);
void set_llm_combos(gui::combo* combo_leader, gui::combo* combo_gender);
void update_llm_lists(const int faction_index);
void update_llm_leader_list(const int faction_index);
void update_llm_gender_list();
void update_llm_leader_selection(const std::string& leader);
void update_llm_gender_selection(const std::string& gender);
/* Setters & Getters */
const std::vector<const config*>& choosable_factions()
{ return choosable_factions_; }
const config& cfg() const { return cfg_; }
const config* current_faction() const { return current_faction_; }
void set_current_faction(const config* current_faction)
{ current_faction_ = current_faction; }
controller mp_controller() const { return mp_controller_; }
void set_mp_controller(controller mp_controller)
{ mp_controller_ = mp_controller; }
int index() const { return index_; }
void set_index(int index) { index_ = index; }
int team() const { return team_; }
void set_team(int team) { team_ = team; }
int color() const { return color_; }
void set_color(int color) { color_ = color; }
int gold() const { return gold_; }
void set_gold(int gold) { gold_ = gold; }
int income() const { return income_; }
void set_income(int income) { income_ = income; }
const std::string& player_id() const { return player_id_; }
void set_player_id(const std::string& player_id) { player_id_ = player_id; }
const std::string& save_id() const { return save_id_; }
const std::string& current_player() const { return current_player_; }
const std::string& leader() const { return leader_; }
void set_leader(const std::string& leader) { leader_ = leader; }
const std::string& gender() const { return gender_; }
void set_gender(const std::string& gender) { gender_ = gender; }
const std::string& ai_algorithm() const { return ai_algorithm_; }
void set_ai_algorithm(const std::string& ai_algorithm)
{ai_algorithm_ = ai_algorithm; }
void set_ready_for_start(const bool ready_for_start)
{ ready_for_start_ = ready_for_start; }
bool allow_player() const { return allow_player_; }
private:
side_engine(const side_engine& engine);
void operator=(const connect_engine&);
config cfg_;
connect_engine& parent_;
leader_list_manager llm_;
// All factions which could be played by a side (including Random).
std::vector<const config*> available_factions_;
// All factions which a side can choose.
std::vector<const config*> choosable_factions_;
const config* current_faction_;
controller mp_controller_;
// Configurable variables.
int index_;
int team_;
int color_;
int gold_;
int income_;
std::string id_;
std::string player_id_;
std::string save_id_;
std::string current_player_;
std::string leader_;
std::string gender_;
std::string ai_algorithm_;
bool ready_for_start_;
bool allow_player_;
bool allow_changes_;
};
} // end namespace mp
#endif

View file

@ -358,6 +358,7 @@ void create_engine::prepare_for_new_level()
parameters_.scenario_data = current_level().data();
parameters_.hash = parameters_.scenario_data.hash();
parameters_.mp_scenario = parameters_.scenario_data["id"].str();
}
void create_engine::prepare_for_campaign(const std::string& difficulty)

View file

@ -876,7 +876,7 @@ int find_suitable_faction(faction_list const &fl, const config &cfg)
return res;
}
std::vector<const config*> available_factions(
std::vector<const config*> init_available_factions(
std::vector<const config*> era_sides, const config& side)
{
std::vector<const config*> available_factions;
@ -892,7 +892,7 @@ std::vector<const config*> available_factions(
return available_factions;
}
std::vector<const config*> choosable_factions(
std::vector<const config*> init_choosable_factions(
std::vector<const config*> available_factions, const config& side,
const bool map_settings)
{

View file

@ -274,10 +274,10 @@ typedef std::vector<const config *> faction_list;
/** Picks the first faction with the greater amount of data matching the criteria. */
int find_suitable_faction(faction_list const &fl, const config &side);
std::vector<const config*> available_factions(
std::vector<const config*> init_available_factions(
std::vector<const config*> era_sides, const config& side);
std::vector<const config*> choosable_factions(
std::vector<const config*> init_choosable_factions(
std::vector<const config*> available_factions, const config& side,
const bool map_settings);
}

View file

@ -282,8 +282,8 @@ void wait::join_game(bool observe)
const bool use_map_settings =
level_.child("multiplayer")["mp_use_map_settings"].to_bool();
leader_sides = choosable_factions(
available_factions(leader_sides, *side_choice),
leader_sides = init_choosable_factions(
init_available_factions(leader_sides, *side_choice),
*side_choice, use_map_settings);
std::vector<std::string> choices;