Overhaul of unit_movement_type

This is a general overhaul of the class embodying movement types,
featuring:

* Better data encapsulation

* Less duplication of code between unit.cpp and unit_type.cpp

* Easier to use

* New files for the class (VC and XCode projects still need updating)

* New (shorter) name for the class

Some additional revisions will be coming.

The primary motivation for this was to get a class that embodies
movement costs (part of the data encapsulation).
This commit is contained in:
J. Tyne 2013-02-20 02:25:14 +00:00
parent 3d9a8e4213
commit bb70a29017
18 changed files with 909 additions and 572 deletions

View file

@ -550,6 +550,8 @@
<Unit filename="../../src/mouse_events.hpp" />
<Unit filename="../../src/mouse_handler_base.cpp" />
<Unit filename="../../src/mouse_handler_base.hpp" />
<Unit filename="../../src/movetype.cpp" />
<Unit filename="../../src/movetype.hpp" />
<Unit filename="../../src/mp_game_settings.cpp" />
<Unit filename="../../src/mp_game_settings.hpp" />
<Unit filename="../../src/multiplayer.cpp" />

View file

@ -739,6 +739,8 @@
<Unit filename="..\..\src\mouse_events.hpp" />
<Unit filename="..\..\src\mouse_handler_base.cpp" />
<Unit filename="..\..\src\mouse_handler_base.hpp" />
<Unit filename="..\..\src\movetype.cpp" />
<Unit filename="..\..\src\movetype.hpp" />
<Unit filename="..\..\src\mp_depcheck.cpp" />
<Unit filename="..\..\src\mp_depcheck.hpp" />
<Unit filename="..\..\src\mp_game_settings.cpp" />

View file

