mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-30 22:43:15 +00:00

...to fix issue where sides that skip a scenario lose recalls and gold. Fixing bug #20373 and duplicates #20455 and #20456
1378 lines
38 KiB
C++
1378 lines
38 KiB
C++
//* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2013 by David White <dave@whitevine.net>
|
|
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.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Maintain status of a game, load&save games.
|
|
*/
|
|
|
|
#include "global.hpp"
|
|
#include "config.hpp"
|
|
|
|
#include "gamestatus.hpp"
|
|
|
|
#include "actions/create.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "gettext.hpp"
|
|
#include "log.hpp"
|
|
#include "game_preferences.hpp"
|
|
#include "replay.hpp"
|
|
#include "resources.hpp"
|
|
#include "statistics.hpp"
|
|
#include "team.hpp"
|
|
#include "unit.hpp"
|
|
#include "unit_id.hpp"
|
|
#include "wesconfig.h"
|
|
#include "wml_exception.hpp"
|
|
#include "formula_string_utils.hpp"
|
|
#include "map.hpp"
|
|
#include "pathfind/pathfind.hpp"
|
|
#include "whiteboard/side_actions.hpp"
|
|
#include "sound.hpp"
|
|
#include "soundsource.hpp"
|
|
#include "game_events.hpp"
|
|
#include "map_label.hpp"
|
|
|
|
#include <boost/bind.hpp>
|
|
#include <boost/foreach.hpp>
|
|
|
|
#ifndef _MSC_VER
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
static lg::log_domain log_engine("engine");
|
|
#define ERR_NG LOG_STREAM(err, log_engine)
|
|
#define WRN_NG LOG_STREAM(warn, log_engine)
|
|
#define LOG_NG LOG_STREAM(info, log_engine)
|
|
#define DBG_NG LOG_STREAM(debug, log_engine)
|
|
|
|
static lg::log_domain log_engine_tc("engine/team_construction");
|
|
#define ERR_NG_TC LOG_STREAM(err, log_engine_tc)
|
|
#define WRN_NG_TC LOG_STREAM(warn, log_engine_tc)
|
|
#define LOG_NG_TC LOG_STREAM(info, log_engine_tc)
|
|
#define DBG_NG_TC LOG_STREAM(debug, log_engine_tc)
|
|
|
|
static lg::log_domain log_enginerefac("enginerefac");
|
|
#define LOG_RG LOG_STREAM(info, log_enginerefac)
|
|
|
|
|
|
/// The default difficulty setting for campaigns.
|
|
const std::string DEFAULT_DIFFICULTY("NORMAL");
|
|
|
|
|
|
wml_menu_item::wml_menu_item(const std::string& id, const config* cfg) :
|
|
name(),
|
|
event_id(id),
|
|
image(),
|
|
description(),
|
|
needs_select(false),
|
|
show_if(),
|
|
filter_location(),
|
|
command()
|
|
|
|
{
|
|
std::stringstream temp;
|
|
temp << "menu item";
|
|
if(!id.empty()) {
|
|
temp << ' ' << id;
|
|
}
|
|
name = temp.str();
|
|
if(cfg != NULL) {
|
|
image = (*cfg)["image"].str();
|
|
description = (*cfg)["description"];
|
|
needs_select = (*cfg)["needs_select"].to_bool();
|
|
if (const config &c = cfg->child("show_if")) show_if = c;
|
|
if (const config &c = cfg->child("filter_location")) filter_location = c;
|
|
if (const config &c = cfg->child("command")) command = c;
|
|
}
|
|
}
|
|
|
|
wmi_container::wmi_container()
|
|
: wml_menu_items_()
|
|
{}
|
|
|
|
wmi_container::wmi_container(std::map<std::string, wml_menu_item*> wml_menu_items)
|
|
: wml_menu_items_(wml_menu_items)
|
|
{}
|
|
|
|
wmi_container::wmi_container(const wmi_container& container)
|
|
: wml_menu_items_()
|
|
{
|
|
clear_wmi();
|
|
std::map<std::string, wml_menu_item*>::const_iterator itor;
|
|
for (itor = container.wml_menu_items_.begin(); itor != container.wml_menu_items_.end(); ++itor) {
|
|
wml_menu_item*& mref = wml_menu_items_[itor->first];
|
|
mref = new wml_menu_item(*(itor->second));
|
|
}
|
|
}
|
|
|
|
|
|
void wmi_container::clear_wmi()
|
|
{
|
|
for (std::map<std::string, wml_menu_item *>::iterator i = wml_menu_items_.begin(),
|
|
i_end = wml_menu_items_.end(); i != i_end; ++i)
|
|
{
|
|
delete i->second;
|
|
}
|
|
wml_menu_items_.clear();
|
|
}
|
|
|
|
void wmi_container::to_config(config& cfg){
|
|
for(std::map<std::string, wml_menu_item *>::const_iterator j=wml_menu_items_.begin();
|
|
j!=wml_menu_items_.end(); ++j) {
|
|
config new_cfg;
|
|
new_cfg["id"]=j->first;
|
|
new_cfg["image"]=j->second->image;
|
|
new_cfg["description"]=j->second->description;
|
|
new_cfg["needs_select"] = j->second->needs_select;
|
|
if(!j->second->show_if.empty())
|
|
new_cfg.add_child("show_if", j->second->show_if);
|
|
if(!j->second->filter_location.empty())
|
|
new_cfg.add_child("filter_location", j->second->filter_location);
|
|
if(!j->second->command.empty())
|
|
new_cfg.add_child("command", j->second->command);
|
|
cfg.add_child("menu_item", new_cfg);
|
|
}
|
|
}
|
|
|
|
void wmi_container::set_menu_items(const config& cfg){
|
|
clear_wmi();
|
|
BOOST_FOREACH(const config &item, cfg.child_range("menu_item"))
|
|
{
|
|
if(!item.has_attribute("id")){ continue; }
|
|
|
|
std::string id = item["id"];
|
|
wml_menu_item*& mref = wml_menu_items_[id];
|
|
if(mref == NULL) {
|
|
mref = new wml_menu_item(id, &item);
|
|
} else {
|
|
WRN_NG << "duplicate menu item (" << id << ") while loading from config\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
carryover::carryover(const config& side)
|
|
: add_(side["add"].to_bool())
|
|
, color_(side["color"])
|
|
, current_player_(side["current_player"])
|
|
, gold_(side["gold"].to_int())
|
|
, name_(side["name"])
|
|
, previous_recruits_()
|
|
, recall_list_()
|
|
, save_id_(side["save_id"])
|
|
{
|
|
std::vector<std::string> temp_recruits = utils::split(side["previous_recruits"], ',');
|
|
previous_recruits_.insert(temp_recruits.begin(), temp_recruits.end());
|
|
|
|
BOOST_FOREACH(const config& u, side.child_range("unit")){
|
|
recall_list_.push_back(unit(u));
|
|
}
|
|
}
|
|
|
|
carryover::carryover(const team& t, const int gold, const bool add)
|
|
: add_ (add)
|
|
, color_(t.color())
|
|
, current_player_(t.current_player())
|
|
, gold_(gold)
|
|
, name_(t.name())
|
|
, previous_recruits_(t.recruits())
|
|
, recall_list_(t.recall_list())
|
|
, save_id_(t.save_id())
|
|
{}
|
|
|
|
static const int default_gold_qty = 100;
|
|
|
|
void carryover::transfer_all_gold_to(config& side_cfg){
|
|
|
|
int cfg_gold = side_cfg["gold"].to_int();
|
|
|
|
if(side_cfg["gold"].empty()) {
|
|
cfg_gold = default_gold_qty;
|
|
side_cfg["gold"] = cfg_gold;
|
|
}
|
|
|
|
if(add_){
|
|
side_cfg["gold"] = cfg_gold + gold_;
|
|
}
|
|
else if(gold_ > cfg_gold){
|
|
side_cfg["gold"] = gold_;
|
|
}
|
|
|
|
gold_ = 0;
|
|
}
|
|
|
|
void carryover::transfer_all_recruits_to(config& side_cfg){
|
|
std::string can_recruit_str = utils::join(previous_recruits_, ",");
|
|
previous_recruits_.clear();
|
|
side_cfg["previous_recruits"] = can_recruit_str;
|
|
}
|
|
|
|
void carryover::transfer_all_recalls_to(config& side_cfg){
|
|
BOOST_FOREACH(unit& u, recall_list_){
|
|
config& new_unit = side_cfg.add_child("unit");
|
|
u.write(new_unit);
|
|
}
|
|
recall_list_.clear();
|
|
}
|
|
|
|
std::string carryover::get_recruits(bool erase){
|
|
std::stringstream can_recruit;
|
|
for(std::set<std::string>::iterator i = previous_recruits_.begin()
|
|
; i != previous_recruits_.end()
|
|
; ){
|
|
can_recruit << *i << ",";
|
|
if(erase){
|
|
previous_recruits_.erase(i++);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
std::string can_recruit_str = can_recruit.str();
|
|
// Remove the trailing comma
|
|
if(can_recruit_str.empty() == false) {
|
|
can_recruit_str.resize(can_recruit_str.size()-1);
|
|
}
|
|
return can_recruit_str;
|
|
}
|
|
|
|
void carryover::update_carryover(const team& t, const int gold, const bool add){
|
|
gold_ += gold;
|
|
add_ = add;
|
|
color_ = t.color();
|
|
current_player_ = t.current_player();
|
|
name_ = t.name();
|
|
previous_recruits_.insert(t.recruits().begin(), t.recruits().end());
|
|
recall_list_.insert(recall_list_.end(), t.recall_list().begin(), t.recall_list().end());
|
|
}
|
|
|
|
void carryover::initialize_team(config& side_cfg){
|
|
transfer_all_gold_to(side_cfg);
|
|
}
|
|
|
|
const std::string carryover::to_string(){
|
|
std::string side = "";
|
|
side.append("Side " + save_id_ + ": gold " + str_cast<int>(gold_) + " recruits " + get_recruits(false) + " units ");
|
|
BOOST_FOREACH(const unit& u, recall_list_){
|
|
side.append(u.name() + ", ");
|
|
}
|
|
return side;
|
|
}
|
|
|
|
void carryover::to_config(config& cfg){
|
|
config& side = cfg.add_child("side");
|
|
side["save_id"] = save_id_;
|
|
side["gold"] = gold_;
|
|
side["add"] = add_;
|
|
side["color"] = color_;
|
|
side["current_player"] = current_player_;
|
|
side["name"] = name_;
|
|
side["previous_recruits"] = get_recruits(false);
|
|
BOOST_FOREACH(unit& u, recall_list_){
|
|
config& unit_cfg = side.add_child("unit");
|
|
u.write(unit_cfg);
|
|
}
|
|
}
|
|
|
|
carryover_info::carryover_info(const config& cfg)
|
|
: carryover_sides_()
|
|
, end_level_()
|
|
, variables_(cfg.child_or_empty("variables"))
|
|
, rng_(cfg)
|
|
, wml_menu_items_()
|
|
, difficulty_(cfg["difficulty"].empty() ? DEFAULT_DIFFICULTY : cfg["difficulty"].str())
|
|
, next_scenario_(cfg["next_scenario"])
|
|
{
|
|
end_level_.read(cfg.child_or_empty("end_level_data"));
|
|
BOOST_FOREACH(const config& side, cfg.child_range("side")){
|
|
this->carryover_sides_.push_back(carryover(side));
|
|
}
|
|
|
|
wml_menu_items_.set_menu_items(cfg);
|
|
}
|
|
|
|
std::vector<carryover>& carryover_info::get_all_sides() {
|
|
return carryover_sides_;
|
|
}
|
|
|
|
void carryover_info::add_side(const config& cfg) {
|
|
carryover_sides_.push_back(carryover(cfg));
|
|
}
|
|
|
|
|
|
const end_level_data& carryover_info::get_end_level() const{
|
|
return end_level_;
|
|
}
|
|
|
|
void carryover_info::transfer_from(const team& t, int carryover_gold){
|
|
BOOST_FOREACH(carryover& side, carryover_sides_){
|
|
if(side.get_save_id() == t.save_id()){
|
|
side.update_carryover(t, carryover_gold, end_level_.carryover_add);
|
|
return;
|
|
}
|
|
}
|
|
|
|
carryover_sides_.push_back(carryover(t, carryover_gold, end_level_.carryover_add));
|
|
}
|
|
|
|
void carryover_info::transfer_all_to(config& side_cfg){
|
|
if(side_cfg["save_id"].empty()){
|
|
side_cfg["save_id"] = side_cfg["id"];
|
|
}
|
|
BOOST_FOREACH(carryover& side, carryover_sides_){
|
|
if(side.get_save_id() == side_cfg["save_id"]){
|
|
side.transfer_all_gold_to(side_cfg);
|
|
side.transfer_all_recalls_to(side_cfg);
|
|
side.transfer_all_recruits_to(side_cfg);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void carryover_info::transfer_from(game_data& gamedata){
|
|
variables_ = gamedata.get_variables();
|
|
wml_menu_items_ = gamedata.get_wml_menu_items();
|
|
rng_ = gamedata.rng();
|
|
difficulty_ = gamedata.difficulty();
|
|
next_scenario_ = gamedata.next_scenario();
|
|
}
|
|
|
|
void carryover_info::transfer_to(config& level){
|
|
//if the game has been loaded from a snapshot, the existing variables will be the current ones
|
|
if(!level.has_child("variables")) {
|
|
level.add_child("variables", variables_);
|
|
}
|
|
|
|
config::attribute_value & seed_value = level["random_seed"];
|
|
if ( seed_value.empty() ) {
|
|
seed_value = rng_.get_random_seed();
|
|
level["random_calls"] = rng_.get_random_calls();
|
|
}
|
|
|
|
if(!level.has_child("menu_item")){
|
|
wml_menu_items_.to_config(level);
|
|
}
|
|
|
|
if(level["difficulty"].empty()){
|
|
level["difficulty"] = difficulty_;
|
|
}
|
|
difficulty_ = "";
|
|
next_scenario_ = "";
|
|
|
|
}
|
|
|
|
const config carryover_info::to_config() {
|
|
config cfg;
|
|
|
|
cfg["difficulty"] = difficulty_;
|
|
cfg["next_scenario"] = next_scenario_;
|
|
|
|
BOOST_FOREACH(carryover& c, carryover_sides_){
|
|
c.to_config(cfg);
|
|
}
|
|
config& end_level = cfg.add_child("end_level_data");
|
|
end_level_.write(end_level);
|
|
|
|
cfg["random_seed"] = rng_.get_random_seed();
|
|
cfg["random_calls"] = rng_.get_random_calls();
|
|
|
|
cfg.add_child("variables", variables_);
|
|
|
|
wml_menu_items_.to_config(cfg);
|
|
|
|
return cfg;
|
|
}
|
|
|
|
carryover* carryover_info::get_side(std::string save_id){
|
|
BOOST_FOREACH(carryover& side, carryover_sides_){
|
|
if(side.get_save_id() == save_id){
|
|
return &side;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
class team_builder {
|
|
public:
|
|
team_builder(const config& side_cfg,
|
|
const std::string &save_id, std::vector<team>& teams,
|
|
const config& level, gamemap& map, unit_map& units,
|
|
bool snapshot, const config &starting_pos)
|
|
: gold_info_ngold_(0)
|
|
, leader_configs_()
|
|
, level_(level)
|
|
, map_(map)
|
|
, player_cfg_(NULL)
|
|
, player_exists_(false)
|
|
, save_id_(save_id)
|
|
, seen_ids_()
|
|
, side_(0)
|
|
, side_cfg_(side_cfg)
|
|
, snapshot_(snapshot)
|
|
, starting_pos_(starting_pos)
|
|
, t_(NULL)
|
|
, teams_(teams)
|
|
, unit_configs_()
|
|
, units_(units)
|
|
{
|
|
}
|
|
|
|
void build_team_stage_one()
|
|
{
|
|
//initialize the context variables and flags, find relevant tags, set up everything
|
|
init();
|
|
|
|
//find out the correct qty of gold and handle gold carryover.
|
|
gold();
|
|
|
|
//create a new instance of team and push it to back of resources::teams vector
|
|
new_team();
|
|
|
|
assert(t_!=NULL);
|
|
|
|
//set team objectives if necessary
|
|
objectives();
|
|
|
|
// If the game state specifies additional units that can be recruited by the player, add them.
|
|
previous_recruits();
|
|
|
|
//place leader
|
|
leader();
|
|
|
|
//prepare units, populate obvious recall lists elements
|
|
prepare_units();
|
|
|
|
}
|
|
|
|
|
|
void build_team_stage_two()
|
|
{
|
|
//place units
|
|
//this is separate stage because we need to place units only after every other team is constructed
|
|
place_units();
|
|
|
|
}
|
|
|
|
protected:
|
|
|
|
static const std::string default_gold_qty_;
|
|
|
|
int gold_info_ngold_;
|
|
std::deque<config> leader_configs_;
|
|
const config &level_;
|
|
gamemap &map_;
|
|
const config *player_cfg_;
|
|
bool player_exists_;
|
|
const std::string save_id_;
|
|
std::set<std::string> seen_ids_;
|
|
int side_;
|
|
const config &side_cfg_;
|
|
bool snapshot_;
|
|
const config &starting_pos_;
|
|
team *t_;
|
|
std::vector<team> &teams_;
|
|
std::vector<const config*> unit_configs_;
|
|
unit_map &units_;
|
|
|
|
|
|
void log_step(const char *s) const
|
|
{
|
|
LOG_NG_TC << "team "<<side_<<" construction: "<< s << std::endl;
|
|
}
|
|
|
|
|
|
void init()
|
|
{
|
|
side_ = side_cfg_["side"].to_int(1);
|
|
if (side_ == 0) // Otherwise falls into the next error, with a very confusing message
|
|
throw config::error("Side number 0 encountered. Side numbers start at 1");
|
|
if (unsigned(side_ - 1) >= teams_.size()) {
|
|
std::stringstream ss;
|
|
ss << "Side number " << side_ << " higher than number of sides (" << teams_.size() << ")";
|
|
throw config::error(ss.str());
|
|
}
|
|
if (teams_[side_ - 1].side() != 0) {
|
|
std::stringstream ss;
|
|
ss << "Duplicate definition of side " << side_;
|
|
throw config::error(ss.str());
|
|
}
|
|
t_ = &teams_[side_ - 1];
|
|
|
|
log_step("init");
|
|
|
|
player_cfg_ = NULL;
|
|
//track whether a [player] tag with persistence information exists (in addition to the [side] tag)
|
|
player_exists_ = false;
|
|
|
|
if(map_.empty()) {
|
|
throw game::load_game_failed("Map not found");
|
|
}
|
|
|
|
DBG_NG_TC << "save id: "<< save_id_ <<std::endl;
|
|
DBG_NG_TC << "snapshot: "<< (player_exists_ ? "true" : "false") <<std::endl;
|
|
|
|
unit_configs_.clear();
|
|
seen_ids_.clear();
|
|
|
|
}
|
|
|
|
|
|
bool use_player_cfg() const
|
|
{
|
|
return (player_cfg_ != NULL) && (!snapshot_);
|
|
}
|
|
|
|
void gold()
|
|
{
|
|
log_step("gold");
|
|
|
|
gold_info_ngold_ = side_cfg_["gold"];
|
|
|
|
DBG_NG_TC << "set gold to '" << gold_info_ngold_ << "'\n";
|
|
//DBG_NG_TC << "set gold add flag to '" << gold_info_add_ << "'\n";
|
|
}
|
|
|
|
|
|
void new_team()
|
|
{
|
|
log_step("new team");
|
|
t_->build(side_cfg_, map_, gold_info_ngold_);
|
|
//t_->set_gold_add(gold_info_add_);
|
|
}
|
|
|
|
|
|
void objectives()
|
|
{
|
|
log_step("objectives");
|
|
// If this team has no objectives, set its objectives
|
|
// to the level-global "objectives"
|
|
if (t_->objectives().empty())
|
|
t_->set_objectives(level_["objectives"], false);
|
|
}
|
|
|
|
|
|
void previous_recruits()
|
|
{
|
|
log_step("previous recruits");
|
|
// If the game state specifies units that
|
|
// can be recruited for the player, add them.
|
|
if (!side_cfg_) return;
|
|
if (const config::attribute_value *v = side_cfg_.get("previous_recruits")) {
|
|
BOOST_FOREACH(const std::string &rec, utils::split(*v)) {
|
|
DBG_NG_TC << "adding previous recruit: " << rec << '\n';
|
|
t_->add_recruit(rec);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void handle_unit(const config &u, const char *origin)
|
|
{
|
|
DBG_NG_TC
|
|
<< "unit from "<<origin
|
|
<< ": type=["<<u["type"]
|
|
<< "] id=["<<u["id"]
|
|
<< "] placement=["<<u["placement"]
|
|
<< "] x=["<<u["x"]
|
|
<< "] y=["<<u["y"]
|
|
<<"]"<< std::endl;
|
|
const std::string &id = u["id"];
|
|
if (!id.empty()) {
|
|
if ( seen_ids_.find(id)!=seen_ids_.end() ) {
|
|
//seen before
|
|
config u_tmp = u;
|
|
u_tmp["side"] = str_cast(side_);
|
|
unit new_unit(u_tmp, true);
|
|
t_->recall_list().push_back(new_unit);
|
|
} else {
|
|
//not seen before
|
|
unit_configs_.push_back(&u);
|
|
seen_ids_.insert(id);
|
|
}
|
|
|
|
} else {
|
|
unit_configs_.push_back(&u);
|
|
}
|
|
}
|
|
|
|
void handle_leader(const config &leader)
|
|
{
|
|
// Make a persistent copy of the config.
|
|
leader_configs_.push_back(leader);
|
|
config & stored = leader_configs_.back();
|
|
|
|
// Remove the attributes used to define a side.
|
|
for ( size_t i = 0; team::attributes[i] != NULL; ++i ) {
|
|
stored.remove_attribute(team::attributes[i]);
|
|
}
|
|
|
|
// Provide some default values, if not specified.
|
|
config::attribute_value &a1 = stored["canrecruit"];
|
|
if (a1.blank()) a1 = true;
|
|
config::attribute_value &a2 = stored["placement"];
|
|
if (a2.blank()) a2 = "map,leader";
|
|
|
|
// Add the leader to the list of units to create.
|
|
handle_unit(stored, "leader_cfg");
|
|
}
|
|
|
|
void leader()
|
|
{
|
|
log_step("leader");
|
|
// If this side tag describes the leader of the side, we can simply add it to front of unit queue
|
|
// there was a hack: if this side tag describes the leader of the side,
|
|
// we may replace the leader with someone from recall list who can recruit, but take positioning from [side]
|
|
// this hack shall be removed, since it messes up with 'multiple leaders'
|
|
|
|
// If this side tag describes the leader of the side
|
|
if (!side_cfg_["no_leader"].to_bool() && side_cfg_["controller"] != "null") {
|
|
if (side_cfg_["type"] == "random") {
|
|
std::vector<std::string> types = utils::split(side_cfg_["random_leader"]);
|
|
if (types.empty())
|
|
types = utils::split(side_cfg_["leader"]);
|
|
if (types.empty()) {
|
|
utils::string_map i18n_symbols;
|
|
i18n_symbols["faction"] = side_cfg_["name"];
|
|
throw config::error(vgettext("Unable to find a leader type for faction $faction", i18n_symbols));
|
|
}
|
|
const int choice = rand() % types.size();
|
|
config leader = side_cfg_;
|
|
leader["type"] = types[choice];
|
|
handle_leader(leader);
|
|
} else
|
|
handle_leader(side_cfg_);
|
|
}
|
|
BOOST_FOREACH(const config &l, side_cfg_.child_range("leader")) {
|
|
handle_leader(l);
|
|
}
|
|
}
|
|
|
|
|
|
void prepare_units()
|
|
{
|
|
log_step("prepare units");
|
|
if (use_player_cfg()) {
|
|
//units in [replay_start][side] merged with [side]
|
|
//only relevant in start-of-scenario saves, that's why !shapshot
|
|
//units that are in '[scenario][side]' are 'first'
|
|
//for create-or-recall semantics to work: for each unit with non-empty
|
|
//id, unconditionally put OTHER, later, units with same id directly to
|
|
//recall list, not including them in unit_configs_
|
|
BOOST_FOREACH(const config &u, (*player_cfg_).child_range("unit")) {
|
|
handle_unit(u,"player_cfg");
|
|
}
|
|
|
|
} else {
|
|
//units in [side]
|
|
BOOST_FOREACH(const config &su, side_cfg_.child_range("unit")) {
|
|
handle_unit(su, "side_cfg");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void place_units()
|
|
{
|
|
log_step("place units");
|
|
unit_creator uc(*t_,map_.starting_position(side_));
|
|
uc
|
|
.allow_add_to_recall(true)
|
|
.allow_discover(true)
|
|
.allow_get_village(true)
|
|
.allow_invalidate(false)
|
|
.allow_rename_side(true)
|
|
.allow_show(false);
|
|
|
|
BOOST_FOREACH(const config *u, unit_configs_) {
|
|
uc.add_unit(*u);
|
|
}
|
|
|
|
// Find the first leader and use its name as the player name.
|
|
unit_map::iterator u = resources::units->find_first_leader(t_->side());
|
|
if ((u != resources::units->end()) && t_->current_player().empty())
|
|
t_->set_current_player(u->name());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
game_data::game_data()
|
|
: scoped_variables()
|
|
, last_selected(map_location::null_location)
|
|
, wml_menu_items_()
|
|
, rng_()
|
|
, variables_()
|
|
, temporaries_()
|
|
, generator_setter_(&recorder)
|
|
, phase_(INITIAL)
|
|
, can_end_turn_(true)
|
|
, difficulty_(DEFAULT_DIFFICULTY)
|
|
, scenario_()
|
|
, next_scenario_()
|
|
{}
|
|
|
|
game_data::game_data(const config& level)
|
|
: scoped_variables()
|
|
, last_selected(map_location::null_location)
|
|
, wml_menu_items_()
|
|
, rng_(level)
|
|
, variables_(level.child_or_empty("variables"))
|
|
, temporaries_()
|
|
, generator_setter_(&recorder)
|
|
, phase_(INITIAL)
|
|
, can_end_turn_(level["can_end_turn"].to_bool(true))
|
|
, difficulty_(level["difficulty"].empty() ? DEFAULT_DIFFICULTY : level["difficulty"].str())
|
|
, scenario_(level["id"])
|
|
, next_scenario_(level["next_scenario"])
|
|
{
|
|
wml_menu_items_.set_menu_items(level);
|
|
}
|
|
|
|
game_data::game_data(const game_data& data)
|
|
: variable_set() // Not sure why empty, copied from old code
|
|
, scoped_variables(data.scoped_variables)
|
|
, last_selected(data.last_selected)
|
|
, wml_menu_items_(data.wml_menu_items_)
|
|
, rng_(data.rng_)
|
|
, variables_(data.variables_)
|
|
, temporaries_()
|
|
, generator_setter_(data.generator_setter_)
|
|
, phase_(data.phase_)
|
|
, can_end_turn_(data.can_end_turn_)
|
|
, difficulty_(data.difficulty_)
|
|
, scenario_(data.scenario_)
|
|
, next_scenario_(data.next_scenario_)
|
|
{}
|
|
|
|
game_data::~game_data()
|
|
{
|
|
wml_menu_items_.clear_wmi();
|
|
}
|
|
|
|
config::attribute_value &game_data::get_variable(const std::string& key)
|
|
{
|
|
return variable_info(key, true, variable_info::TYPE_SCALAR).as_scalar();
|
|
}
|
|
|
|
config::attribute_value game_data::get_variable_const(const std::string &key) const
|
|
{
|
|
variable_info to_get(key, false, variable_info::TYPE_SCALAR);
|
|
if (!to_get.is_valid)
|
|
{
|
|
config::attribute_value &to_return = temporaries_[key];
|
|
if (key.size() > 7 && key.substr(key.size() - 7) == ".length") {
|
|
// length is a special attribute, so guarantee its correctness
|
|
to_return = 0;
|
|
}
|
|
return to_return;
|
|
}
|
|
return to_get.as_scalar();
|
|
}
|
|
|
|
config& game_data::get_variable_cfg(const std::string& key)
|
|
{
|
|
return variable_info(key, true, variable_info::TYPE_CONTAINER).as_container();
|
|
}
|
|
|
|
void game_data::set_variable(const std::string& key, const t_string& value)
|
|
{
|
|
get_variable(key) = value;
|
|
}
|
|
|
|
config& game_data::add_variable_cfg(const std::string& key, const config& value)
|
|
{
|
|
variable_info to_add(key, true, variable_info::TYPE_ARRAY);
|
|
return to_add.vars->add_child(to_add.key, value);
|
|
}
|
|
|
|
void game_data::clear_variable_cfg(const std::string& varname)
|
|
{
|
|
variable_info to_clear(varname, false, variable_info::TYPE_CONTAINER);
|
|
if(!to_clear.is_valid) return;
|
|
if(to_clear.explicit_index) {
|
|
to_clear.vars->remove_child(to_clear.key, to_clear.index);
|
|
} else {
|
|
to_clear.vars->clear_children(to_clear.key);
|
|
}
|
|
}
|
|
|
|
void game_data::clear_variable(const std::string& varname)
|
|
{
|
|
variable_info to_clear(varname, false);
|
|
if(!to_clear.is_valid) return;
|
|
if(to_clear.explicit_index) {
|
|
to_clear.vars->remove_child(to_clear.key, to_clear.index);
|
|
} else {
|
|
to_clear.vars->clear_children(to_clear.key);
|
|
to_clear.vars->remove_attribute(to_clear.key);
|
|
}
|
|
}
|
|
|
|
void game_data::write_snapshot(config& cfg){
|
|
cfg["difficulty"] = difficulty_;
|
|
cfg["scenario"] = scenario_;
|
|
cfg["next_scenario"] = next_scenario_;
|
|
|
|
cfg["can_end_turn"] = can_end_turn_;
|
|
|
|
cfg["random_seed"] = rng_.get_random_seed();
|
|
cfg["random_calls"] = rng_.get_random_calls();
|
|
|
|
cfg.add_child("variables", variables_);
|
|
|
|
wml_menu_items_.to_config(cfg);
|
|
}
|
|
|
|
void game_data::write_config(config_writer& out){
|
|
out.write_key_val("difficulty", difficulty_);
|
|
out.write_key_val("scenario", scenario_);
|
|
out.write_key_val("next_scenario", next_scenario_);
|
|
|
|
out.write_key_val("random_seed", lexical_cast<std::string>(rng_.get_random_seed()));
|
|
out.write_key_val("random_calls", lexical_cast<std::string>(rng_.get_random_calls()));
|
|
out.write_child("variables", variables_);
|
|
|
|
config cfg;
|
|
wml_menu_items_.to_config(cfg);
|
|
out.write_child("menu_item", cfg);
|
|
}
|
|
|
|
team_builder_ptr game_data::create_team_builder(const config& side_cfg,
|
|
std::string save_id, std::vector<team>& teams,
|
|
const config& level, gamemap& map, unit_map& units,
|
|
bool snapshot, const config& starting_pos)
|
|
{
|
|
return team_builder_ptr(new team_builder(side_cfg,save_id,teams,level,map,units,snapshot,starting_pos));
|
|
}
|
|
|
|
void game_data::build_team_stage_one(team_builder_ptr tb_ptr)
|
|
{
|
|
tb_ptr->build_team_stage_one();
|
|
}
|
|
|
|
void game_data::build_team_stage_two(team_builder_ptr tb_ptr)
|
|
{
|
|
tb_ptr->build_team_stage_two();
|
|
}
|
|
|
|
game_data& game_data::operator=(const game_data& info)
|
|
{
|
|
// Use copy constructor to make sure we are coherent
|
|
if (this != &info) {
|
|
this->~game_data();
|
|
new (this) game_data(info) ;
|
|
}
|
|
return *this ;
|
|
}
|
|
|
|
game_data* game_data::operator=(const game_data* info)
|
|
{
|
|
// Use copy constructor to make sure we are coherent
|
|
if (this != info) {
|
|
this->~game_data();
|
|
new (this) game_data(*info) ;
|
|
}
|
|
return this ;
|
|
}
|
|
|
|
game_classification::game_classification():
|
|
savegame_config(),
|
|
label(),
|
|
parent(),
|
|
version(),
|
|
campaign_type(),
|
|
campaign_define(),
|
|
campaign_xtra_defines(),
|
|
campaign(),
|
|
history(),
|
|
abbrev(),
|
|
completion(),
|
|
end_credits(true),
|
|
end_text(),
|
|
end_text_duration()
|
|
{}
|
|
|
|
game_classification::game_classification(const config& cfg):
|
|
savegame_config(),
|
|
label(cfg["label"]),
|
|
parent(cfg["parent"]),
|
|
version(cfg["version"]),
|
|
campaign_type(cfg["campaign_type"].empty() ? "scenario" : cfg["campaign_type"].str()),
|
|
campaign_define(cfg["campaign_define"]),
|
|
campaign_xtra_defines(utils::split(cfg["campaign_extra_defines"])),
|
|
campaign(cfg["campaign"]),
|
|
history(cfg["history"]),
|
|
abbrev(cfg["abbrev"]),
|
|
completion(cfg["completion"]),
|
|
end_credits(cfg["end_credits"].to_bool(true)),
|
|
end_text(cfg["end_text"]),
|
|
end_text_duration(cfg["end_text_duration"])
|
|
{}
|
|
|
|
game_classification::game_classification(const game_classification& gc):
|
|
savegame_config(),
|
|
label(gc.label),
|
|
parent(gc.parent),
|
|
version(gc.version),
|
|
campaign_type(gc.campaign_type),
|
|
campaign_define(gc.campaign_define),
|
|
campaign_xtra_defines(gc.campaign_xtra_defines),
|
|
campaign(gc.campaign),
|
|
history(gc.history),
|
|
abbrev(gc.abbrev),
|
|
completion(gc.completion),
|
|
end_credits(gc.end_credits),
|
|
end_text(gc.end_text),
|
|
end_text_duration(gc.end_text_duration)
|
|
{
|
|
}
|
|
|
|
config game_classification::to_config() const
|
|
{
|
|
config cfg;
|
|
|
|
cfg["label"] = label;
|
|
cfg["parent"] = parent;
|
|
cfg["version"] = game_config::version;
|
|
cfg["campaign_type"] = campaign_type;
|
|
cfg["campaign_define"] = campaign_define;
|
|
cfg["campaign_extra_defines"] = utils::join(campaign_xtra_defines);
|
|
cfg["campaign"] = campaign;
|
|
cfg["history"] = history;
|
|
cfg["abbrev"] = abbrev;
|
|
cfg["completion"] = completion;
|
|
cfg["end_credits"] = end_credits;
|
|
cfg["end_text"] = end_text;
|
|
cfg["end_text_duration"] = str_cast<unsigned int>(end_text_duration);
|
|
|
|
return cfg;
|
|
}
|
|
|
|
game_state::game_state() :
|
|
replay_data(),
|
|
snapshot(),
|
|
carryover_sides(),
|
|
carryover_sides_start(carryover_info().to_config()),
|
|
replay_start_(),
|
|
classification_(),
|
|
mp_settings_()
|
|
{}
|
|
|
|
void write_players(game_state& gamestate, config& cfg, const bool use_snapshot, const bool merge_side)
|
|
{
|
|
// If there is already a player config available it means we are loading
|
|
// from a savegame. Don't do anything then, the information is already there
|
|
config::child_itors player_cfg = cfg.child_range("player");
|
|
if (player_cfg.first != player_cfg.second)
|
|
return;
|
|
|
|
config *source = NULL;
|
|
if (use_snapshot) {
|
|
source = &gamestate.carryover_sides_start;
|
|
} else {
|
|
source = &gamestate.replay_start();
|
|
}
|
|
|
|
if (merge_side) {
|
|
//merge sides/players from starting pos with the scenario cfg
|
|
std::vector<std::string> tags;
|
|
tags.push_back("side");
|
|
tags.push_back("player"); //merge [player] tags for backwards compatibility of saves
|
|
|
|
BOOST_FOREACH(const std::string& side_tag, tags)
|
|
{
|
|
BOOST_FOREACH(config &carryover_side, source->child_range(side_tag))
|
|
{
|
|
config *scenario_side = NULL;
|
|
|
|
//TODO: use the player_id instead of the save_id for that
|
|
if (config& c = cfg.find_child("side", "save_id", carryover_side["save_id"])) {
|
|
scenario_side = &c;
|
|
} else if (config& c = cfg.find_child("side", "id", carryover_side["save_id"])) {
|
|
scenario_side = &c;
|
|
}
|
|
|
|
if (scenario_side == NULL) {
|
|
//no matching side in the current scenario, we add the persistent information in a [player] tag
|
|
cfg.add_child("player", carryover_side);
|
|
continue;
|
|
}
|
|
|
|
//we have a matching side in the current scenario
|
|
|
|
//sort carryover gold
|
|
int ngold = (*scenario_side)["gold"].to_int(100);
|
|
int player_gold = carryover_side["gold"];
|
|
if (carryover_side["gold_add"].to_bool()) {
|
|
ngold += player_gold;
|
|
} else if (player_gold >= ngold) {
|
|
ngold = player_gold;
|
|
}
|
|
carryover_side["gold"] = str_cast(ngold);
|
|
if (const config::attribute_value *v = scenario_side->get("gold_add")) {
|
|
carryover_side["gold_add"] = *v;
|
|
}
|
|
//merge player information into the scenario cfg
|
|
(*scenario_side)["save_id"] = carryover_side["save_id"];
|
|
(*scenario_side)["gold"] = ngold;
|
|
(*scenario_side)["gold_add"] = carryover_side["gold_add"];
|
|
if (const config::attribute_value *v = carryover_side.get("previous_recruits")) {
|
|
(*scenario_side)["previous_recruits"] = *v;
|
|
} else {
|
|
(*scenario_side)["previous_recruits"] = carryover_side["can_recruit"];
|
|
}
|
|
(*scenario_side)["name"] = carryover_side["name"];
|
|
(*scenario_side)["current_player"] = carryover_side["current_player"];
|
|
|
|
(*scenario_side)["color"] = carryover_side["color"];
|
|
|
|
//add recallable units
|
|
BOOST_FOREACH(const config &u, carryover_side.child_range("unit")) {
|
|
scenario_side->add_child("unit", u);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
BOOST_FOREACH(const config &snapshot_side, source->child_range("side")) {
|
|
//take all side tags and add them as players (assuming they only contain carryover information)
|
|
cfg.add_child("player", snapshot_side);
|
|
}
|
|
}
|
|
}
|
|
|
|
game_state::game_state(const config& cfg, bool show_replay) :
|
|
replay_data(),
|
|
snapshot(),
|
|
carryover_sides(),
|
|
carryover_sides_start(),
|
|
replay_start_(),
|
|
classification_(cfg),
|
|
mp_settings_(cfg)
|
|
{
|
|
n_unit::id_manager::instance().set_save_id(cfg["next_underlying_unit_id"]);
|
|
log_scope("read_game");
|
|
|
|
if(cfg.has_child("carryover_sides")){
|
|
carryover_sides = cfg.child("carryover_sides");
|
|
}
|
|
if(cfg.has_child("carryover_sides_start")){
|
|
carryover_sides_start = cfg.child("carryover_sides_start");
|
|
}
|
|
|
|
if(show_replay){
|
|
//If replay_start and replay_data couldn't be loaded
|
|
if(!load_replay(cfg)){
|
|
//TODO: notify user of failure
|
|
ERR_NG<<"Could not load as replay \n";
|
|
}
|
|
} else {
|
|
if(const config& snapshot = cfg.child("snapshot")){
|
|
this->snapshot = snapshot;
|
|
load_replay(cfg);
|
|
} else if(carryover_sides_start.empty() && !carryover_sides.empty()){
|
|
//if we are loading a start of scenario save and don't have carryover_sides_start, use carryover_sides
|
|
carryover_sides_start = carryover_sides;
|
|
}
|
|
//TODO: check if loading fails completely
|
|
}
|
|
|
|
LOG_NG << "scenario: '" << carryover_sides_start["next_scenario"] << "'\n";
|
|
|
|
if (const config &stats = cfg.child("statistics")) {
|
|
statistics::fresh_stats();
|
|
statistics::read_stats(stats);
|
|
}
|
|
|
|
}
|
|
|
|
bool game_state::load_replay(const config& cfg){
|
|
bool replay_loaded = false;
|
|
|
|
if(const config& replay_start = cfg.child("replay_start")){
|
|
replay_start_ = replay_start;
|
|
if(const config& replay = cfg.child("replay")){
|
|
this->replay_data = replay;
|
|
replay_loaded = true;
|
|
}
|
|
}
|
|
|
|
return replay_loaded;
|
|
}
|
|
|
|
void convert_old_saves(config& cfg){
|
|
if(!cfg.has_child("snapshot")){
|
|
return;
|
|
}
|
|
|
|
const config& snapshot = cfg.child("snapshot");
|
|
const config& replay_start = cfg.child("replay_start");
|
|
const config& replay = cfg.child("replay");
|
|
|
|
if(!cfg.has_child("carryover_sides") && !cfg.has_child("carryover_sides_start")){
|
|
config carryover;
|
|
//copy rng and menu items from toplevel to new carryover_sides
|
|
carryover["random_seed"] = cfg["random_seed"];
|
|
carryover["random_calls"] = cfg["random_calls"];
|
|
BOOST_FOREACH(const config& menu_item, cfg.child_range("menu_item")){
|
|
carryover.add_child("menu_item", menu_item);
|
|
}
|
|
carryover["difficulty"] = cfg["difficulty"];
|
|
//the scenario to be played is always stored as next_scenario in carryover_sides_start
|
|
carryover["next_scenario"] = cfg["scenario"];
|
|
|
|
config carryover_start = carryover;
|
|
|
|
//copy sides from either snapshot or replay_start to new carryover_sides
|
|
if(!snapshot.empty()){
|
|
BOOST_FOREACH(const config& side, snapshot.child_range("side")){
|
|
carryover.add_child("side", side);
|
|
}
|
|
//for compatibility with old savegames that use player instead of side
|
|
BOOST_FOREACH(const config& side, snapshot.child_range("player")){
|
|
carryover.add_child("side", side);
|
|
}
|
|
//save the sides from replay_start in carryover_sides_start
|
|
BOOST_FOREACH(const config& side, replay_start.child_range("side")){
|
|
carryover_start.add_child("side", side);
|
|
}
|
|
//for compatibility with old savegames that use player instead of side
|
|
BOOST_FOREACH(const config& side, replay_start.child_range("player")){
|
|
carryover_start.add_child("side", side);
|
|
}
|
|
} else if (!replay_start.empty()){
|
|
BOOST_FOREACH(const config& side, replay_start.child_range("side")){
|
|
carryover.add_child("side", side);
|
|
carryover_start.add_child("side", side);
|
|
}
|
|
//for compatibility with old savegames that use player instead of side
|
|
BOOST_FOREACH(const config& side, replay_start.child_range("player")){
|
|
carryover.add_child("side", side);
|
|
carryover_start.add_child("side", side);
|
|
}
|
|
}
|
|
|
|
//get variables according to old hierarchy and copy them to new carryover_sides
|
|
if(!snapshot.empty()){
|
|
if(const config& variables = snapshot.child("variables")){
|
|
carryover.add_child("variables", variables);
|
|
carryover_start.add_child("variables", replay_start.child_or_empty("variables"));
|
|
} else if (const config& variables = cfg.child("variables")){
|
|
carryover.add_child("variables", variables);
|
|
carryover_start.add_child("variables", variables);
|
|
}
|
|
} else if (!replay_start.empty()){
|
|
if(const config& variables = replay_start.child("variables")){
|
|
carryover.add_child("variables", variables);
|
|
carryover_start.add_child("variables", variables);
|
|
}
|
|
} else {
|
|
carryover.add_child("variables", cfg.child("variables"));
|
|
carryover_start.add_child("variables", cfg.child("variables"));
|
|
}
|
|
|
|
cfg.add_child("carryover_sides", carryover);
|
|
cfg.add_child("carryover_sides_start", carryover_start);
|
|
}
|
|
|
|
//if replay and snapshot are empty we've got a start of scenario save and don't want replay_start either
|
|
if(replay.empty() && snapshot.empty()){
|
|
LOG_RG<<"removing replay_start \n";
|
|
cfg.remove_child("replay_start", 0);
|
|
}
|
|
|
|
//remove empty replay or snapshot so type of save can be detected more easily
|
|
if(replay.empty()){
|
|
LOG_RG<<"removing replay \n";
|
|
cfg.remove_child("replay", 0);
|
|
}
|
|
|
|
if(snapshot.empty()){
|
|
LOG_RG<<"removing snapshot \n";
|
|
cfg.remove_child("snapshot", 0);
|
|
}
|
|
|
|
LOG_RG<<"cfg after conversion "<<cfg<<"\n";
|
|
}
|
|
|
|
void game_state::write_snapshot(config& cfg, game_display* gui) const
|
|
{
|
|
log_scope("write_game");
|
|
if(gui != NULL){
|
|
cfg["snapshot"] = true;
|
|
cfg["playing_team"] = str_cast(gui->playing_team());
|
|
|
|
game_events::write_events(cfg);
|
|
|
|
sound::write_music_play_list(cfg);
|
|
}
|
|
|
|
cfg["label"] = classification_.label;
|
|
cfg["history"] = classification_.history;
|
|
cfg["abbrev"] = classification_.abbrev;
|
|
cfg["version"] = game_config::version;
|
|
|
|
cfg["completion"] = classification_.completion;
|
|
|
|
cfg["campaign"] = classification_.campaign;
|
|
cfg["campaign_type"] = classification_.campaign_type;
|
|
|
|
cfg["campaign_define"] = classification_.campaign_define;
|
|
cfg["campaign_extra_defines"] = utils::join(classification_.campaign_xtra_defines);
|
|
cfg["next_underlying_unit_id"] = str_cast(n_unit::id_manager::instance().get_save_id());
|
|
|
|
cfg["end_credits"] = classification_.end_credits;
|
|
cfg["end_text"] = classification_.end_text;
|
|
cfg["end_text_duration"] = str_cast<unsigned int>(classification_.end_text_duration);
|
|
|
|
if(resources::gamedata != NULL){
|
|
resources::gamedata->write_snapshot(cfg);
|
|
}
|
|
|
|
if(gui != NULL){
|
|
gui->labels().write(cfg);
|
|
}
|
|
}
|
|
|
|
void extract_summary_from_config(config& cfg_save, config& cfg_summary)
|
|
{
|
|
const config &cfg_snapshot = cfg_save.child("snapshot");
|
|
const config &cfg_replay_start = cfg_save.child("replay_start");
|
|
|
|
const config &cfg_replay = cfg_save.child("replay");
|
|
const bool has_replay = cfg_replay && !cfg_replay.empty();
|
|
const bool has_snapshot = cfg_snapshot && cfg_snapshot.child("side");
|
|
|
|
cfg_summary["replay"] = has_replay;
|
|
cfg_summary["snapshot"] = has_snapshot;
|
|
|
|
cfg_summary["label"] = cfg_save["label"];
|
|
cfg_summary["parent"] = cfg_save["parent"];
|
|
cfg_summary["campaign_type"] = cfg_save["campaign_type"];
|
|
|
|
if(cfg_save.has_child("carryover_sides_start")){
|
|
cfg_summary["scenario"] = cfg_save.child("carryover_sides_start")["next_scenario"];
|
|
cfg_summary["difficulty"] = cfg_save.child("carryover_sides_start")["difficulty"];
|
|
} else {
|
|
cfg_summary["scenario"] = cfg_save["scenario"];
|
|
cfg_summary["difficulty"] = cfg_save["difficulty"];
|
|
}
|
|
cfg_summary["campaign"] = cfg_save["campaign"];
|
|
cfg_summary["version"] = cfg_save["version"];
|
|
cfg_summary["corrupt"] = "";
|
|
|
|
if(has_snapshot) {
|
|
cfg_summary["turn"] = cfg_snapshot["turn_at"];
|
|
if (cfg_snapshot["turns"] != "-1") {
|
|
cfg_summary["turn"] = cfg_summary["turn"].str() + "/" + cfg_snapshot["turns"].str();
|
|
}
|
|
}
|
|
|
|
// Find the first human leader so we can display their icon in the load menu.
|
|
|
|
/** @todo Ideally we should grab all leaders if there's more than 1 human player? */
|
|
std::string leader;
|
|
std::string leader_image;
|
|
|
|
//BOOST_FOREACH(const config &p, cfg_save.child_range("player"))
|
|
//{
|
|
// if (p["canrecruit"].to_bool(false))) {
|
|
// leader = p["save_id"];
|
|
// }
|
|
//}
|
|
|
|
bool shrouded = false;
|
|
|
|
//if (!leader.empty())
|
|
//{
|
|
if (const config &snapshot = *(has_snapshot ? &cfg_snapshot : &cfg_replay_start))
|
|
{
|
|
BOOST_FOREACH(const config &side, snapshot.child_range("side"))
|
|
{
|
|
if (side["controller"] != "human") {
|
|
continue;
|
|
}
|
|
|
|
if (side["shroud"].to_bool()) {
|
|
shrouded = true;
|
|
}
|
|
|
|
if (side["canrecruit"].to_bool())
|
|
{
|
|
leader = side["id"].str();
|
|
leader_image = side["image"].str();
|
|
break;
|
|
}
|
|
|
|
BOOST_FOREACH(const config &u, side.child_range("unit"))
|
|
{
|
|
if (u["canrecruit"].to_bool()) {
|
|
leader = u["id"].str();
|
|
leader_image = u["image"].str();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//}
|
|
|
|
cfg_summary["leader"] = leader;
|
|
// We need a binary path-independent path to the leader image here
|
|
// so it can be displayed for campaign-specific units in the dialog
|
|
// even when the campaign isn't loaded yet.
|
|
cfg_summary["leader_image"] = get_independent_image_path(leader_image);
|
|
|
|
if(!shrouded) {
|
|
if(has_snapshot) {
|
|
if (!cfg_snapshot.find_child("side", "shroud", "yes")) {
|
|
cfg_summary.add_child("map", cfg_snapshot.child_or_empty("map"));
|
|
}
|
|
} else if(has_replay) {
|
|
if (!cfg_replay_start.find_child("side","shroud","yes")) {
|
|
cfg_summary.add_child("map", cfg_replay_start.child_or_empty("map"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
game_state::game_state(const game_state& state) :
|
|
replay_data(state.replay_data),
|
|
snapshot(state.snapshot),
|
|
carryover_sides(state.carryover_sides),
|
|
carryover_sides_start(state.carryover_sides_start),
|
|
replay_start_(state.replay_start_),
|
|
classification_(state.classification_),
|
|
mp_settings_(state.mp_settings_)
|
|
{}
|
|
|
|
game_state& game_state::operator=(const game_state& state)
|
|
{
|
|
// Use copy constructor to make sure we are coherent
|
|
if (this != &state) {
|
|
this->~game_state();
|
|
new (this) game_state(state) ;
|
|
}
|
|
return *this ;
|
|
}
|
|
|
|
|
|
void game_state::write_config(config_writer& out) const
|
|
{
|
|
out.write(classification_.to_config());
|
|
if (classification_.campaign_type == "multiplayer")
|
|
out.write_child("multiplayer", mp_settings_.to_config());
|
|
}
|
|
|