Refactor faction set up in multiplayer.

The code has been improved to switch from index lookups to lookups by
ids. This makes the implementation more flexible for future improvements
and easier for maintenance.

The "Custom" faction has been added. It is automatically chosen and
locked for sides with a recruit list. The sides without recruit list
won't see it in their faction choice combo box.

The recruit list in side will be now be overriden by a faction if map
settings are not in use.
This commit is contained in:
Andrius Silinskas 2013-07-18 12:29:20 +01:00
parent 298e6f8ede
commit 77523d86d6
9 changed files with 187 additions and 85 deletions

View file

@ -1,4 +1,12 @@
#textdomain wesnoth-multiplayer
[multiplayer_side]
id=Custom
name= _"Custom"
image="units/unknown-unit.png"
{MAGENTA_IS_THE_TEAM_COLOR}
[/multiplayer_side]
#define RANDOM_SIDE
[multiplayer_side]
id=Random

View file

@ -26,18 +26,23 @@
const std::string leader_list_manager::random_enemy_picture("units/random-dice.png");
leader_list_manager::leader_list_manager(const std::vector<const config *> &side_list,
gui::combo* leader_combo , gui::combo* gender_combo):
leader_list_manager::leader_list_manager(gui::combo* leader_combo,
gui::combo* gender_combo):
leaders_(),
genders_(),
gender_ids_(),
side_list_(side_list),
leader_combo_(leader_combo),
gender_combo_(gender_combo),
color_(0)
{
}
void leader_list_manager::set_side_list(
const std::vector<const config *> &side_list)
{
side_list_ = side_list;
}
void leader_list_manager::set_leader_combo(gui::combo* combo)
{
int selected = leader_combo_ != NULL ? leader_combo_->selected() : 0;
@ -69,7 +74,7 @@ void leader_list_manager::update_leader_list(int side_index)
leaders_.clear();
if (side["random_faction"].to_bool()) {
if (side["random_faction"].to_bool() || side["id"] == "Custom") {
if(leader_combo_ != NULL) {
std::vector<std::string> dummy;
dummy.push_back(utils::unicode_em_dash);

View file

@ -26,9 +26,10 @@ class leader_list_manager
public:
static const std::string random_enemy_picture;
leader_list_manager(const std::vector<const config *> &side_list,
gui::combo* leader_combo = NULL, gui::combo* gender_combo = NULL);
leader_list_manager(gui::combo* leader_combo = NULL,
gui::combo* gender_combo = NULL);
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);
void update_leader_list(int side);

View file

@ -104,6 +104,10 @@ config initial_level_config(game_display& disp, mp_game_settings& params,
/*config& cfg =*/
level.add_child("era", era_cfg);
const config& custom_side = resources::config_manager->
game_config().find_child("multiplayer_side", "id", "Custom");
level.child("era").add_child_at("multiplayer_side", custom_side, 0);
// Convert options to event
//FIXME
//cfg.add_child_at("event", mp::options::to_event(

View file

@ -67,13 +67,15 @@ namespace mp {
connect::side::side(connect& parent, const config& cfg, int index) :
parent_(&parent),
cfg_(cfg),
available_factions_(),
choosable_factions_(),
current_faction_(),
index_(index),
id_(cfg["id"]),
player_id_(cfg["player_id"]),
save_id_(cfg["save_id"]),
current_player_(cfg["current_player"]),
controller_(CNTR_NETWORK),
faction_(cfg["faction"]),
team_(0),
color_(index),
gold_(cfg["gold"].to_int(100)),
@ -102,7 +104,7 @@ connect::side::side(connect& parent, const config& cfg, int index) :
allow_player_(cfg["controller"] == "ai" && cfg["allow_player"].empty() ? false : cfg["allow_player"].to_bool(true)),
allow_changes_(cfg["allow_changes"].to_bool(true)),
enabled_(!parent_->params_.saved_game), changed_(false),
llm_(parent.era_sides_, enabled_ ? &combo_leader_ : NULL, enabled_ ? &combo_gender_ : NULL)
llm_(enabled_ ? &combo_leader_ : NULL, enabled_ ? &combo_gender_ : NULL)
{
DBG_MP << "initializing side" << std::endl;
// convert ai controllers
@ -137,6 +139,19 @@ connect::side::side(connect& parent, const config& cfg, int index) :
}
}
if (cfg_.has_attribute("recruit") && parent_->params_.use_map_settings) {
cfg_["faction"] = "Custom";
}
// Initialize faction lists.
available_factions_ = available_factions(parent_->era_sides_, cfg_);
choosable_factions_ = choosable_factions(available_factions_, cfg_,
parent_->params_.use_map_settings);
current_faction_ = choosable_factions_[0],
llm_.set_side_list(choosable_factions_);
slider_gold_.set_min(20);
slider_gold_.set_max(800);
slider_gold_.set_increment(25);
@ -173,15 +188,13 @@ connect::side::side(connect& parent, const config& cfg, int index) :
}
llm_.set_color(color_);
update_faction_combo();
if (const config &ai = cfg.child("ai"))
ai_algorithm_ = ai["ai_algorithm"].str();
init_ai_algorithm_combo();
// "Faction name" hack
if (!enabled_) {
faction_ = 0;
current_faction_ = choosable_factions_[0];
std::vector<std::string> pseudo_factions;
pseudo_factions.push_back(cfg["name"]);
combo_faction_.set_items(pseudo_factions);
@ -276,30 +289,37 @@ connect::side::side(connect& parent, const config& cfg, int index) :
// Try to pick a faction for the sake of appearance
// and for filling in the blanks
if(faction_ == 0) {
faction_ = find_suitable_faction(parent.era_sides_, cfg);
if (faction_ < 0) faction_ = 0;
if (faction_) {
llm_.update_leader_list(faction_);
llm_.update_gender_list(llm_.get_leader());
combo_faction_.enable(false);
int faction_index = 0;
if (choosable_factions_.size() > 1) {
faction_index = find_suitable_faction(choosable_factions_, cfg_);
if (faction_index < 0) {
faction_index = 0;
}
} else {
combo_faction_.enable(false);
}
current_faction_ = choosable_factions_[faction_index];
llm_.update_leader_list(faction_index);
llm_.update_gender_list(llm_.get_leader());
}
update_faction_combo();
update_ui();
}
connect::side::side(const side& a) :
parent_(a.parent_), cfg_(a.cfg_),
available_factions_(a.available_factions_),
choosable_factions_(a.choosable_factions_),
current_faction_(a.current_faction_),
index_(a.index_), id_(a.id_), player_id_(a.player_id_), save_id_(a.save_id_),
current_player_(a.current_player_),
controller_(a.controller_),
faction_(a.faction_), team_(a.team_), color_(a.color_),
gold_(a.gold_), income_(a.income_), leader_(a.leader_),
gender_(a.gender_),
team_(a.team_), color_(a.color_),
gold_(a.gold_), income_(a.income_),
leader_(a.leader_), gender_(a.gender_),
ai_algorithm_(a.ai_algorithm_),
ready_for_start_(a.ready_for_start_),
gold_lock_(a.gold_lock_),
@ -316,6 +336,7 @@ connect::side::side(const side& a) :
allow_player_(a.allow_player_), allow_changes_(a.allow_changes_),
enabled_(a.enabled_), changed_(a.changed_), llm_(a.llm_)
{
llm_.set_side_list(choosable_factions_);
llm_.set_color(color_);
llm_.set_leader_combo((enabled_ && leader_.empty()) ? &combo_leader_ : NULL);
llm_.set_gender_combo((enabled_ && leader_.empty()) ? &combo_gender_ : NULL);
@ -412,8 +433,9 @@ void connect::side::process_event()
changed_ = true;
}
if (combo_faction_.changed() && combo_faction_.selected() >= 0) {
faction_ = combo_faction_.selected();
llm_.update_leader_list(faction_);
const int sel = combo_faction_.selected();
current_faction_ = choosable_factions_[sel];
llm_.update_leader_list(sel);
llm_.update_gender_list(llm_.get_leader());
changed_ = true;
}
@ -558,8 +580,7 @@ void connect::side::init_ai_algorithm_combo()
void connect::side::update_faction_combo()
{
std::vector<std::string> factions;
BOOST_FOREACH(const config *faction, parent_->era_sides_)
{
BOOST_FOREACH(const config* faction, choosable_factions_) {
const std::string& name = (*faction)["name"];
const std::string& icon = (*faction)["image"];
if (!icon.empty()) {
@ -573,15 +594,18 @@ void connect::side::update_faction_combo()
}
}
combo_faction_.set_items(factions);
combo_faction_.set_selected(faction_);
combo_faction_.set_selected(selected_faction_index());
}
void connect::side::update_ui()
{
update_controller_ui();
if (combo_faction_.selected() != faction_ && combo_faction_.selected() >= 0) {
combo_faction_.set_selected(faction_);
const int sel = selected_faction_index();
if (combo_faction_.selected() != sel &&
combo_faction_.selected() >= 0) {
combo_faction_.set_selected(sel);
}
combo_team_.set_selected(team_);
@ -600,18 +624,32 @@ void connect::side::update_ui()
label_income_.set_text(buf.str());
}
int connect::side::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;
}
config connect::side::get_config() const
{
config res;
config res = cfg_;
// If the user is allowed to change type, faction, leader etc,
// then import their new values in the config.
if(enabled_ && !parent_->era_sides_.empty()) {
if(enabled_ && !choosable_factions_.empty()) {
// Merge the faction data to res
res.append(*(parent_->era_sides_[faction_]));
res.append(*current_faction_);
res["faction_name"] = res["name"];
}
res.append(cfg_);
if (!cfg_.has_attribute("side") || cfg_["side"].to_int() != index_ + 1) {
res["side"] = index_ + 1;
}
@ -736,6 +774,7 @@ config connect::side::get_config() const
}
res.merge_with(trimmed);
}
return res;
}
@ -827,12 +866,15 @@ void connect::side::import_network_user(const config& data)
player_id_ = data["name"].str();
controller_ = CNTR_NETWORK;
if(enabled_ && !parent_->era_sides_.empty()) {
if(enabled_ && !choosable_factions_.empty()) {
if(combo_faction_.enabled()) {
faction_ = data["faction"];
if(faction_ > int(parent_->era_sides_.size()))
faction_ = 0;
llm_.update_leader_list(faction_);
BOOST_FOREACH(const config* faction, choosable_factions_) {
if ((*faction)["id"] == data["faction"]) {
current_faction_ = faction;
}
}
llm_.update_leader_list(selected_faction_index());
llm_.update_gender_list(llm_.get_leader());
}
if(combo_leader_.enabled()) {
@ -857,11 +899,11 @@ void connect::side::reset(mp::controller controller)
if ((controller == mp::CNTR_NETWORK) || (controller == mp::CNTR_RESERVED))
ready_for_start_ = false;
if(enabled_ && !parent_->era_sides_.empty()) {
if(enabled_ && !choosable_factions_.empty()) {
if(combo_faction_.enabled())
faction_ = 0;
current_faction_ = choosable_factions_[0];
if(combo_leader_.enabled())
llm_.update_leader_list(faction_);
llm_.update_leader_list(selected_faction_index());
if (combo_gender_.enabled())
llm_.update_gender_list(llm_.get_leader());
}
@ -871,17 +913,17 @@ void connect::side::reset(mp::controller controller)
void connect::side::resolve_random()
{
if(!enabled_ || parent_->era_sides_.empty())
if(!enabled_ || choosable_factions_.empty())
return;
if ((*parent_->era_sides_[faction_])["random_faction"].to_bool())
if ((*current_faction_)["random_faction"].to_bool())
{
std::vector<std::string> faction_choices, faction_excepts;
faction_choices = utils::split((*parent_->era_sides_[faction_])["choices"]);
faction_choices = utils::split((*current_faction_)["choices"]);
if(faction_choices.size() == 1 && faction_choices.front() == "") {
faction_choices.clear();
}
faction_excepts = utils::split((*parent_->era_sides_[faction_])["except"]);
faction_excepts = utils::split((*current_faction_)["except"]);
if(faction_excepts.size() == 1 && faction_excepts.front() == "") {
faction_excepts.clear();
}
@ -889,8 +931,7 @@ void connect::side::resolve_random()
// Builds the list of sides eligible for choice (nonrandom factions)
std::vector<int> nonrandom_sides;
int num = -1;
BOOST_FOREACH(const config *i, parent_->era_sides_)
{
BOOST_FOREACH(const config* i, available_factions_) {
++num;
if (!(*i)["random_faction"].to_bool()) {
const std::string& faction_id = (*i)["id"];
@ -912,30 +953,33 @@ void connect::side::resolve_random()
throw config::error(_("Only random sides in the current era."));
}
faction_ = nonrandom_sides[rand() % nonrandom_sides.size()];
const int faction_index =
nonrandom_sides[rand() % nonrandom_sides.size()];
current_faction_ = available_factions_[faction_index];
}
LOG_MP << "FACTION" << (index_ + 1) << ": " << (*parent_->era_sides_[faction_])["name"] << std::endl;
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");
const config& fact = *parent_->era_sides_[faction_];
std::vector<std::string> types = utils::split(fact["random_leader"]);
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 exists, we use leader=
types = utils::split(fact["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"] = fact["name"];
i18n_symbols["faction"] = (*current_faction_)["name"];
throw config::error(vgettext("Unable to find a leader type for faction $faction", i18n_symbols));
}
}
@ -970,13 +1014,10 @@ void connect::side::resolve_random()
void connect::side::set_faction_commandline(std::string faction_name)
{
// Set faction_ (the faction number) according to the name given at the commandline
int num = -1;
BOOST_FOREACH(const config *i, parent_->era_sides_)
{
++num;
// Set current_faction_ according to the name given at the commandline.
BOOST_FOREACH(const config* i, choosable_factions_) {
if ((*i)["name"] == faction_name) {
faction_ = num;
current_faction_ = i;
break;
}
}
@ -1016,7 +1057,6 @@ connect::connect(game_display& disp, const config& game_config,
params_(params),
era_sides_(),
player_types_(),
player_factions_(),
player_teams_(),
player_colors_(),
ai_algorithms_(),
@ -1620,10 +1660,6 @@ void connect::lists_init()
player_types_.push_back(_("Computer Player"));
player_types_.push_back(_("Empty"));
BOOST_FOREACH(const config *faction, era_sides_) {
player_factions_.push_back((*faction)["name"]);
}
// AI algorithms
const config &era = level_.child("era");
ai::configuration::add_era_ai_from_config(era);
@ -1725,13 +1761,11 @@ void connect::load_game()
return;
}
if (!params_.saved_game) {
era_sides_.clear();
BOOST_FOREACH(const config &e,
level_.child("era").child_range("multiplayer_side")) {
era_sides_.clear();
BOOST_FOREACH(const config &e,
level_.child("era").child_range("multiplayer_side")) {
era_sides_.push_back(&e);
}
era_sides_.push_back(&e);
}
// Add the map name to the title.

View file

@ -134,6 +134,10 @@ 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.
*
@ -148,6 +152,12 @@ public:
*/
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_;
@ -155,7 +165,6 @@ public:
std::string save_id_;
std::string current_player_;
mp::controller controller_;
int faction_;
int team_;
int color_;
int gold_;
@ -295,7 +304,6 @@ private:
// Lists used for combos
std::vector<std::string> player_types_;
std::vector<std::string> player_factions_;
std::vector<std::string> player_teams_;
std::vector<std::string> player_colors_;
std::vector<ai::description*> ai_algorithms_;

View file

@ -876,4 +876,40 @@ int find_suitable_faction(faction_list const &fl, const config &cfg)
return res;
}
std::vector<const config*> available_factions(
std::vector<const config*> era_sides, const config& side)
{
std::vector<const config*> available_factions;
BOOST_FOREACH(const config* faction, era_sides) {
if ((*faction)["id"] == "Custom" && side["faction"] != "Custom") {
continue;
}
available_factions.push_back(faction);
}
return available_factions;
}
std::vector<const config*> choosable_factions(
std::vector<const config*> available_factions, const config& side,
const bool map_settings)
{
std::vector<const config*> choosable_factions(available_factions);
if (!side["faction"].empty() && map_settings) {
int faction_index = find_suitable_faction(choosable_factions, side);
if (faction_index >= 0) {
const config* faction = choosable_factions[faction_index];
choosable_factions.clear();
choosable_factions.push_back(faction);
return choosable_factions;
}
}
return choosable_factions;
}
}// namespace mp

View file

@ -274,6 +274,12 @@ 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*> era_sides, const config& side);
std::vector<const config*> choosable_factions(
std::vector<const config*> available_factions, const config& side,
const bool map_settings);
}
#endif

View file

@ -48,9 +48,10 @@ wait::leader_preview_pane::leader_preview_pane(game_display& disp,
color_(color),
leader_combo_(disp, std::vector<std::string>()),
gender_combo_(disp, std::vector<std::string>()),
leaders_(side_list, &leader_combo_, &gender_combo_),
leaders_(&leader_combo_, &gender_combo_),
selection_(0)
{
leaders_.set_side_list(side_list);
leaders_.set_color(color_);
set_location(leader_pane_position);
}
@ -278,12 +279,12 @@ void wait::join_game(bool observe)
leader_sides.push_back(&side);
}
int forced_faction = find_suitable_faction(leader_sides, *side_choice);
if (forced_faction >= 0) {
const config *f = leader_sides[forced_faction];
leader_sides.clear();
leader_sides.push_back(f);
}
const bool use_map_settings =
level_.child("multiplayer")["mp_use_map_settings"].to_bool();
leader_sides = choosable_factions(
available_factions(leader_sides, *side_choice),
*side_choice, use_map_settings);
std::vector<std::string> choices;
BOOST_FOREACH(const config *s, leader_sides)
@ -308,22 +309,21 @@ void wait::join_game(bool observe)
leader_preview_pane leader_selector(disp(), leader_sides, color);
preview_panes.push_back(&leader_selector);
const int res = gui::show_dialog(disp(), NULL, _("Choose your faction:"), _("Starting position: ") + lexical_cast<std::string>(side_num + 1),
gui::OK_CANCEL, &choices, &preview_panes);
if(res < 0) {
const int faction_choice = gui::show_dialog(disp(), NULL,
_("Choose your faction:"), _("Starting position: ") +
lexical_cast<std::string>(side_num + 1), gui::OK_CANCEL,
&choices, &preview_panes);
if(faction_choice < 0) {
set_result(QUIT);
return;
}
const int faction_choice = res;
leader_choice = leader_selector.get_selected_leader();
gender_choice = leader_selector.get_selected_gender();
assert(static_cast<unsigned>(faction_choice) < leader_sides.size());
config faction;
config& change = faction.add_child("change_faction");
change["name"] = preferences::login();
change["faction"] = forced_faction >= 0 ? forced_faction : faction_choice;
change["faction"] = (*leader_sides[faction_choice])["id"];
change["leader"] = leader_choice;
change["gender"] = gender_choice;
network::send_data(faction, 0);