@ -705,7 +705,7 @@ set(wesnoth-main_SRC
gui/dialogs/debug_clock.cpp
gui/dialogs/dialog.cpp
gui/dialogs/edit_label.cpp
gui/dialogs/editor/editor_edit_label.cpp
gui/dialogs/editor/editor_edit_label.cpp
gui/dialogs/editor_generate_map.cpp
gui/dialogs/editor_new_map.cpp
gui/dialogs/editor_resize_map.cpp
@ -752,6 +752,7 @@ set(wesnoth-main_SRC
menu_events.cpp
mouse_events.cpp
mouse_handler_base.cpp
movetype.cpp
mp_depcheck.cpp
mp_game_settings.cpp
mp_options.cpp

View file

@ -426,6 +426,7 @@ wesnoth_sources = Split("""
menu_events.cpp
mouse_events.cpp
mouse_handler_base.cpp
movetype.cpp
mp_depcheck.cpp
mp_game_settings.cpp
mp_options.cpp

View file

@ -326,7 +326,7 @@ int ai_default_recruitment_stage::average_resistance_against(const unit_type& a,
j_end = terrain.end(); j != j_end; ++j)
{
// Use only reachable tiles when computing the average defense.
if (a.movement_type().movement_cost(j->first) < unit_movement_type::UNREACHABLE) {
if (a.movement_type().movement_cost(j->first) < movetype::UNREACHABLE) {
defense += a.movement_type().defense_modifier(j->first) * j->second;
weighting_sum += j->second;
}

View file

@ -1154,7 +1154,7 @@ private:
}
for (std::vector<map_location>::const_iterator loc_iter = route.steps.begin() + 1 ; loc_iter !=route.steps.end(); ++loc_iter) {
if (unit_it->movement_cost((*resources::game_map)[*loc_iter]) < unit_movement_type::UNREACHABLE )
if (unit_it->movement_cost((*resources::game_map)[*loc_iter]) < movetype::UNREACHABLE )
locations.push_back( variant( new location_callable(*loc_iter) ));
else
break;

View file

@ -371,7 +371,7 @@ int recruitment_phase::average_resistance_against(const unit_type& a, const unit
j_end = terrain.end(); j != j_end; ++j)
{
// Use only reachable tiles when computing the average defense.
if (a.movement_type().movement_cost(j->first) < unit_movement_type::UNREACHABLE) {
if (a.movement_type().movement_cost(j->first) < movetype::UNREACHABLE) {
defense += a.movement_type().defense_modifier(j->first) * j->second;
weighting_sum += j->second;
}

View file

@ -257,7 +257,7 @@ static int average_resistance_against(const unit_type& a, const unit_type& b)
j_end = terrain.end(); j != j_end; ++j)
{
// Use only reachable tiles when computing the average defense.
if (a.movement_type().movement_cost(j->first) < unit_movement_type::UNREACHABLE) {
if (a.movement_type().movement_cost(j->first) < movetype::UNREACHABLE) {
defense += a.movement_type().defense_modifier(j->first) * j->second;
weighting_sum += j->second;
}

View file

@ -1573,7 +1573,7 @@ public:
push_header(first_res_row, _("Attack Type"));
push_header(first_res_row, _("Resistance"));
resistance_table.push_back(first_res_row);
const unit_movement_type &movement_type = type_.movement_type();
const movetype &movement_type = type_.movement_type();
utils::string_map dam_tab = movement_type.damage_table();
for(utils::string_map::const_iterator dam_it = dam_tab.begin(), dam_end = dam_tab.end();
dam_it != dam_end; ++dam_it) {
@ -1660,7 +1660,7 @@ public:
row.push_back(std::make_pair(markup,
font::line_width(str.str(), normal_font_size)));
//movement - range: 1 .. 5, unit_movement_type::UNREACHABLE=impassable
//movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
str.str(clear_stringstream);
const bool cannot_move = moves > type_.movement();
if (cannot_move) // cannot move in this terrain

607
src/movetype.cpp Normal file
View file

@ -0,0 +1,607 @@
/* $Id$ */
/*
Copyright (C) 2013 - 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
* Handle movement types.
*/
#include "movetype.hpp"
#include "log.hpp"
#include "map.hpp"
#include "resources.hpp"
#include "terrain_translation.hpp"
#include "unit_types.hpp" // for attack_type
#include <boost/foreach.hpp>
static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
#define WRN_CF LOG_STREAM(warn, log_config)
/* *** parameters *** */
namespace { // Some functions for use with parameters::eval.
/// Converts config defense values to a "max" value.
int config_to_max(int value)
{
return value < 0 ? -value : value;
}
/// Converts config defense values to a "min" value.
int config_to_min(int value)
{
return value < 0 ? -value : 0;
}
}
/// The parameters used when calculating a terrain-based value.
struct movetype::terrain_info::parameters
{
int min_value; /// The smallest allowable value.
int max_value; /// The largest allowable value.
int default_value; /// The default value (if no data is available).
int (*eval)(int); /// Converter for values taken from a config. May be NULL.
bool use_move; /// Whether to look at underlying movement or defense terrains.
bool high_is_good; /// Whether we are looking for highest or lowest (unless inverted by the underlying terrain).
parameters(int min, int max, int (*eval_fun)(int)=NULL, bool move=true, bool high=false) :
min_value(min), max_value(max), default_value(high ? min : max),
eval(eval_fun), use_move(move), high_is_good(high)
{}
};
const movetype::terrain_info::parameters
movetype::terrain_costs::params_(1, movetype::UNREACHABLE);
const movetype::terrain_info::parameters
movetype::terrain_defense::params_min_(0, 100, config_to_min, false, true);
const movetype::terrain_info::parameters
movetype::terrain_defense::params_max_(0, 100, config_to_max, false, false);
/* *** data *** */
class movetype::terrain_info::data
{
public:
/// Constructor.
/// @a params must be long-lived (typically a static variable).
explicit data(const parameters & params) :
cfg_(), cache_(), params_(params)
{}
/// Constructor.
/// @a params must be long-lived (typically a static variable).
data(const config & cfg, const parameters & params) :
cfg_(cfg), cache_(), params_(params)
{}
data(const data & that) :
cfg_(that.cfg_), cache_(that.cache_), params_(that.params_)
{}
/// Tests for no data in this object.
bool empty() const { return cfg_.empty(); }
/// Merges the given config over the existing costs.
void merge(const config & new_values, bool overwrite);
/// Returns the value associated with the given terrain.
int value(const t_translation::t_terrain & terrain) const
{ return value(terrain, 0); }
/// If there is data, writes it to the config.
void write(config & out_cfg, const std::string & child_name) const;
private:
/// Calculates the value associated with the given terrain.
int calc_value(const t_translation::t_terrain & terrain,
unsigned recurse_count) const;
/// Returns the value associated with the given terrain (possibly cached).
int value(const t_translation::t_terrain & terrain,
unsigned recurse_count) const;
private:
typedef std::map<t_translation::t_terrain, int> cache_t;
/// Config describing the terrain values.
config cfg_;
/// Cache of values based on the config.
mutable cache_t cache_;
/// Various parameters used when calculating values.
const parameters & params_;
};
/**
* Merges the given config over the existing costs.
* @param[in] new_values The new values.
* @param[in] overwrite If true, the new values overwrite the old.
* If false, the new values are added to the old.
* @param[in] cascade Cache clearing will be cascaded into this terrain_info.
*/
void movetype::terrain_info::data::merge(const config & new_values, bool overwrite)
{
if ( overwrite )
// We do not support child tags here, so do not copy any that might
// be in the input. (If in the future we need to support child tags,
// change "merge_attributes" to "merge_with".)
cfg_.merge_attributes(new_values);
else {
BOOST_FOREACH( const config::attribute & a, new_values.attribute_range() ) {
config::attribute_value & dest = cfg_[a.first];
int old = dest.to_int(params_.max_value);
// The new value is the absolute value of the old plus the
// provided value, capped between minimum and maximum, then
// given the sign of the old value.
// (Think defenses for why we might have negative values.)
int value = abs(old) + a.second.to_int(0);
value = std::max(params_.min_value, std::min(value, params_.max_value));
if ( old < 0 )
value = -value;
dest = value;
}
}
// The new data has invalidated the cache.
cache_.clear();
}
/**
* If there is data, writes it to a config.
* @param[out] out_cfg The config that will receive the data.
* @param[in] child_name If not empty, create and write to a child config with this tag.
* This child will *not* be created if there is no data to write.
*/
void movetype::terrain_info::data::write(
config & out_cfg, const std::string & child_name) const
{
if ( cfg_.empty() )
return;
if ( child_name.empty() )
out_cfg.merge_with(cfg_);
else
out_cfg.add_child(child_name, cfg_);
}
/**
* Calculates the value associated with the given terrain.
* This is separate from value() to separate the calculating of the
* value from the caching of it.
* @param[in] terrain The terrain whose value is requested.
* @param[in] recurse_count Detects (probable) infinite recursion.
*/
int movetype::terrain_info::data::calc_value(
const t_translation::t_terrain & terrain,
unsigned recurse_count) const
{
// Infinite recursion detection:
if ( recurse_count > 100 ) {
ERR_CF << "infinite terrain_info recursion on "
<< (params_.use_move ? "movement" : "defense") << ": "
<< t_translation::write_terrain_code(terrain)
<< " depth " << recurse_count << '\n';
return params_.default_value;
}
assert(resources::game_map);
gamemap & map = *resources::game_map;
// Get a list of underlying terrains.
const t_translation::t_list & underlying = params_.use_move ?
map.underlying_mvt_terrain(terrain) :
map.underlying_def_terrain(terrain);
assert(!underlying.empty());
if ( underlying.size() == 1 && underlying.front() == terrain )
{
// This is not an alias; get the value directly.
int result = params_.default_value;
const std::string & id = map.get_terrain_info(terrain).id();
if (const config::attribute_value *val = cfg_.get(id)) {
// Read the value from our config.
result = val->to_int(params_.default_value);
if ( params_.eval != NULL )
result = params_.eval(result);
}
// Validate the value.
if ( result < params_.min_value ) {
WRN_CF << "Terrain '" << terrain << "' has evaluated to " << result
<< " (" << (params_.use_move ? "cost" : "defense")
<< "), which is less than " << params_.min_value
<< "; resetting to " << params_.min_value << ".\n";
result = params_.min_value;
}
if ( result > params_.max_value ) {
WRN_CF << "Terrain '" << terrain << "' has evaluated to " << result
<< " (" << (params_.use_move ? "cost" : "defense")
<< "), which is more than " << params_.max_value
<< "; resetting to " << params_.max_value << ".\n";
result = params_.max_value;
}
return result;
}
else
{
// This is an alias; select the best of all underlying terrains.
bool prefer_high = params_.high_is_good;
int result = params_.default_value;
if ( underlying.front() == t_translation::MINUS )
// Use the other value as the initial value.
result = result == params_.max_value ? params_.min_value :
params_.max_value;
// Loop through all underlying terrains.
t_translation::t_list::const_iterator i;
for ( i = underlying.begin(); i != underlying.end(); ++i )
{
if ( *i == t_translation::PLUS ) {
// Prefer what is good.
prefer_high = params_.high_is_good;
}
else if ( *i == t_translation::MINUS ) {
// Prefer what is bad.
prefer_high = !params_.high_is_good;
}
else {
// Test the underlying terrain's value against the best so far.
const int num = value(*i, recurse_count + 1);
if ( ( prefer_high && num > result) ||
(!prefer_high && num < result) )
result = num;
}
}
return result;
}
}
/**
* Returns the value associated with the given terrain (possibly cached).
* @param[in] terrain The terrain whose value is requested.
* @param[in] recurse_count Detects (probable) infinite recursion.
*/
int movetype::terrain_info::data::value(
const t_translation::t_terrain & terrain,
unsigned recurse_count) const
{
// Check the cache.
std::pair<cache_t::iterator, bool> cache_it =
cache_.insert(std::make_pair(terrain, -127)); // Bogus value that should never be seen.
if ( cache_it.second )
// The cache did not have an entry for this terrain, so calculate the value.
cache_it.first->second = calc_value(terrain, recurse_count);
return cache_it.first->second;
}
/* *** terrain_info *** */
/**
* Constructor.
* @param[in] params The parameters to use when calculating values.
* This is stored as a reference, so it must be long-lived (typically a static variable).
* @param[in] fallback Used as a backup in case we have no data (think vision costs falling back to movement costs).
* @note The fallback mechanism is a bit fragile and really should only
* be used by movetype.
*/
movetype::terrain_info::terrain_info(const parameters & params,
const terrain_info * fallback) :
data_(new data(params)),
fallback_(fallback)
{
}
/**
* Constructor.
* @param[in] cfg An initial data set.
* @param[in] params The parameters to use when calculating values.
* This is stored as a reference, so it must be long-lived (typically a static variable).
* @param[in] fallback Used as a backup in case we have no data (think vision costs falling back to movement costs).
* @note The fallback mechanism is a bit fragile and really should only
* be used by movetype.
*/
movetype::terrain_info::terrain_info(const config & cfg, const parameters & params,
const terrain_info * fallback) :
data_(new data(cfg, params)),
fallback_(fallback)
{
}
/**
* Copy constructor.
* @param[in] that The terran_info to copy.
* @param[in] fallback Used as a backup in case we have no data (think vision costs falling back to movement costs).
* @note The fallback mechanism is a bit fragile and really should only
* be used by movetype.
*/
movetype::terrain_info::terrain_info(const terrain_info & that,
const terrain_info * fallback) :
// If we do not have a fallback, we need to incorporate that's fallback.
// (See also the assignment operator.)
data_(new data(fallback ? *that.data_ : that.get_merged())),
fallback_(fallback)
{
}
/**
* Destructor
*/
movetype::terrain_info::~terrain_info()
{
delete data_;
}
/**
* Assignment operator.
*/
movetype::terrain_info & movetype::terrain_info::operator=(const terrain_info & that)
{
if ( this != &that ) {
delete data_;
// If we do not have a fallback, we need to incorporate that's fallback.
// (See also the copy constructor.)
data_ = new data(fallback_ ? *that.data_ : that.get_merged());
// We do not change our fallback.
}
return *this;
}
/**
* Merges the given config over the existing values.
* @param[in] new_values The new values.
* @param[in] overwrite If true, the new values overwrite the old.
* If false, the new values are added to the old.
*/
void movetype::terrain_info::merge(const config & new_values, bool overwrite)
{
data_->merge(new_values, overwrite);
}
/**
* Returns the value associated with the given terrain.
*/
int movetype::terrain_info::value(const t_translation::t_terrain & terrain) const
{
if ( fallback_ && data_->empty() )
return fallback_->value(terrain);
return data_->value(terrain);
}
/**
* Writes our data to a config.
* @param[out] cfg The config that will receive the data.
* @param[in] child_name If not empty, create and write to a child config with this tag.
* @param[in] merged If true, our data will be merged with our fallback's, and it is possible an empty child will be created.
* If false, data will not be merged, and an empty child will not be created.
*/
void movetype::terrain_info::write(config & cfg, const std::string & child_name,
bool merged) const
{
if ( !merged )
data_->write(cfg, child_name);
else
{
// Get a place to write to.
config & merged_cfg = child_name.empty() ? cfg : cfg.add_child(child_name);
if ( fallback_ && data_->empty() )
fallback_->write(merged_cfg, "", true);
else
data_->write(merged_cfg, "");
}
}
/**
* Returns data that incorporates our fallback.
*/
const movetype::terrain_info::data & movetype::terrain_info::get_merged() const
{
if ( !fallback_ || !data_->empty() )
return *data_;
else
return fallback_->get_merged();
}
/* *** resistances *** */
/**
* Returns a map from attack types to resistances.
*/
utils::string_map movetype::resistances::damage_table() const
{
utils::string_map result;
BOOST_FOREACH( const config::attribute & attrb, cfg_.attribute_range() )
result[attrb.first] = attrb.second;
return result;
}
/**
* Returns the resistance against the indicated attack.
*/
int movetype::resistances::resistance_against(const attack_type & attack) const
{
return cfg_[attack.type()].to_int(100);
}
/**
* Returns the resistance against the indicated damage type.
*/
int movetype::resistances::resistance_against(const std::string & damage_type) const
{
return cfg_[damage_type].to_int(100);
}
/**
* Merges the given config over the existing costs.
* If @a overwrite is false, the new values will be added to the old.
*/
void movetype::resistances::merge(const config & new_data, bool overwrite)
{
if ( overwrite )
// We do not support child tags here, so do not copy any that might
// be in the input. (If in the future we need to support child tags,
// change "merge_attributes" to "merge_with".)
cfg_.merge_attributes(new_data);
else
BOOST_FOREACH( const config::attribute & a, new_data.attribute_range() ) {
config::attribute_value & dest = cfg_[a.first];
dest = std::max(0, dest.to_int(100) + a.second.to_int(0));
}
}
/**
* Writes our data to a config, as a child if @a child_name is specified.
* (No child is created if there is no data.)
*/
void movetype::resistances::write(config & out_cfg, const std::string & child_name) const
{
if ( cfg_.empty() )
return;
if ( child_name.empty() )
out_cfg.merge_with(cfg_);
else
out_cfg.add_child(child_name, cfg_);
}
/* *** movetype *** */
/**
* Default constructor
*/
movetype::movetype() :
movement_(NULL),
vision_(&movement_),
jamming_(NULL),
defense_(),
resist_(),
flying_(false)
{
}
/**
* Constructor from a config
*/
movetype::movetype(const config & cfg) :
movement_(cfg.child_or_empty("movement_costs"), NULL),
vision_(cfg.child_or_empty("vision_costs"), &movement_),
jamming_(cfg.child_or_empty("jamming_costs"), NULL),
defense_(cfg.child_or_empty("defense")),
resist_(cfg.child_or_empty("resistance")),
flying_(cfg["flies"].to_bool(false))
{
}
/**
* Copy constructor
*/
movetype::movetype(const movetype & that) :
movement_(that.movement_, NULL),
vision_(that.vision_, &movement_),
jamming_(that.jamming_, NULL),
defense_(that.defense_),
resist_(that.resist_),
flying_(that.flying_)
{
}
/**
* Merges the given config over the existing data.
* If @a overwrite is false, the new values will be added to the old.
*/
void movetype::merge(const config & new_cfg, bool overwrite)
{
BOOST_FOREACH( const config & child, new_cfg.child_range("movement_costs") )
movement_.merge(child, overwrite);
BOOST_FOREACH( const config & child, new_cfg.child_range("vision_costs") )
vision_.merge(child, overwrite);
BOOST_FOREACH( const config & child, new_cfg.child_range("jamming_costs") )
jamming_.merge(child, overwrite);
BOOST_FOREACH( const config & child, new_cfg.child_range("defense") )
defense_.merge(child, overwrite);
BOOST_FOREACH( const config & child, new_cfg.child_range("resistance") )
resist_.merge(child, overwrite);
// "flies" is used when WML defines a movetype.
// "flying" is used when WML defines a unit.
// It's easier to support both than to track which case we are in.
flying_ = new_cfg["flies"].to_bool(flying_);
flying_ = new_cfg["flying"].to_bool(flying_);
}
/**
* Writes the movement type data to the provided config.
*/
void movetype::write(config & cfg) const
{
movement_.write(cfg, "movement_costs", false);
vision_.write(cfg, "vision_costs", false);
jamming_.write(cfg, "jamming_costs", false);
defense_.write(cfg, "defense");
resist_.write(cfg, "resistance");
if ( flying_ )
cfg["flying"] = true;
}

218
src/movetype.hpp Normal file
View file

@ -0,0 +1,218 @@
/* $Id$ */
/*
Copyright (C) 2013 - 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.
*/
#ifndef MOVETYPE_H_INCLUDED
#define MOVETYPE_H_INCLUDED
#include "config.hpp"
#include "serialization/string_utils.hpp"
class attack_type;
namespace t_translation { class t_terrain; }
/// The basic "size" of the unit - flying, small land, large land, etc.
/// This encompasses terrain costs, defenses, and resistances.
class movetype
{
/// Stores a set of data based on terrain.
class terrain_info
{
/// The terrain-based data.
class data;
// The data class is not defined here to keep the header file cleaner.
public:
/// The parameters used when calculating a terrain-based value.
struct parameters;
explicit terrain_info(const parameters & params,
const terrain_info * fallback=NULL);
terrain_info(const config & cfg, const parameters & params,
const terrain_info * fallback=NULL);
terrain_info(const terrain_info & that,
const terrain_info * fallback=NULL);
~terrain_info();
terrain_info & operator=(const terrain_info & that);
/// Merges the given config over the existing values.
void merge(const config & new_values, bool overwrite);
/// Returns the value associated with the given terrain.
int value(const t_translation::t_terrain & terrain) const;
/// Writes our data to a config.
void write(config & cfg, const std::string & child_name="", bool merged=true) const;
private:
// Returns data that incorporates our fallback.
const data & get_merged() const;
private:
data * data_; /// Never NULL
const terrain_info * const fallback_;
};
public:
/// Magic value that signifies a hex is unreachable.
/// The UNREACHABLE macro in the data tree should match this value.
static const int UNREACHABLE = 99;
/// Stores a set of terrain costs (for movement, vision, or "jamming").
class terrain_costs : public terrain_info
{
static const parameters params_;
public:
explicit terrain_costs(const terrain_costs * fallback=NULL) :
terrain_info(params_, fallback)
{}
explicit terrain_costs(const config & cfg,
const terrain_costs * fallback=NULL) :
terrain_info(cfg, params_, fallback)
{}
terrain_costs(const terrain_costs & that,
const terrain_costs * fallback=NULL) :
terrain_info(that, fallback)
{}
/// Returns the cost associated with the given terrain.
int cost(const t_translation::t_terrain & terrain) const
{ return value(terrain); }
// Inherited from terrain_info:
//void merge(const config & new_values, bool overwrite);
//void write(config & cfg, const std::string & child_name="", bool merged=true) const;
};
/// Stores a set of defense levels.
class terrain_defense
{
static const terrain_info::parameters params_min_;
static const terrain_info::parameters params_max_;
public:
terrain_defense() : min_(params_min_), max_(params_max_) {}
explicit terrain_defense(const config & cfg) :
min_(cfg, params_min_), max_(cfg, params_max_)
{}
/// Returns the defense associated with the given terrain.
int defense(const t_translation::t_terrain & terrain) const
{ return std::max(min_.value(terrain), max_.value(terrain)); }
/// Merges the given config over the existing costs.
/// (Not overwriting implies adding.)
void merge(const config & new_data, bool overwrite)
{ min_.merge(new_data, overwrite); max_.merge(new_data, overwrite); }
/// Writes our data to a config, as a child if @a child_name is specified.
/// (No child is created if there is no data.)
void write(config & cfg, const std::string & child_name="") const
{ max_.write(cfg, child_name, false); }
private:
// There will be duplication of the config here, but it is a small
// config, and the duplication allows greater code sharing.
terrain_info min_;
terrain_info max_;
};
/// Stores a set of resistances.
class resistances
{
public:
resistances() : cfg_() {}
explicit resistances(const config & cfg) : cfg_(cfg) {}
/// Returns a map from attack types to resistances.
utils::string_map damage_table() const;
/// Returns the resistance against the indicated attack.
int resistance_against(const attack_type & attack) const;
/// Returns the resistance against the indicated damage type.
int resistance_against(const std::string & damage_type) const;
/// Merges the given config over the existing costs.
void merge(const config & new_data, bool overwrite);
/// Writes our data to a config, as a child if @a child_name is specified.
void write(config & out_cfg, const std::string & child_name="") const;
private:
config cfg_;
};
public:
movetype();
explicit movetype(const config & cfg);
movetype(const movetype & that);
// This class is basically just a holder for its various pieces, so
// provide access to those pieces on demenad.
terrain_costs & get_movement() { return movement_; }
terrain_costs & get_vision() { return vision_; }
terrain_costs & get_jamming() { return jamming_; }
terrain_defense & get_defense() { return defense_; }
resistances & get_resistances() { return resist_; }
// And const access:
const terrain_costs & get_movement() const { return movement_; }
const terrain_costs & get_vision() const { return vision_; }
const terrain_costs & get_jamming() const { return jamming_; }
const terrain_defense & get_defense() const { return defense_; }
const resistances & get_resistances() const { return resist_; }
/// Returns whether or not *this is flagged as a flying movement type.
bool is_flying() const { return flying_; }
/// Sets whether or not *this is flagged as a flying movement type.
void set_flying(bool flies=true) { flying_ = flies; }
/// Returns the cost to move through the indicated terrain.
int movement_cost(const t_translation::t_terrain & terrain) const
{ return movement_.cost(terrain); }
/// Returns the cost to see through the indicated terrain.
int vision_cost(const t_translation::t_terrain & terrain) const
{ return vision_.cost(terrain); }
/// Returns the cost to "jam" through the indicated terrain.
int jamming_cost(const t_translation::t_terrain & terrain) const
{ return jamming_.cost(terrain); }
/// Returns the defensive value of the indicated terrain.
int defense_modifier(const t_translation::t_terrain & terrain) const
{ return defense_.defense(terrain); }
/// Returns the resistance against the indicated attack.
int resistance_against(const attack_type & attack) const
{ return resist_.resistance_against(attack); }
/// Returns the resistance against the indicated damage type.
int resistance_against(const std::string & damage_type) const
{ return resist_.resistance_against(damage_type); }
/// Returns a map from attack types to resistances.
utils::string_map damage_table() const
{ return resist_.damage_table(); }
/// Merges the given config over the existing data.
void merge(const config & new_cfg, bool overwrite=true);
/// Writes the movement type data to the provided config.
void write(config & cfg) const;
private:
terrain_costs movement_;
terrain_costs vision_;
terrain_costs jamming_;
terrain_defense defense_;
resistances resist_;
bool flying_;
};
#endif // MOVETYPE_H_INCLUDED

View file

@ -81,7 +81,7 @@ map_location pathfind::find_vacant_tile(const map_location& loc,
//If this area is not a castle but should, skip it.
if (vacancy == pathfind::VACANT_CASTLE && !map.is_castle(loc)) continue;
const bool pass_check_and_unreachable = pass_check
&& pass_check->movement_cost(map[loc]) == unit_movement_type::UNREACHABLE;
&& pass_check->movement_cost(map[loc]) == movetype::UNREACHABLE;
//If the unit can't reach the tile and we have searched
//an area of at least radius 10 (arbitrary), skip the tile.
//Neccessary for cases such as an unreachable
@ -693,7 +693,7 @@ double pathfind::shortest_path_calculator::cost(const map_location& loc, const d
return move_cost + (defense_subcost + other_unit_subcost) / 10000.0;
}
pathfind::move_type_path_calculator::move_type_path_calculator(const unit_movement_type& mt, int movement_left, int total_movement, team const &t, gamemap const &map)
pathfind::move_type_path_calculator::move_type_path_calculator(const movetype& mt, int movement_left, int total_movement, team const &t, gamemap const &map)
: movement_type_(mt), movement_left_(movement_left),
total_movement_(total_movement), viewing_team_(t), map_(map)
{}

View file

@ -22,10 +22,10 @@
#define PATHFIND_H_INCLUDED
class gamemap;
class movetype;
class team;
class unit;
class unit_map;
class unit_movement_type;
#include "map_location.hpp"
@ -227,11 +227,11 @@ private:
struct move_type_path_calculator : cost_calculator
{
move_type_path_calculator(const unit_movement_type& mt, int movement_left, int total_movement, const team& t, const gamemap& map);
move_type_path_calculator(const movetype& mt, int movement_left, int total_movement, const team& t, const gamemap& map);
virtual double cost(const map_location& loc, const double so_far) const;
private:
const unit_movement_type &movement_type_;
const movetype &movement_type_;
const int movement_left_;
const int total_movement_;
team const &viewing_team_;

View file

@ -601,7 +601,7 @@ static config unit_moves(const unit* u)
tooltip << name << ": ";
std::string color;
//movement - range: 1 .. 5, unit_movement_type::UNREACHABLE=impassable
//movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
const bool cannot_move = moves > u->total_movement();
if (cannot_move) // cannot move in this terrain
color = "red";
@ -1263,7 +1263,7 @@ REPORT_GENERATOR(position)
int move_cost = u->movement_cost(terrain);
int defense = 100 - u->defense_modifier(terrain);
if (move_cost < unit_movement_type::UNREACHABLE) {
if (move_cost < movetype::UNREACHABLE) {
str << " (" << defense << "%," << move_cost << ')';
} else if (mouseover_hex == displayed_unit_hex) {
str << " (" << defense << "%,)";

View file

@ -145,12 +145,9 @@ unit::unit(const unit& o):
movement_(o.movement_),
max_movement_(o.max_movement_),
movement_costs_(o.movement_costs_),
vision_(o.vision_),
vision_costs_(o.vision_costs_),
jamming_(o.jamming_),
jamming_costs_(o.jamming_costs_),
defense_mods_(o.defense_mods_),
movement_type_(o.movement_type_),
hold_position_(o.hold_position_),
end_turn_(o.end_turn_),
resting_(o.resting_),
@ -176,7 +173,6 @@ unit::unit(const unit& o):
unit_value_(o.unit_value_),
goto_(o.goto_),
interrupted_move_(o.interrupted_move_),
flying_(o.flying_),
is_fearless_(o.is_fearless_),
is_healthy_(o.is_healthy_),
@ -231,12 +227,9 @@ unit::unit(const config &cfg, bool use_traits, game_state* state, const vconfig*
formula_vars_(),
movement_(0),
max_movement_(0),
movement_costs_(),
vision_(-1),
vision_costs_(),
jamming_(0),
jamming_costs_(),
defense_mods_(),
movement_type_(),
hold_position_(false),
end_turn_(false),
resting_(false),
@ -258,7 +251,6 @@ unit::unit(const config &cfg, bool use_traits, game_state* state, const vconfig*
unit_value_(),
goto_(),
interrupted_move_(),
flying_(false),
is_fearless_(false),
is_healthy_(false),
modification_descriptions_(),
@ -345,9 +337,6 @@ unit::unit(const config &cfg, bool use_traits, game_state* state, const vconfig*
if (const config::attribute_value *v = cfg.get("zoc")) {
emit_zoc_ = v->to_bool(level_ > 0);
}
if (const config::attribute_value *v = cfg.get("flying")) {
flying_ = v->to_bool();
}
if (const config::attribute_value *v = cfg.get("description")) {
cfg_["description"] = *v;
}
@ -416,50 +405,9 @@ unit::unit(const config &cfg, bool use_traits, game_state* state, const vconfig*
} while(++cfg_range.first != cfg_range.second);
}
//adjust the unit_type's defense if this config has its own defined
cfg_range = cfg.child_range("defense");
if(cfg_range.first != cfg_range.second) {
config &target = cfg_.child_or_add("defense");
do {
target.append(*cfg_range.first);
} while(++cfg_range.first != cfg_range.second);
}
//adjust the unit_type's movement costs if this config has its own defined
cfg_range = cfg.child_range("movement_costs");
if(cfg_range.first != cfg_range.second) {
config &target = cfg_.child_or_add("movement_costs");
do {
target.append(*cfg_range.first);
} while(++cfg_range.first != cfg_range.second);
}
//adjust the unit_type's vision costs if this config has its own defined
cfg_range = cfg.child_range("vision_costs");
if(cfg_range.first != cfg_range.second) {
config &target = cfg_.child_or_add("vision_costs");
do {
target.append(*cfg_range.first);
} while(++cfg_range.first != cfg_range.second);
}
//adjust the unit_type's jamming costs if this config has its own defined
cfg_range = cfg.child_range("jamming_costs");
if(cfg_range.first != cfg_range.second) {
config &target = cfg_.child_or_add("jamming_costs");
do {
target.append(*cfg_range.first);
} while(++cfg_range.first != cfg_range.second);
}
//adjust the unit_type's resistance if this config has its own defined
cfg_range = cfg.child_range("resistance");
if(cfg_range.first != cfg_range.second) {
config &target = cfg_.child_or_add("resistance");
do {
target.append(*cfg_range.first);
} while(++cfg_range.first != cfg_range.second);
}
// Adjust the unit's defense, movement, vision, jamming, resistances, and
// flying status if this config has its own defined.
movement_type_.merge(cfg);
if (const config &status_flags = cfg.child("status"))
{
@ -607,12 +555,9 @@ unit::unit(const unit_type &u_type, int side, bool real_unit,
formula_vars_(),
movement_(0),
max_movement_(0),
movement_costs_(),
vision_(-1),
vision_costs_(),
jamming_(0),
jamming_costs_(),
defense_mods_(),
movement_type_(),
hold_position_(false),
end_turn_(false),
resting_(false),
@ -634,7 +579,6 @@ unit::unit(const unit_type &u_type, int side, bool real_unit,
unit_value_(),
goto_(),
interrupted_move_(),
flying_(false),
is_fearless_(false),
is_healthy_(false),
modification_descriptions_(),
@ -820,10 +764,6 @@ void unit::advance_to(const config &old_cfg, const unit_type &u_type,
// Clear modification-related caches
modification_descriptions_.clear();
movement_costs_.clear();
vision_costs_.clear();
jamming_costs_.clear();
defense_mods_.clear();
// Clear the stored config and replace it with the one from the unit type,
// except for a few attributes.
@ -836,15 +776,6 @@ void unit::advance_to(const config &old_cfg, const unit_type &u_type,
}
}
if ( new_type.movement_type().get_parent() ) {
new_cfg.merge_with(new_type.movement_type().get_parent()->get_cfg());
// Convert movement type's "flies" to unit's "flying".
if ( const config::attribute_value * flies = new_cfg.get("flies") ) {
new_cfg["flying"] = flies->to_bool();
new_cfg.remove_attribute("flies");
}
}
// Inherit from the new unit type.
new_cfg.merge_with(new_type.get_cfg_for_units());
@ -881,10 +812,10 @@ void unit::advance_to(const config &old_cfg, const unit_type &u_type,
max_movement_ = new_type.movement();
vision_ = new_type.vision();
jamming_ = new_type.jamming();
movement_type_ = new_type.movement_type();
emit_zoc_ = new_type.has_zoc();
attacks_ = new_type.attacks();
unit_value_ = new_type.cost();
flying_ = new_type.movement_type().is_flying();
max_attacks_ = new_type.max_attacks();
@ -1673,6 +1604,7 @@ bool unit::internal_matches_filter(const vconfig& cfg, const map_location& loc,
void unit::write(config& cfg) const
{
cfg.append(cfg_);
movement_type_.write(cfg);
if ( cfg["description"] == type().unit_description() ) {
cfg.remove_attribute("description");
@ -1724,11 +1656,8 @@ void unit::write(config& cfg) const
}
cfg["gender"] = gender_string(gender_);
cfg["variation"] = variation_;
cfg["role"] = role_;
cfg["flying"] = flying_;
config status_flags;
std::map<std::string,std::string> all_states = get_states();
@ -2207,10 +2136,9 @@ bool unit::loyal() const
int unit::movement_cost(const t_translation::t_terrain & terrain) const
{
const int res = movement_cost_internal(movement_costs_,
cfg_.child("movement_costs"), NULL, terrain);
const int res = movement_type_.movement_cost(terrain);
if (res == unit_movement_type::UNREACHABLE) {
if ( res == movetype::UNREACHABLE ) {
return res;
} else if(get_state(STATE_SLOWED)) {
return res*2;
@ -2220,12 +2148,9 @@ int unit::movement_cost(const t_translation::t_terrain & terrain) const
int unit::vision_cost(const t_translation::t_terrain & terrain) const
{
if (cfg_.child_count("vision_costs") == 0) return movement_cost(terrain);
const int res = movement_type_.vision_cost(terrain);
const int res = movement_cost_internal(vision_costs_,
cfg_.child("vision_costs"), NULL, terrain);
if (res == unit_movement_type::UNREACHABLE) {
if ( res == movetype::UNREACHABLE ) {
return res;
} else if(get_state(STATE_SLOWED)) {
return res*2;
@ -2235,12 +2160,9 @@ int unit::vision_cost(const t_translation::t_terrain & terrain) const
int unit::jamming_cost(const t_translation::t_terrain & terrain) const
{
// if (cfg_.child_count("jamming_costs") == 0) return movement_cost(terrain);
const int res = movement_type_.jamming_cost(terrain);
const int res = movement_cost_internal(jamming_costs_,
cfg_.child("jamming_costs"), NULL, terrain);
if (res == unit_movement_type::UNREACHABLE) {
if ( res == movetype::UNREACHABLE ) {
return res;
} else if(get_state(STATE_SLOWED)) {
return res*2;
@ -2250,7 +2172,7 @@ int unit::jamming_cost(const t_translation::t_terrain & terrain) const
int unit::defense_modifier(const t_translation::t_terrain & terrain) const
{
int def = defense_modifier_internal(defense_mods_, cfg_, NULL, terrain);
int def = movement_type_.defense_modifier(terrain);
#if 0
// A [defense] ability is too costly and doesn't take into account target locations.
// Left as a comment in case someone ever wonders why it isn't a good idea.
@ -2289,11 +2211,7 @@ bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std
int unit::resistance_against(const std::string& damage_name,bool attacker,const map_location& loc) const
{
int res = 100;
if (const config &resistance = cfg_.child("resistance")) {
res = resistance[damage_name].to_int(100);
}
int res = movement_type_.resistance_against(damage_name);
unit_ability_list resistance_abilities = get_abilities("resistance",loc);
for (unit_ability_list::iterator i = resistance_abilities.begin(); i != resistance_abilities.end();) {
@ -2312,26 +2230,6 @@ int unit::resistance_against(const std::string& damage_name,bool attacker,const
return res;
}
utils::string_map unit::get_base_resistances() const
{
if (const config &resistance = cfg_.child("resistance"))
{
utils::string_map res;
BOOST_FOREACH(const config::attribute &i, resistance.attribute_range()) {
res[i.first] = i.second;
}
return res;
}
return utils::string_map();
}
#if 0
std::map<terrain_type::TERRAIN,int> unit::movement_type() const
{
return movement_costs_;
}
#endif
std::map<std::string,std::string> unit::advancement_icons() const
{
std::map<std::string,std::string> temp;
@ -2431,16 +2329,6 @@ size_t unit::modification_count(const std::string& mod_type, const std::string&
return res;
}
/** Helper function for add_modifications */
static void mod_mdr_merge(config& dst, const config& mod, bool delta, int minimum)
{
BOOST_FOREACH(const config::attribute &i, mod.attribute_range()) {
int base = delta ? dst[i.first].to_int() : 0;
dst[i.first] = std::max(minimum, base + i.second.to_int());
}
}
void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
{
//some trait activate specific flags
@ -2635,59 +2523,27 @@ void unit::add_modification(const std::string& mod_type, const config& mod, bool
set_state(remove, false);
set_poisoned = set_poisoned && remove != "poisoned";
}
// Note: It would not be hard to define a new "applies_to=" that
// combines the next five options (the movetype effects).
} else if (apply_to == "movement_costs") {
config &mv = cfg_.child_or_add("movement_costs");
if (const config &ap = effect.child("movement_costs")) {
mod_mdr_merge(mv, ap, !effect["replace"].to_bool(), 1);
movement_type_.get_movement().merge(ap, effect["replace"].to_bool());
}
movement_costs_.clear();
} else if (apply_to == "vision_costs") {
config &vi = cfg_.child_or_add("vision_costs");
if (const config &ap = effect.child("vision_costs")) {
mod_mdr_merge(vi, ap, !effect["replace"].to_bool(), 1);
movement_type_.get_vision().merge(ap, effect["replace"].to_bool());
}
vision_costs_.clear();
} else if (apply_to == "jamming_costs") {
config &jm = cfg_.child_or_add("jamming_costs");
if (const config &ap = effect.child("jamming_costs")) {
mod_mdr_merge(jm, ap, !effect["replace"].to_bool(), 1);
movement_type_.get_jamming().merge(ap, effect["replace"].to_bool());
}
jamming_costs_.clear();
} else if (apply_to == "defense") {
config &def = cfg_.child_or_add("defense");
if (const config &ap = effect.child("defense")) {
bool replace = effect["replace"].to_bool();
BOOST_FOREACH(const config::attribute &i, ap.attribute_range()) {
// The new value:
int value = i.second.to_int(replace ? 100 : 0);
// Where the value gets stored:
config::attribute_value &dst = def[i.first];
if ( replace ) {
// The new value gets capped between -100 and +100.
value = std::max(-100, std::min(value, 100));
}
else {
int old = dst.to_int(100);
// Add the absolute value of the old value.
value += abs(old);
// This gets capped between 0 and 100.
value = std::max(0, std::min(value, 100));
// Restore the sign of the old value.
if ( old < 0 )
value = -value;
}
// Save this value.
dst = value;
}
movement_type_.get_defense().merge(ap, effect["replace"].to_bool());
}
defense_mods_.clear();
} else if (apply_to == "resistance") {
config &mv = cfg_.child_or_add("resistance");
if (const config &ap = effect.child("resistance")) {
mod_mdr_merge(mv, ap, !effect["replace"].to_bool(), 0);
movement_type_.get_resistances().merge(ap, effect["replace"].to_bool());
}
} else if (apply_to == "zoc") {
if (const config::attribute_value *v = effect.get("value")) {

View file

@ -21,7 +21,9 @@
#include <boost/tuple/tuple.hpp>
#include "formula_callable.hpp"
#include "portrait.hpp"
#include "resources.hpp"
#include "unit_animation.hpp"
#include "unit_types.hpp"
#include "unit_map.hpp"
@ -265,7 +267,7 @@ public:
void set_hidden(bool state);
bool get_hidden() const { return hidden_; }
bool is_flying() const { return flying_; }
bool is_flying() const { return movement_type_.is_flying(); }
bool is_fearless() const { return is_fearless_; }
bool is_healthy() const { return is_healthy_; }
int movement_cost(const t_translation::t_terrain & terrain) const;
@ -277,8 +279,8 @@ public:
{return resistance_against(damage_type.type(), attacker, loc);};
//return resistances without any abilities applied
utils::string_map get_base_resistances() const;
// std::map<terrain_type::TERRAIN,int> movement_type() const;
utils::string_map get_base_resistances() const { return movement_type_.damage_table(); }
const movetype & movement_type() const { return movement_type_; }
bool can_advance() const { return advances_to_.empty()==false || get_modification_advances().empty() == false; }
bool advances() const { return experience_ >= max_experience() && can_advance(); }
@ -449,12 +451,9 @@ private:
int movement_;
int max_movement_;
mutable std::map<t_translation::t_terrain, int> movement_costs_; // movement cost cache
int vision_;
mutable std::map<t_translation::t_terrain, int> vision_costs_; // view cost cache
int jamming_;
mutable std::map<t_translation::t_terrain, int> jamming_costs_; // jamming cost cache
mutable defense_cache defense_mods_; // defense modifiers cache
movetype movement_type_;
bool hold_position_;
bool end_turn_;
bool resting_;
@ -482,7 +481,7 @@ private:
int unit_value_;
map_location goto_, interrupted_move_;
bool flying_, is_fearless_, is_healthy_;
bool is_fearless_, is_healthy_;
utils::string_map modification_descriptions_;
// Animations:

View file

@ -26,8 +26,8 @@
#include "gettext.hpp"
#include "loadscreen.hpp"
#include "log.hpp"
#include "map.hpp"
#include "resources.hpp"
#include "portrait.hpp"
#include "unit_animation.hpp"
#include <boost/foreach.hpp>
@ -337,284 +337,6 @@ bool attack_type::describe_modification(const config& cfg,std::string* descripti
}
/* ** unit_movement_type ** */
unit_movement_type::unit_movement_type(const config& cfg, const unit_movement_type* parent) :
moveCosts_(),
visionCosts_(),
jammingCosts_(),
defenseMods_(),
parent_(parent),
cfg_()
{
//the unit_type give its whole cfg, we don't need all that.
//so we filter to keep only keys related to movement_type
//FIXME: This helps but it's still not clean, both cfg use a "name" key
const t_string& name = cfg["name"];
if (!name.empty())
cfg_["name"]= cfg["name"];
const t_string& flies = cfg["flies"];
if (!flies.empty())
cfg_["flies"]= cfg["flies"];
if (const config &movement_costs = cfg.child("movement_costs"))
cfg_.add_child("movement_costs", movement_costs);
if (const config &vision_costs = cfg.child("vision_costs"))
cfg_.add_child("vision_costs", vision_costs);
if (const config &jamming_costs = cfg.child("jamming_costs"))
cfg_.add_child("jamming_costs", jamming_costs);
if (const config &defense = cfg.child("defense"))
cfg_.add_child("defense", defense);
if (const config &resistance = cfg.child("resistance"))
cfg_.add_child("resistance", resistance);
}
unit_movement_type::unit_movement_type(): moveCosts_(), visionCosts_(), jammingCosts_(), defenseMods_(), parent_(NULL), cfg_()
{}
std::string unit_movement_type::name() const
{
if (!cfg_.has_attribute("name") && parent_)
return parent_->name();
else
return cfg_["name"];
}
int unit_movement_type::resistance_against(const attack_type& attack) const
{
bool result_found = false;
int res = 100;
if (const config &resistance = cfg_.child("resistance"))
{
if (const::config::attribute_value *val = resistance.get(attack.type())) {
res = *val;
result_found = true;
}
}
if(!result_found && parent_ != NULL) {
res = parent_->resistance_against(attack);
}
return res;
}
utils::string_map unit_movement_type::damage_table() const
{
utils::string_map res;
if(parent_ != NULL)
res = parent_->damage_table();
if (const config &resistance = cfg_.child("resistance"))
{
BOOST_FOREACH(const config::attribute &i, resistance.attribute_range()) {
res[i.first] = i.second;
}
}
return res;
}
bool unit_movement_type::is_flying() const
{
if (!cfg_.has_attribute("flies") && parent_)
return parent_->is_flying();
return cfg_["flies"].to_bool();
}
int movement_cost_internal(std::map<t_translation::t_terrain, int>& move_costs,
const config& cfg, const unit_movement_type* parent,
const t_translation::t_terrain & terrain, int recurse_count)
{
assert(resources::game_map != NULL);
const gamemap & map = *resources::game_map;
const int impassable = unit_movement_type::UNREACHABLE;
const std::map<t_translation::t_terrain, int>::const_iterator i = move_costs.find(terrain);
if (i != move_costs.end()) return i->second;
// If this is an alias, then select the best of all underlying terrains.
const t_translation::t_list& underlying = map.underlying_mvt_terrain(terrain);
assert(!underlying.empty());
if (underlying.size() != 1 || underlying.front() != terrain) {
bool revert = (underlying.front() == t_translation::MINUS ? true : false);
if (recurse_count >= 100) {
ERR_CF << "infinite movement_cost recursion: "
<< t_translation::write_terrain_code(terrain)
<< " depth " << recurse_count << "\n";
move_costs.insert(std::pair<t_translation::t_terrain, int>(terrain, impassable));
return impassable;
}
int ret_value = revert ? 0 : impassable;
for (t_translation::t_list::const_iterator i = underlying.begin();
i != underlying.end(); ++i)
{
if (*i == t_translation::PLUS) {
revert = false;
continue;
} else if (*i == t_translation::MINUS) {
revert = true;
continue;
}
const int value = movement_cost_internal(move_costs, cfg,
parent, *i, recurse_count + 1);
if (value < ret_value && !revert) {
ret_value = value;
} else if (value > ret_value && revert) {
ret_value = value;
}
}
move_costs.insert(std::pair<t_translation::t_terrain, int>(terrain, ret_value));
return ret_value;
}
bool result_found = false;
int res = impassable;
if (cfg) {
if (underlying.size() != 1) {
ERR_CF << "Terrain '" << terrain << "' has "
<< underlying.size() << " underlying names - 0 expected.\n";
move_costs.insert(std::pair<t_translation::t_terrain, int>(terrain, impassable));
return impassable;
}
const std::string& id = map.get_terrain_info(underlying.front()).id();
if (const config::attribute_value *val = cfg.get(id)) {
res = *val;
result_found = true;
}
}
if (!result_found && parent != NULL) {
res = parent->movement_cost(terrain);
}
if (res <= 0) {
WRN_CF << "Terrain '" << terrain << "' has a movement cost of '"
<< res << "' which is '<= 0'; resetting to 1.\n";
res = 1;
}
move_costs.insert(std::pair<t_translation::t_terrain, int>(terrain, res));
return res;
}
const defense_range &defense_range_modifier_internal(defense_cache &defense_mods,
const config& cfg, const unit_movement_type* parent,
const t_translation::t_terrain & terrain, int recurse_count)
{
assert(resources::game_map != NULL);
const gamemap & map = *resources::game_map;
defense_range dummy = { 0, 100 };
std::pair<defense_cache::iterator, bool> ib =
defense_mods.insert(defense_cache::value_type(terrain, dummy));
if (!ib.second) return ib.first->second;
defense_range &res = ib.first->second;
// If this is an alias, then select the best of all underlying terrains.
const t_translation::t_list& underlying = map.underlying_def_terrain(terrain);
assert(!underlying.empty());
if (underlying.size() != 1 || underlying.front() != terrain) {
bool revert = underlying.front() == t_translation::MINUS;
if(recurse_count >= 90) {
ERR_CF << "infinite defense_modifier recursion: "
<< t_translation::write_terrain_code(terrain)
<< " depth " << recurse_count << "\n";
}
if (recurse_count >= 100) {
return res;
}
if (revert) {
res.max_ = 0;
res.min_ = 100;
}
for (t_translation::t_list::const_iterator i = underlying.begin();
i != underlying.end(); ++i) {
if (*i == t_translation::PLUS) {
revert = false;
continue;
} else if (*i == t_translation::MINUS) {
revert = true;
continue;
}
const defense_range &inh = defense_range_modifier_internal
(defense_mods, cfg, parent, *i, recurse_count + 1);
if (!revert) {
if (inh.max_ < res.max_) res.max_ = inh.max_;
if (inh.min_ > res.min_) res.min_ = inh.min_;
} else {
if (inh.max_ > res.max_) res.max_ = inh.max_;
if (inh.min_ < res.min_) res.min_ = inh.min_;
}
}
goto check;
}
if (const config& defense = cfg.child("defense"))
{
const std::string& id = map.get_terrain_info(underlying.front()).id();
if (const config::attribute_value *val = defense.get(id)) {
int def = *val;
if (def >= 0) res.max_ = def;
else res.max_ = res.min_ = -def;
goto check;
}
}
if (parent) {
/* Assign to the reference res to put the value in the defense_cache. */
res = parent->defense_range_modifier(terrain);
return res;
}
check:
if (res.min_ < 0) {
WRN_CF << "Defense '" << res.min_ << "' is '< 0' reset to 0 (100% defense).\n";
res.min_ = 0;
}
if (res.max_ > 100) {
WRN_CF << "Defense '" << res.max_ << "' is '> 100' reset to 100 (0% defense).\n";
res.max_ = 100;
}
return res;
}
int defense_modifier_internal(defense_cache &defense_mods,
const config &cfg, const unit_movement_type *parent,
const t_translation::t_terrain & terrain, int recurse_count)
{
const defense_range &def = defense_range_modifier_internal(defense_mods,
cfg, parent, terrain, recurse_count);
return (std::max)(def.max_, def.min_);
}
/* ** unit_type ** */
@ -655,7 +377,7 @@ unit_type::unit_type(const unit_type& o) :
experience_needed_(o.experience_needed_),
in_advancefrom_(o.in_advancefrom_),
alignment_(o.alignment_),
movementType_(o.movementType_),
movement_type_(o.movement_type_),
possibleTraits_(o.possibleTraits_),
genders_(o.genders_),
animations_(o.animations_),
@ -709,7 +431,7 @@ unit_type::unit_type(const config &cfg, const std::string & parent_id) :
experience_needed_(0),
in_advancefrom_(false),
alignment_(),
movementType_(),
movement_type_(),
possibleTraits_(),
genders_(),
animations_(),
@ -795,21 +517,8 @@ void unit_type::build_full(const movement_type_map &mv_types,
alpha_ = ftofxp(alpha_blend.to_double());
}
const std::string& move_type = cfg_["movement_type"];
const movement_type_map::const_iterator it = mv_types.find(move_type);
if(it != mv_types.end()) {
DBG_UT << "setting parent for movement_type " << move_type << "\n";
movementType_.set_parent(&(it->second));
}
else{
DBG_UT << "no parent found for movement_type " << move_type << "\n";
}
game_config::add_color_info(cfg_);
BOOST_FOREACH(const config &portrait, cfg_.child_range("portrait")) {
portraits_.push_back(tportrait(portrait));
}
@ -906,7 +615,18 @@ void unit_type::build_help_index(const movement_type_map &mv_types,
}
}
movementType_ = unit_movement_type(cfg_);
// Set the movement type.
const std::string move_type = cfg_["movement_type"];
const movement_type_map::const_iterator find_it = mv_types.find(move_type);
if ( find_it != mv_types.end() ) {
DBG_UT << "inheriting from movement_type '" << move_type << "'\n";
movement_type_ = find_it->second;
}
else if ( !move_type.empty() ) {
DBG_UT << "movement_type '" << move_type << "' not found\n";
}
// Override parts of the movement type with what is in our config.
movement_type_.merge(cfg_);
BOOST_FOREACH(const config &t, traits)
{
@ -1335,6 +1055,13 @@ const config & unit_type::build_unit_cfg() const
unit_cfg_.clear_children("male");
unit_cfg_.clear_children("female");
// Remove movement type data (it will be received via a movetype object).
unit_cfg_.clear_children("movement_costs");
unit_cfg_.clear_children("vision_costs");
unit_cfg_.clear_children("jamming_costs");
unit_cfg_.clear_children("defense");
unit_cfg_.clear_children("resistance");
built_unit_cfg_ = true;
return unit_cfg_;
}
@ -1478,9 +1205,7 @@ void unit_type_data::set_config(config &cfg)
BOOST_FOREACH(const config &mt, cfg.child_range("movetype"))
{
const unit_movement_type move_type(mt);
movement_types_.insert(
std::pair<std::string,unit_movement_type>(move_type.name(), move_type));
movement_types_.insert(std::make_pair(mt["name"].str(), movetype(mt)));
loadscreen::increment_progress();
}

View file

@ -15,17 +15,19 @@
#ifndef UNIT_TYPES_H_INCLUDED
#define UNIT_TYPES_H_INCLUDED
#include "unit_animation.hpp"
#include "portrait.hpp"
#include "map_location.hpp"
#include "movetype.hpp"
#include "race.hpp"
#include "util.hpp"
#include <boost/noncopyable.hpp>
class gamemap;
class unit;
class tportrait;
class unit_ability_list;
class unit_map;
class unit_type_data;
class unit_animation;
typedef std::map<std::string, movetype> movement_type_map;
//the 'attack type' is the type of attack, how many times it strikes,
@ -106,82 +108,6 @@ private:
int parry_;
};
class unit_movement_type;
/**
* Possible range of the defense. When a single value is needed, #max_
* (maximum defense) is selected, unless #min_ is bigger.
*/
struct defense_range
{
int min_, max_;
};
typedef std::map<t_translation::t_terrain, defense_range> defense_cache;
const defense_range &defense_range_modifier_internal(defense_cache &defense_mods,
const config &cfg, const unit_movement_type *parent,
const t_translation::t_terrain & terrain, int recurse_count = 0);
int defense_modifier_internal(defense_cache &defense_mods,
const config &cfg, const unit_movement_type *parent,
const t_translation::t_terrain & terrain, int recurse_count = 0);
int movement_cost_internal(std::map<t_translation::t_terrain, int> &move_costs,
const config &cfg, const unit_movement_type *parent,
const t_translation::t_terrain & terrain, int recurse_count = 0);
//the 'unit movement type' is the basic size of the unit - flying, small land,
//large land, etc etc.
class unit_movement_type
{
public:
//this move distance means a hex is unreachable
//if there is an UNREACHABLE macro declared in the data tree
//it should match this value.
static const int UNREACHABLE = 99;
//this class assumes that the passed in reference will remain valid
//for at least as long as the class instance
explicit unit_movement_type(const config& cfg, const unit_movement_type* parent=NULL);
unit_movement_type();
std::string name() const;
int movement_cost(const t_translation::t_terrain & terrain) const
{ return movement_cost_internal(moveCosts_, cfg_.child("movement_costs"), parent_, terrain); }
int vision_cost(const t_translation::t_terrain & terrain) const
{ return movement_cost_internal(visionCosts_, cfg_.child("vision_costs"), parent_, terrain); }
int jamming_cost(const t_translation::t_terrain & terrain) const
{ return movement_cost_internal(jammingCosts_, cfg_.child("jamming_costs"), parent_, terrain); }
int defense_modifier(const t_translation::t_terrain & terrain) const
{ return defense_modifier_internal(defenseMods_, cfg_, parent_, terrain); }
const defense_range &defense_range_modifier(const t_translation::t_terrain & terrain) const
{ return defense_range_modifier_internal(defenseMods_, cfg_, parent_, terrain); }
int damage_against(const attack_type& attack) const { return resistance_against(attack); }
int resistance_against(const attack_type& attack) const;
utils::string_map damage_table() const;
void set_parent(const unit_movement_type* parent) { parent_ = parent; }
bool is_flying() const;
const config& get_cfg() const { return cfg_; }
const unit_movement_type* get_parent() const { return parent_; }
private:
mutable std::map<t_translation::t_terrain, int> moveCosts_;
mutable std::map<t_translation::t_terrain, int> visionCosts_;
mutable std::map<t_translation::t_terrain, int> jammingCosts_;
mutable defense_cache defenseMods_;
const unit_movement_type* parent_;
config cfg_;
};
typedef std::map<std::string,unit_movement_type> movement_type_map;
class unit_type
{
public:
@ -274,7 +200,7 @@ public:
const std::string& flag_rgb() const { return flag_rgb_; }
std::vector<attack_type> attacks() const;
const unit_movement_type& movement_type() const { return movementType_; }
const movetype & movement_type() const { return movement_type_; }
int experience_needed(bool with_acceleration=true) const;
@ -387,7 +313,7 @@ private:
ALIGNMENT alignment_;
unit_movement_type movementType_;
movetype movement_type_;
config possibleTraits_;