mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-11 16:07:48 +00:00
2702 lines
78 KiB
C++
2702 lines
78 KiB
C++
/*
|
|
Copyright (C) 2003 - 2014 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
|
|
* Routines to manage units.
|
|
*/
|
|
|
|
#include "unit.hpp"
|
|
|
|
#include "actions/move.hpp"
|
|
#include "formula_string_utils.hpp"
|
|
#include "game_display.hpp"
|
|
#include "game_events/handlers.hpp"
|
|
#include "game_preferences.hpp"
|
|
#include "gettext.hpp"
|
|
#include "halo.hpp"
|
|
#include "log.hpp"
|
|
#include "play_controller.hpp"
|
|
#include "random_new.hpp"
|
|
#include "resources.hpp"
|
|
#include "unit_id.hpp"
|
|
#include "unit_abilities.hpp"
|
|
#include "unit_animation_component.hpp"
|
|
#include "unit_formula_manager.hpp"
|
|
#include "scripting/lua.hpp"
|
|
#include "side_filter.hpp"
|
|
#include "terrain_filter.hpp"
|
|
|
|
#include <boost/bind.hpp>
|
|
#include <boost/foreach.hpp>
|
|
|
|
static lg::log_domain log_unit("unit");
|
|
#define DBG_UT LOG_STREAM(debug, log_unit)
|
|
#define LOG_UT LOG_STREAM(info, log_unit)
|
|
#define WRN_UT LOG_STREAM(warn, log_unit)
|
|
#define ERR_UT LOG_STREAM(err, log_unit)
|
|
|
|
static lg::log_domain log_engine("engine");
|
|
#define ERR_NG LOG_STREAM(err, log_engine)
|
|
|
|
static lg::log_domain log_config("config");
|
|
#define WRN_CF LOG_STREAM(warn, log_config)
|
|
#define ERR_CONFIG LOG_STREAM(err, log_config)
|
|
|
|
static lg::log_domain log_enginerefac("enginerefac");
|
|
#define LOG_RG LOG_STREAM(info, log_enginerefac)
|
|
|
|
namespace {
|
|
const std::string ModificationTypes[] = { "advance", "trait", "object" };
|
|
const size_t NumModificationTypes = sizeof(ModificationTypes)/
|
|
sizeof(*ModificationTypes);
|
|
|
|
/**
|
|
* Pointers to units which have data in their internal caches. The
|
|
* destructor of an unit removes itself from the cache, so the pointers are
|
|
* always valid.
|
|
*/
|
|
static std::vector<const unit *> units_with_cache;
|
|
|
|
const std::string leader_crown_path = "misc/leader-crown.png";
|
|
}
|
|
|
|
/**
|
|
* Intrusive Pointer interface
|
|
*
|
|
**/
|
|
|
|
void intrusive_ptr_add_ref(const unit * u)
|
|
{
|
|
++(u->ref_count_);
|
|
}
|
|
|
|
void intrusive_ptr_release(const unit * u)
|
|
{
|
|
if (--(u->ref_count_) == 0)
|
|
delete u;
|
|
}
|
|
|
|
/**
|
|
* Converts a string ID to a unit_type.
|
|
* Throws a game_error exception if the string does not correspond to a type.
|
|
*/
|
|
static const unit_type &get_unit_type(const std::string &type_id)
|
|
{
|
|
if ( type_id.empty() )
|
|
throw game::game_error("creating unit with an empty type field");
|
|
|
|
const unit_type *i = unit_types.find(type_id);
|
|
if (!i) throw game::game_error("unknown unit type: " + type_id);
|
|
return *i;
|
|
}
|
|
|
|
static unit_race::GENDER generate_gender(const unit_type & type, bool random_gender)
|
|
{
|
|
const std::vector<unit_race::GENDER>& genders = type.genders();
|
|
assert( genders.size() > 0 );
|
|
|
|
if ( random_gender == false || genders.size() == 1 ) {
|
|
return genders.front();
|
|
} else {
|
|
int random = random_new::generator->next_random();
|
|
return genders[random % genders.size()];
|
|
// Note: genders is guaranteed to be non-empty, so this is not a
|
|
// potential division by zero.
|
|
// Note: Whoever wrote this code, you should have used an assertion, to save others hours of work...
|
|
// If the assertion size>0 is failing for you, one possible cause is that you are constructing a unit
|
|
// from a unit type which has not been ``built'' using the unit_type_data methods.
|
|
}
|
|
}
|
|
|
|
static unit_race::GENDER generate_gender(const unit_type & u_type, const config &cfg)
|
|
{
|
|
const std::string& gender = cfg["gender"];
|
|
if(!gender.empty())
|
|
return string_gender(gender);
|
|
|
|
return generate_gender(u_type, cfg["random_gender"].to_bool());
|
|
}
|
|
|
|
const std::string& unit::leader_crown()
|
|
{
|
|
return leader_crown_path;
|
|
}
|
|
|
|
// Copy constructor
|
|
unit::unit(const unit& o):
|
|
cfg_(o.cfg_),
|
|
loc_(o.loc_),
|
|
advances_to_(o.advances_to_),
|
|
type_(o.type_),
|
|
type_name_(o.type_name_),
|
|
race_(o.race_),
|
|
id_(o.id_),
|
|
name_(o.name_),
|
|
underlying_id_(o.underlying_id_),
|
|
undead_variation_(o.undead_variation_),
|
|
variation_(o.variation_),
|
|
|
|
hit_points_(o.hit_points_),
|
|
max_hit_points_(o.max_hit_points_),
|
|
experience_(o.experience_),
|
|
max_experience_(o.max_experience_),
|
|
level_(o.level_),
|
|
recall_cost_(o.recall_cost_),
|
|
canrecruit_(o.canrecruit_),
|
|
recruit_list_(o.recruit_list_),
|
|
alignment_(o.alignment_),
|
|
flag_rgb_(o.flag_rgb_),
|
|
image_mods_(o.image_mods_),
|
|
|
|
unrenamable_(o.unrenamable_),
|
|
side_(o.side_),
|
|
gender_(o.gender_),
|
|
|
|
alpha_(o.alpha_),
|
|
|
|
formula_man_(new unit_formula_manager(o.formula_manager())),
|
|
|
|
movement_(o.movement_),
|
|
max_movement_(o.max_movement_),
|
|
vision_(o.vision_),
|
|
jamming_(o.jamming_),
|
|
movement_type_(o.movement_type_),
|
|
hold_position_(o.hold_position_),
|
|
end_turn_(o.end_turn_),
|
|
resting_(o.resting_),
|
|
attacks_left_(o.attacks_left_),
|
|
max_attacks_(o.max_attacks_),
|
|
|
|
states_(o.states_),
|
|
known_boolean_states_(o.known_boolean_states_),
|
|
variables_(o.variables_),
|
|
events_(o.events_),
|
|
filter_recall_(o.filter_recall_),
|
|
emit_zoc_(o.emit_zoc_),
|
|
|
|
overlays_(o.overlays_),
|
|
|
|
role_(o.role_),
|
|
attacks_(o.attacks_),
|
|
facing_(o.facing_),
|
|
|
|
trait_names_(o.trait_names_),
|
|
trait_descriptions_(o.trait_descriptions_),
|
|
unit_value_(o.unit_value_),
|
|
goto_(o.goto_),
|
|
interrupted_move_(o.interrupted_move_),
|
|
is_fearless_(o.is_fearless_),
|
|
is_healthy_(o.is_healthy_),
|
|
|
|
modification_descriptions_(o.modification_descriptions_),
|
|
|
|
anim_comp_(new unit_animation_component(*this, *o.anim_comp_)),
|
|
|
|
getsHit_(o.getsHit_),
|
|
hidden_(o.hidden_),
|
|
hp_bar_scaling_(o.hp_bar_scaling_),
|
|
xp_bar_scaling_(o.xp_bar_scaling_),
|
|
|
|
modifications_(o.modifications_),
|
|
invisibility_cache_()
|
|
{
|
|
}
|
|
|
|
unit::unit(const config &cfg, bool use_traits, const vconfig* vcfg) :
|
|
cfg_(),
|
|
loc_(cfg["x"] - 1, cfg["y"] - 1),
|
|
advances_to_(),
|
|
type_(&get_unit_type(cfg["parent_type"].blank() ? cfg["type"] : cfg["parent_type"])),
|
|
type_name_(),
|
|
race_(&unit_race::null_race),
|
|
id_(cfg["id"]),
|
|
name_(cfg["name"].t_str()),
|
|
underlying_id_(0),
|
|
undead_variation_(),
|
|
variation_(cfg["variation"].empty() ? type_->default_variation() : cfg["variation"]),
|
|
hit_points_(1),
|
|
max_hit_points_(0),
|
|
experience_(0),
|
|
max_experience_(0),
|
|
level_(0),
|
|
recall_cost_(-1),
|
|
canrecruit_(cfg["canrecruit"].to_bool()),
|
|
recruit_list_(),
|
|
alignment_(lexical_cast_default<unit_type::ALIGNMENT> (cfg["alignment"].str(), unit_type::NEUTRAL)),
|
|
flag_rgb_(),
|
|
image_mods_(),
|
|
unrenamable_(false),
|
|
side_(0),
|
|
gender_(generate_gender(*type_, cfg)),
|
|
alpha_(),
|
|
formula_man_(new unit_formula_manager()),
|
|
movement_(0),
|
|
max_movement_(0),
|
|
vision_(-1),
|
|
jamming_(0),
|
|
movement_type_(),
|
|
hold_position_(false),
|
|
end_turn_(false),
|
|
resting_(false),
|
|
attacks_left_(0),
|
|
max_attacks_(0),
|
|
states_(),
|
|
known_boolean_states_(known_boolean_state_names_.size(),false),
|
|
variables_(),
|
|
events_(),
|
|
filter_recall_(),
|
|
emit_zoc_(0),
|
|
overlays_(),
|
|
role_(cfg["role"]),
|
|
attacks_(),
|
|
facing_(map_location::NDIRECTIONS),
|
|
trait_names_(),
|
|
trait_descriptions_(),
|
|
unit_value_(),
|
|
goto_(),
|
|
interrupted_move_(),
|
|
is_fearless_(false),
|
|
is_healthy_(false),
|
|
modification_descriptions_(),
|
|
anim_comp_(new unit_animation_component(*this)),
|
|
getsHit_(0),
|
|
hidden_(false),
|
|
hp_bar_scaling_(cfg["hp_bar_scaling"].blank() ? type_->hp_bar_scaling() : cfg["hp_bar_scaling"]),
|
|
xp_bar_scaling_(cfg["xp_bar_scaling"].blank() ? type_->xp_bar_scaling() : cfg["xp_bar_scaling"]),
|
|
modifications_(),
|
|
invisibility_cache_()
|
|
{
|
|
side_ = cfg["side"];
|
|
if(side_ <= 0) {
|
|
side_ = 1;
|
|
}
|
|
|
|
validate_side(side_);
|
|
|
|
underlying_id_ = cfg["underlying_id"];
|
|
set_underlying_id();
|
|
|
|
overlays_ = utils::parenthetical_split(cfg["overlays"], ',');
|
|
if(overlays_.size() == 1 && overlays_.front() == "") {
|
|
overlays_.clear();
|
|
}
|
|
if (const config &variables = cfg.child("variables")) {
|
|
variables_ = variables;
|
|
}
|
|
|
|
if(vcfg) {
|
|
const vconfig& filter_recall = vcfg->child("filter_recall");
|
|
if(!filter_recall.null())
|
|
filter_recall_ = filter_recall.get_config();
|
|
|
|
const vconfig::child_list& events = vcfg->get_children("event");
|
|
BOOST_FOREACH(const vconfig& e, events) {
|
|
events_.add_child("event", e.get_config());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filter_recall_ = cfg.child_or_empty("filter_recall");
|
|
|
|
BOOST_FOREACH(const config& unit_event, cfg.child_range("event")) {
|
|
events_.add_child("event", unit_event);
|
|
}
|
|
}
|
|
game_events::add_events(events_.child_range("event"));
|
|
|
|
|
|
facing_ = map_location::parse_direction(cfg["facing"]);
|
|
if(facing_ == map_location::NDIRECTIONS) facing_ = static_cast<map_location::DIRECTION>(rand()%map_location::NDIRECTIONS);
|
|
|
|
if (const config &mods = cfg.child("modifications")) {
|
|
modifications_ = mods;
|
|
}
|
|
|
|
// Apply the unit type's data to this unit.
|
|
advance_to(cfg, *type_, use_traits);
|
|
|
|
if (const config::attribute_value *v = cfg.get("race")) {
|
|
if (const unit_race *r = unit_types.find_race(*v)) {
|
|
race_ = r;
|
|
} else {
|
|
race_ = &unit_race::null_race;
|
|
}
|
|
}
|
|
level_ = cfg["level"].to_int(level_);
|
|
if (const config::attribute_value *v = cfg.get("undead_variation")) {
|
|
undead_variation_ = v->str();
|
|
}
|
|
if(const config::attribute_value *v = cfg.get("max_attacks")) {
|
|
max_attacks_ = std::max(0, v->to_int(1));
|
|
}
|
|
attacks_left_ = std::max(0, cfg["attacks_left"].to_int(max_attacks_));
|
|
|
|
if (const config::attribute_value *v = cfg.get("alpha")) {
|
|
alpha_ = lexical_cast_default<fixed_t>(*v);
|
|
}
|
|
if (const config::attribute_value *v = cfg.get("zoc")) {
|
|
emit_zoc_ = v->to_bool(level_ > 0);
|
|
}
|
|
if (const config::attribute_value *v = cfg.get("description")) {
|
|
cfg_["description"] = *v;
|
|
}
|
|
if (const config::attribute_value *v = cfg.get("cost")) {
|
|
unit_value_ = *v;
|
|
}
|
|
if (const config::attribute_value *v = cfg.get("halo")) {
|
|
anim_comp_->clear_haloes();
|
|
cfg_["halo"] = *v;
|
|
}
|
|
if (const config::attribute_value *v = cfg.get("profile")) {
|
|
std::string big = *v, small = cfg["small_profile"];
|
|
adjust_profile(small, big, "");
|
|
cfg_["profile"] = big;
|
|
cfg_["small_profile"] = small;
|
|
}
|
|
max_hit_points_ = std::max(1, cfg["max_hitpoints"].to_int(max_hit_points_));
|
|
max_movement_ = std::max(0, cfg["max_moves"].to_int(max_movement_));
|
|
max_experience_ = std::max(1, cfg["max_experience"].to_int(max_experience_));
|
|
|
|
vision_ = cfg["vision"].to_int(vision_);
|
|
|
|
std::vector<std::string> temp_advances = utils::split(cfg["advances_to"]);
|
|
if(temp_advances.size() == 1 && temp_advances.front() == "null") {
|
|
advances_to_.clear();
|
|
}else if(temp_advances.size() >= 1 && temp_advances.front() != "") {
|
|
advances_to_ = temp_advances;
|
|
}
|
|
|
|
if (const config &ai = cfg.child("ai"))
|
|
{
|
|
formula_man_->read(ai);
|
|
}
|
|
|
|
//don't use the unit_type's attacks if this config has its own defined
|
|
config::const_child_itors cfg_range = cfg.child_range("attack");
|
|
if(cfg_range.first != cfg_range.second) {
|
|
attacks_.clear();
|
|
do {
|
|
attacks_.push_back(attack_type(*cfg_range.first));
|
|
} while(++cfg_range.first != cfg_range.second);
|
|
}
|
|
|
|
//don't use the unit_type's abilities if this config has its own defined
|
|
cfg_range = cfg.child_range("abilities");
|
|
if(cfg_range.first != cfg_range.second) {
|
|
cfg_.clear_children("abilities");
|
|
config &target = cfg_.add_child("abilities");
|
|
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"))
|
|
{
|
|
BOOST_FOREACH(const config::attribute &st, status_flags.attribute_range()) {
|
|
if (st.second.to_bool()) {
|
|
set_state(st.first, true);
|
|
}
|
|
}
|
|
}
|
|
if(cfg["ai_special"] == "guardian") {
|
|
set_state(STATE_GUARDIAN, true);
|
|
}
|
|
|
|
// Remove animations from private cfg, they're not needed there now
|
|
BOOST_FOREACH(const std::string& tag_name, unit_animation::all_tag_names()) {
|
|
cfg_.clear_children(tag_name);
|
|
}
|
|
|
|
if (const config::attribute_value *v = cfg.get("hitpoints")) {
|
|
hit_points_ = *v;
|
|
} else {
|
|
hit_points_ = max_hit_points_;
|
|
}
|
|
|
|
goto_.x = cfg["goto_x"].to_int() - 1;
|
|
goto_.y = cfg["goto_y"].to_int() - 1;
|
|
|
|
if (const config::attribute_value *v = cfg.get("moves")) {
|
|
movement_ = *v;
|
|
if(movement_ < 0) {
|
|
attacks_left_ = 0;
|
|
movement_ = 0;
|
|
}
|
|
} else {
|
|
movement_ = max_movement_;
|
|
}
|
|
experience_ = cfg["experience"];
|
|
resting_ = cfg["resting"].to_bool();
|
|
unrenamable_ = cfg["unrenamable"].to_bool();
|
|
|
|
/* We need to check to make sure that the cfg is not blank and if it
|
|
isn't pull that value otherwise it goes with the default of -1. */
|
|
if(!cfg["recall_cost"].blank()) {
|
|
recall_cost_ = cfg["recall_cost"].to_int(recall_cost_);
|
|
}
|
|
|
|
generate_name();
|
|
|
|
// Make the default upkeep "full"
|
|
if(cfg_["upkeep"].empty()) {
|
|
cfg_["upkeep"] = "full";
|
|
}
|
|
|
|
set_recruits(utils::split(cfg["extra_recruit"]));
|
|
|
|
game_config::add_color_info(cfg);
|
|
|
|
config input_cfg;
|
|
input_cfg.merge_attributes(cfg);
|
|
|
|
static char const *internalized_attrs[] = { "type", "id", "name",
|
|
"gender", "random_gender", "variation", "role", "ai_special",
|
|
"side", "underlying_id", "overlays", "facing", "race",
|
|
"level", "recall_cost", "undead_variation", "max_attacks",
|
|
"attacks_left", "alpha", "zoc", "flying", "cost",
|
|
"max_hitpoints", "max_moves", "vision", "jamming", "max_experience",
|
|
"advances_to", "hitpoints", "goto_x", "goto_y", "moves",
|
|
"experience", "resting", "unrenamable", "alignment",
|
|
"canrecruit", "extra_recruit", "x", "y", "placement",
|
|
"parent_type",
|
|
// Useless attributes created when saving units to WML:
|
|
"flag_rgb", "language_name" };
|
|
BOOST_FOREACH(const char *attr, internalized_attrs) {
|
|
input_cfg.remove_attribute(attr);
|
|
cfg_.remove_attribute(attr);
|
|
}
|
|
|
|
static char const *raw_attrs[] = { "description", "halo",
|
|
"profile", "small_profile", "upkeep", "usage", "ellipse",
|
|
"image", "image_icon", "random_traits", "generate_name" };
|
|
BOOST_FOREACH(const char *attr, raw_attrs) {
|
|
input_cfg.remove_attribute(attr);
|
|
}
|
|
|
|
BOOST_FOREACH(const config::attribute &attr, input_cfg.attribute_range()) {
|
|
if (attr.first == "do_not_list") continue;
|
|
WRN_UT << "Unknown attribute '" << attr.first << "' discarded." << std::endl;
|
|
}
|
|
|
|
//debug unit animations for units as they appear in game
|
|
/*for(std::vector<unit_animation>::const_iterator i = anim_comp_->animations_.begin(); i != anim_comp_->animations_.end(); ++i) {
|
|
std::cout << (*i).debug();
|
|
}*/
|
|
}
|
|
|
|
void unit::clear_status_caches()
|
|
{
|
|
for(std::vector<const unit *>::const_iterator itor = units_with_cache.begin();
|
|
itor != units_with_cache.end(); ++itor) {
|
|
(*itor)->clear_visibility_cache();
|
|
}
|
|
|
|
units_with_cache.clear();
|
|
}
|
|
|
|
unit::unit(const unit_type &u_type, int side, bool real_unit,
|
|
unit_race::GENDER gender) :
|
|
cfg_(),
|
|
loc_(),
|
|
advances_to_(),
|
|
type_(&u_type),
|
|
type_name_(),
|
|
race_(&unit_race::null_race),
|
|
id_(),
|
|
name_(),
|
|
underlying_id_(real_unit? 0: n_unit::id_manager::instance().next_fake_id()),
|
|
undead_variation_(),
|
|
variation_(type_->default_variation()),
|
|
hit_points_(0),
|
|
max_hit_points_(0),
|
|
experience_(0),
|
|
max_experience_(0),
|
|
level_(0),
|
|
recall_cost_(-1),
|
|
canrecruit_(false),
|
|
recruit_list_(),
|
|
alignment_(),
|
|
flag_rgb_(),
|
|
image_mods_(),
|
|
unrenamable_(false),
|
|
side_(side),
|
|
gender_(gender != unit_race::NUM_GENDERS ?
|
|
gender : generate_gender(u_type, real_unit)),
|
|
alpha_(),
|
|
formula_man_(new unit_formula_manager()),
|
|
movement_(0),
|
|
max_movement_(0),
|
|
vision_(-1),
|
|
jamming_(0),
|
|
movement_type_(),
|
|
hold_position_(false),
|
|
end_turn_(false),
|
|
resting_(false),
|
|
attacks_left_(0),
|
|
max_attacks_(0),
|
|
states_(),
|
|
known_boolean_states_( get_known_boolean_state_names().size(),false),
|
|
variables_(),
|
|
events_(),
|
|
filter_recall_(),
|
|
emit_zoc_(0),
|
|
overlays_(),
|
|
role_(),
|
|
attacks_(),
|
|
facing_(static_cast<map_location::DIRECTION>(rand()%map_location::NDIRECTIONS)),
|
|
trait_names_(),
|
|
trait_descriptions_(),
|
|
unit_value_(),
|
|
goto_(),
|
|
interrupted_move_(),
|
|
is_fearless_(false),
|
|
is_healthy_(false),
|
|
modification_descriptions_(),
|
|
anim_comp_(new unit_animation_component(*this)),
|
|
getsHit_(0),
|
|
hidden_(false),
|
|
modifications_(),
|
|
invisibility_cache_()
|
|
{
|
|
cfg_["upkeep"]="full";
|
|
|
|
// Apply the unit type's data to this unit.
|
|
advance_to(u_type, real_unit);
|
|
|
|
if(real_unit) {
|
|
generate_name();
|
|
}
|
|
set_underlying_id();
|
|
|
|
// Set these after traits and modifications have set the maximums.
|
|
movement_ = max_movement_;
|
|
hit_points_ = max_hit_points_;
|
|
attacks_left_ = max_attacks_;
|
|
}
|
|
|
|
unit::~unit()
|
|
{
|
|
try {
|
|
anim_comp_->clear_haloes();
|
|
|
|
// Remove us from the status cache
|
|
std::vector<const unit *>::iterator itor =
|
|
std::find(units_with_cache.begin(), units_with_cache.end(), this);
|
|
|
|
if(itor != units_with_cache.end()) {
|
|
units_with_cache.erase(itor);
|
|
}
|
|
} catch (std::exception & e) {
|
|
ERR_UT << "Caught exception when destroying unit: " << e.what() << std::endl;
|
|
} catch (...) {}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Assignment operator.
|
|
*
|
|
* This function is unsuitable for derived classes and MUST be overridden.
|
|
* Furthermore, derived classes must not explicitly call this version.
|
|
*
|
|
* The overriding function can be almost the same, except "new (this)" should
|
|
* be followed by the derived class instead of "unit(u)".
|
|
* (There was only one derived class when this was written, so this approach
|
|
* might be simplest.)
|
|
*/
|
|
unit& unit::operator=(const unit& u)
|
|
{
|
|
// Use copy constructor to make sure we are coherent
|
|
if (this != &u) {
|
|
this->~unit();
|
|
new (this) unit(u) ;
|
|
}
|
|
return *this ;
|
|
}
|
|
|
|
|
|
void unit::generate_name()
|
|
{
|
|
if (!name_.empty() || !cfg_["generate_name"].to_bool(true)) return;
|
|
|
|
name_ = race_->generate_name(gender_);
|
|
cfg_["generate_name"] = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Apply mandatory traits (e.g. undead, mechanical) to a unit and then
|
|
* fill out with available (leaders have a restricted set of available traits)
|
|
* traits until no more are available or the unit has its maximum number
|
|
* of traits.
|
|
* This routine does not apply the effects of added traits to a unit.
|
|
* That must be done by the caller.
|
|
* Note that random numbers used in config files don't work in multiplayer,
|
|
* so that leaders should be barred from all random traits until that
|
|
* is fixed. Later the restrictions will be based on play balance.
|
|
* @a musthaveonly is true when you don't want to generate random traits or
|
|
* you don't want to give any optional traits to a unit.
|
|
*/
|
|
void unit::generate_traits(bool musthaveonly)
|
|
{
|
|
LOG_UT << "Generating a trait for unit type " << type().log_id() << " with musthaveonly " << musthaveonly << "\n";
|
|
const unit_type &u_type = type();
|
|
|
|
// Calculate the unit's traits
|
|
config::const_child_itors current_traits = modifications_.child_range("trait");
|
|
std::vector<config> candidate_traits;
|
|
|
|
BOOST_FOREACH(const config &t, u_type.possible_traits())
|
|
{
|
|
// Skip the trait if the unit already has it.
|
|
const std::string &tid = t["id"];
|
|
bool already = false;
|
|
BOOST_FOREACH(const config &mod, current_traits)
|
|
{
|
|
if (mod["id"] == tid) {
|
|
already = true;
|
|
break;
|
|
}
|
|
}
|
|
if (already) continue;
|
|
|
|
// Add the trait if it is mandatory.
|
|
const std::string &avl = t["availability"];
|
|
if (avl == "musthave")
|
|
{
|
|
modifications_.add_child("trait", t);
|
|
current_traits = modifications_.child_range("trait");
|
|
continue;
|
|
}
|
|
|
|
// The trait is still available, mark it as a candidate for randomizing.
|
|
// For leaders, only traits with availability "any" are considered.
|
|
if (!musthaveonly && (!can_recruit() || avl == "any"))
|
|
candidate_traits.push_back(t);
|
|
}
|
|
|
|
if (musthaveonly) return;
|
|
|
|
// Now randomly fill out to the number of traits required or until
|
|
// there aren't any more traits.
|
|
int nb_traits = std::distance(current_traits.first, current_traits.second);
|
|
int max_traits = u_type.num_traits();
|
|
for (; nb_traits < max_traits && !candidate_traits.empty(); ++nb_traits)
|
|
{
|
|
int num = random_new::generator->next_random() % candidate_traits.size();
|
|
modifications_.add_child("trait", candidate_traits[num]);
|
|
candidate_traits.erase(candidate_traits.begin() + num);
|
|
}
|
|
|
|
// Once random traits are added, don't do it again.
|
|
// Such as when restoring a saved character.
|
|
cfg_["random_traits"] = false;
|
|
}
|
|
|
|
std::vector<std::string> unit::get_traits_list() const
|
|
{
|
|
std::vector<std::string> res;
|
|
|
|
BOOST_FOREACH(const config &mod, modifications_.child_range("trait"))
|
|
{
|
|
std::string const &id = mod["id"];
|
|
// Make sure to return empty id trait strings as otherwise
|
|
// names will not match in length (Bug #21967)
|
|
res.push_back(id);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Advances this unit to the specified type.
|
|
* Experience is left unchanged.
|
|
* Current hit point total is left unchanged unless it would violate max HP.
|
|
* Assumes gender_ and variation_ are set to their correct values.
|
|
*/
|
|
void unit::advance_to(const config &old_cfg, const unit_type &u_type,
|
|
bool use_traits)
|
|
{
|
|
// For reference, the type before this advancement.
|
|
const unit_type & old_type = type();
|
|
// Adjust the new type for gender and variation.
|
|
const unit_type & new_type = u_type.get_gender_unit_type(gender_).get_variation(variation_);
|
|
|
|
// Reset the scalar values first
|
|
trait_names_.clear();
|
|
trait_descriptions_.clear(),
|
|
is_fearless_ = false;
|
|
is_healthy_ = false;
|
|
|
|
// Clear modification-related caches
|
|
modification_descriptions_.clear();
|
|
|
|
// Clear the stored config and replace it with the one from the unit type,
|
|
// except for a few attributes.
|
|
config new_cfg;
|
|
static char const *persistent_attrs[] = { "upkeep", "ellipse",
|
|
"image", "image_icon", "usage", "random_traits", "generate_name" };
|
|
BOOST_FOREACH(const char *attr, persistent_attrs) {
|
|
if (const config::attribute_value *v = old_cfg.get(attr)) {
|
|
new_cfg[attr] = *v;
|
|
}
|
|
}
|
|
|
|
// Inherit from the new unit type.
|
|
new_cfg.merge_with(new_type.get_cfg_for_units());
|
|
|
|
// If unit has specific profile, remember it and keep it after advancing
|
|
std::string profile = old_cfg["profile"].str();
|
|
if ( !profile.empty() && profile != old_type.big_profile() ) {
|
|
new_cfg["profile"] = profile;
|
|
} else {
|
|
new_cfg["profile"] = new_type.big_profile();
|
|
}
|
|
profile = old_cfg["small_profile"].str();
|
|
if ( !profile.empty() && profile != old_type.small_profile() ) {
|
|
new_cfg["small_profile"] = profile;
|
|
} else {
|
|
new_cfg["small_profile"] = new_type.small_profile();
|
|
}
|
|
|
|
cfg_.swap(new_cfg);
|
|
// NOTE: There should be no need to access old_cfg (or new_cfg) after this
|
|
// line. Particularly since the swap might have affected old_cfg.
|
|
|
|
advances_to_ = new_type.advances_to();
|
|
|
|
race_ = new_type.race();
|
|
type_ = &new_type;
|
|
type_name_ = new_type.type_name();
|
|
cfg_["description"] = new_type.unit_description();
|
|
undead_variation_ = new_type.undead_variation();
|
|
max_experience_ = new_type.experience_needed(false);
|
|
level_ = new_type.level();
|
|
recall_cost_ = new_type.recall_cost();
|
|
/* Need to add a check to see if the unit's old cost is equal
|
|
to the unit's old unit_type cost first. If it is change the cost
|
|
otherwise keep the old cost. */
|
|
if(old_type.recall_cost() == recall_cost_) {
|
|
recall_cost_ = new_type.recall_cost();
|
|
}
|
|
alignment_ = new_type.alignment();
|
|
alpha_ = new_type.alpha();
|
|
max_hit_points_ = new_type.hitpoints();
|
|
hp_bar_scaling_ = new_type.hp_bar_scaling();
|
|
xp_bar_scaling_ = new_type.xp_bar_scaling();
|
|
max_movement_ = new_type.movement();
|
|
vision_ = new_type.vision(true);
|
|
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();
|
|
|
|
max_attacks_ = new_type.max_attacks();
|
|
|
|
flag_rgb_ = new_type.flag_rgb();
|
|
|
|
|
|
if (cfg_["random_traits"].to_bool(true)) {
|
|
generate_traits(!use_traits);
|
|
} else {
|
|
// This will add any "musthave" traits to the new unit that it doesn't already have.
|
|
// This covers the Dark Sorcerer advancing to Lich and gaining the "undead" trait,
|
|
// but random and/or optional traits are not added,
|
|
// and neither are inappropriate traits removed.
|
|
generate_traits(true);
|
|
}
|
|
|
|
// Apply modifications etc, refresh the unit.
|
|
// This needs to be after type and gender are fixed,
|
|
// since there can be filters on the modifications
|
|
// that may result in different effects after the advancement.
|
|
apply_modifications();
|
|
|
|
// Now that modifications are done modifying traits, check if poison should
|
|
// be cleared.
|
|
if ( get_state("unpoisonable") )
|
|
set_state(STATE_POISONED, false);
|
|
|
|
// Now that modifications are done modifying the maximum hit points,
|
|
// enforce this maximum.
|
|
if ( hit_points_ > max_hit_points_ )
|
|
hit_points_ = max_hit_points_;
|
|
|
|
// In case the unit carries EventWML, apply it now
|
|
game_events::add_events(cfg_.child_range("event"), new_type.id());
|
|
cfg_.clear_children("event");
|
|
|
|
anim_comp_->reset_after_advance(&new_type);
|
|
}
|
|
|
|
std::string unit::big_profile() const
|
|
{
|
|
const std::string &prof = cfg_["profile"];
|
|
if (!prof.empty() && prof != "unit_image") {
|
|
return prof;
|
|
}
|
|
return absolute_image();
|
|
}
|
|
|
|
std::string unit::small_profile() const
|
|
{
|
|
const std::string &prof = cfg_["small_profile"];
|
|
if (!prof.empty() && prof != "unit_image") {
|
|
return prof;
|
|
}
|
|
return absolute_image();
|
|
}
|
|
|
|
static SDL_Color hp_color_(int hitpoints, int max_hitpoints)
|
|
{
|
|
double unit_energy = 0.0;
|
|
SDL_Color energy_color = {0,0,0,0};
|
|
|
|
if(max_hitpoints > 0) {
|
|
unit_energy = double(hitpoints)/double(max_hitpoints);
|
|
}
|
|
|
|
if(1.0 == unit_energy){
|
|
energy_color.r = 33;
|
|
energy_color.g = 225;
|
|
energy_color.b = 0;
|
|
} else if(unit_energy > 1.0) {
|
|
energy_color.r = 100;
|
|
energy_color.g = 255;
|
|
energy_color.b = 100;
|
|
} else if(unit_energy >= 0.75) {
|
|
energy_color.r = 170;
|
|
energy_color.g = 255;
|
|
energy_color.b = 0;
|
|
} else if(unit_energy >= 0.5) {
|
|
energy_color.r = 255;
|
|
energy_color.g = 175;
|
|
energy_color.b = 0;
|
|
} else if(unit_energy >= 0.25) {
|
|
energy_color.r = 255;
|
|
energy_color.g = 155;
|
|
energy_color.b = 0;
|
|
} else {
|
|
energy_color.r = 255;
|
|
energy_color.g = 0;
|
|
energy_color.b = 0;
|
|
}
|
|
return energy_color;
|
|
}
|
|
|
|
SDL_Color unit::hp_color() const
|
|
{
|
|
return hp_color_(hitpoints(), max_hitpoints());
|
|
}
|
|
|
|
SDL_Color unit::hp_color(int new_hitpoints) const
|
|
{
|
|
return hp_color_(new_hitpoints, hitpoints());
|
|
}
|
|
|
|
SDL_Color unit::xp_color() const
|
|
{
|
|
const SDL_Color near_advance_color = {255,255,255,0};
|
|
const SDL_Color mid_advance_color = {150,255,255,0};
|
|
const SDL_Color far_advance_color = {0,205,205,0};
|
|
const SDL_Color normal_color = {0,160,225,0};
|
|
const SDL_Color near_amla_color = {225,0,255,0};
|
|
const SDL_Color mid_amla_color = {169,30,255,0};
|
|
const SDL_Color far_amla_color = {139,0,237,0};
|
|
const SDL_Color amla_color = {170,0,255,0};
|
|
const bool near_advance = max_experience() - experience() <= game_config::kill_experience;
|
|
const bool mid_advance = max_experience() - experience() <= game_config::kill_experience*2;
|
|
const bool far_advance = max_experience() - experience() <= game_config::kill_experience*3;
|
|
|
|
SDL_Color color=normal_color;
|
|
if(advances_to().size()){
|
|
if(near_advance){
|
|
color=near_advance_color;
|
|
} else if(mid_advance){
|
|
color=mid_advance_color;
|
|
} else if(far_advance){
|
|
color=far_advance_color;
|
|
}
|
|
} else if (get_modification_advances().size()){
|
|
if(near_advance){
|
|
color=near_amla_color;
|
|
} else if(mid_advance){
|
|
color=mid_amla_color;
|
|
} else if(far_advance){
|
|
color=far_amla_color;
|
|
} else {
|
|
color=amla_color;
|
|
}
|
|
}
|
|
return(color);
|
|
}
|
|
|
|
void unit::set_recruits(const std::vector<std::string>& recruits)
|
|
{
|
|
unit_types.check_types(recruits);
|
|
recruit_list_ = recruits;
|
|
//TODO crab
|
|
//info_.minimum_recruit_price = 0;
|
|
//ai::manager::raise_recruit_list_changed();
|
|
}
|
|
|
|
const std::vector<std::string> unit::advances_to_translated() const
|
|
{
|
|
std::vector<std::string> result;
|
|
BOOST_FOREACH(std::string adv_type_id, advances_to_)
|
|
{
|
|
const unit_type *adv_type = unit_types.find(adv_type_id);
|
|
if ( adv_type )
|
|
result.push_back(adv_type->type_name());
|
|
else
|
|
WRN_UT << "unknown unit in advances_to list of type "
|
|
<< type().log_id() << ": " << adv_type_id << "\n";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void unit::set_advances_to(const std::vector<std::string>& advances_to)
|
|
{
|
|
unit_types.check_types(advances_to);
|
|
advances_to_ = advances_to;
|
|
}
|
|
|
|
std::string unit::side_id() const {return teams_manager::get_teams()[side()-1].save_id(); }
|
|
|
|
/**
|
|
* Set the unit's remaining movement to @a moves.
|
|
* If @a unit_action is set to true, then additionally the "end turn" and
|
|
* "hold position" flags will be cleared (as they should be if a unit acts,
|
|
* as opposed to the movement being set by the engine for other reasons).
|
|
*/
|
|
void unit::set_movement(int moves, bool unit_action)
|
|
{
|
|
// If this was because the unit acted, clear its "not acting" flags.
|
|
if ( unit_action )
|
|
end_turn_ = hold_position_ = false;
|
|
|
|
movement_ = std::max<int>(0, moves);
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines if @a mod_dur "matches" @a goal_dur.
|
|
* If goal_dur is not empty, they match if they are equal.
|
|
* If goal_dur is empty, they match if mod_dur is neither empty nor "forever".
|
|
* Helper function for expire_modifications().
|
|
*/
|
|
inline bool mod_duration_match(const std::string & mod_dur,
|
|
const std::string & goal_dur)
|
|
{
|
|
if ( goal_dur.empty() )
|
|
// Default is all temporary modifications.
|
|
return !mod_dur.empty() && mod_dur != "forever";
|
|
else
|
|
return mod_dur == goal_dur;
|
|
}
|
|
|
|
/**
|
|
* Clears those modifications whose duration has expired.
|
|
* If @a duration is empty, then all temporary modifications (those not
|
|
* lasting forever) have expired. Otherwise, modifications whose duration
|
|
* equals @a duration have expired.
|
|
*/
|
|
void unit::expire_modifications(const std::string & duration)
|
|
{
|
|
// If any modifications expire, then we will need to rebuild the unit.
|
|
const unit_type * rebuild_from = NULL;
|
|
|
|
// Loop through all types of modifications.
|
|
for(unsigned int i = 0; i != NumModificationTypes; ++i) {
|
|
const std::string& mod_name = ModificationTypes[i];
|
|
// Loop through all modifications of this type.
|
|
// Looping in reverse since we may delete the current modification.
|
|
for (int j = modifications_.child_count(mod_name)-1; j >= 0; --j)
|
|
{
|
|
const config &mod = modifications_.child(mod_name, j);
|
|
|
|
if ( mod_duration_match(mod["duration"], duration) ) {
|
|
// If removing this mod means reverting the unit's type:
|
|
if ( const config::attribute_value *v = mod.get("prev_type") ) {
|
|
rebuild_from = &get_unit_type(v->str());
|
|
}
|
|
// Else, if we have not already specified a type to build from:
|
|
else if ( rebuild_from == NULL )
|
|
rebuild_from = &type();
|
|
|
|
modifications_.remove_child(mod_name, j);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( rebuild_from != NULL ) {
|
|
anim_comp_->clear_haloes();
|
|
advance_to(*rebuild_from);
|
|
}
|
|
}
|
|
|
|
|
|
void unit::new_turn()
|
|
{
|
|
expire_modifications("turn");
|
|
|
|
end_turn_ = hold_position_;
|
|
movement_ = total_movement();
|
|
attacks_left_ = max_attacks_;
|
|
set_state(STATE_UNCOVERED, false);
|
|
}
|
|
|
|
void unit::end_turn()
|
|
{
|
|
set_state(STATE_SLOWED,false);
|
|
if((movement_ != total_movement()) && !(get_state(STATE_NOT_MOVED))) {
|
|
resting_ = false;
|
|
}
|
|
set_state(STATE_NOT_MOVED,false);
|
|
// Clear interrupted move
|
|
set_interrupted_move(map_location());
|
|
}
|
|
|
|
void unit::new_scenario()
|
|
{
|
|
// Set the goto-command to be going to no-where
|
|
goto_ = map_location();
|
|
|
|
// Expire all temporary modifications.
|
|
expire_modifications("");
|
|
|
|
heal_all();
|
|
set_state(STATE_SLOWED, false);
|
|
set_state(STATE_POISONED, false);
|
|
set_state(STATE_PETRIFIED, false);
|
|
set_state(STATE_GUARDIAN, false);
|
|
}
|
|
|
|
void unit::heal(int amount)
|
|
{
|
|
int max_hp = max_hitpoints();
|
|
if (hit_points_ < max_hp) {
|
|
hit_points_ += amount;
|
|
if (hit_points_ > max_hp) {
|
|
hit_points_ = max_hp;
|
|
}
|
|
}
|
|
if(hit_points_<1) {
|
|
hit_points_ = 1;
|
|
}
|
|
}
|
|
|
|
const std::map<std::string,std::string> unit::get_states() const
|
|
{
|
|
std::map<std::string, std::string> all_states;
|
|
BOOST_FOREACH(std::string const &s, states_) {
|
|
all_states[s] = "yes";
|
|
}
|
|
for (std::map<std::string, state_t>::const_iterator i = known_boolean_state_names_.begin(),
|
|
i_end = known_boolean_state_names_.end(); i != i_end; ++i)
|
|
{
|
|
if (get_state(i->second)) {
|
|
all_states.insert(make_pair(i->first, "yes"));
|
|
}
|
|
|
|
}
|
|
// Backwards compatibility for not_living. Don't remove before 1.12
|
|
if (all_states.find("undrainable") != all_states.end() &&
|
|
all_states.find("unpoisonable") != all_states.end() &&
|
|
all_states.find("unplagueable") != all_states.end())
|
|
all_states["not_living"] = "yes";
|
|
return all_states;
|
|
}
|
|
|
|
bool unit::get_state(const std::string &state) const
|
|
{
|
|
state_t known_boolean_state_id = get_known_boolean_state_id(state);
|
|
if (known_boolean_state_id!=STATE_UNKNOWN){
|
|
return get_state(known_boolean_state_id);
|
|
}
|
|
// Backwards compatibility for not_living. Don't remove before 1.12
|
|
if (state == "not_living") {
|
|
return get_state("undrainable") &&
|
|
get_state("unpoisonable") &&
|
|
get_state("unplagueable");
|
|
}
|
|
return states_.find(state) != states_.end();
|
|
}
|
|
|
|
void unit::set_state(state_t state, bool value)
|
|
{
|
|
known_boolean_states_[state] = value;
|
|
}
|
|
|
|
bool unit::get_state(state_t state) const
|
|
{
|
|
return known_boolean_states_[state];
|
|
}
|
|
|
|
unit::state_t unit::get_known_boolean_state_id(const std::string &state) {
|
|
std::map<std::string, state_t>::const_iterator i = known_boolean_state_names_.find(state);
|
|
if (i != known_boolean_state_names_.end()) {
|
|
return i->second;
|
|
}
|
|
return STATE_UNKNOWN;
|
|
}
|
|
|
|
std::map<std::string, unit::state_t> unit::known_boolean_state_names_ = get_known_boolean_state_names();
|
|
|
|
std::map<std::string, unit::state_t> unit::get_known_boolean_state_names()
|
|
{
|
|
std::map<std::string, state_t> known_boolean_state_names_map;
|
|
known_boolean_state_names_map.insert(std::make_pair("slowed",STATE_SLOWED));
|
|
known_boolean_state_names_map.insert(std::make_pair("poisoned",STATE_POISONED));
|
|
known_boolean_state_names_map.insert(std::make_pair("petrified",STATE_PETRIFIED));
|
|
known_boolean_state_names_map.insert(std::make_pair("uncovered", STATE_UNCOVERED));
|
|
known_boolean_state_names_map.insert(std::make_pair("not_moved",STATE_NOT_MOVED));
|
|
known_boolean_state_names_map.insert(std::make_pair("unhealable",STATE_UNHEALABLE));
|
|
known_boolean_state_names_map.insert(std::make_pair("guardian",STATE_GUARDIAN));
|
|
return known_boolean_state_names_map;
|
|
}
|
|
|
|
void unit::set_state(const std::string &state, bool value)
|
|
{
|
|
state_t known_boolean_state_id = get_known_boolean_state_id(state);
|
|
if (known_boolean_state_id != STATE_UNKNOWN) {
|
|
set_state(known_boolean_state_id, value);
|
|
return;
|
|
}
|
|
// Backwards compatibility for not_living. Don't remove before 1.12
|
|
if (state == "not_living") {
|
|
set_state("undrainable", value);
|
|
set_state("unpoisonable", value);
|
|
set_state("unplagueable", value);
|
|
}
|
|
if (value)
|
|
states_.insert(state);
|
|
else
|
|
states_.erase(state);
|
|
}
|
|
|
|
|
|
bool unit::has_ability_by_id(const std::string& ability) const
|
|
{
|
|
if (const config &abil = cfg_.child("abilities"))
|
|
{
|
|
BOOST_FOREACH(const config::any_child &ab, abil.all_children_range()) {
|
|
if (ab.cfg["id"] == ability)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void unit::remove_ability_by_id(const std::string &ability)
|
|
{
|
|
if (config &abil = cfg_.child("abilities"))
|
|
{
|
|
config::all_children_iterator i = abil.ordered_begin();
|
|
while (i != abil.ordered_end()) {
|
|
if (i->cfg["id"] == ability) {
|
|
i = abil.erase(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool unit::matches_filter(const vconfig& cfg, const map_location& loc, bool use_flat_tod) const
|
|
{
|
|
bool matches = true;
|
|
|
|
if(loc.valid()) {
|
|
assert(resources::units != NULL);
|
|
scoped_xy_unit auto_store("this_unit", loc.x, loc.y, *resources::units);
|
|
matches = internal_matches_filter(cfg, loc, use_flat_tod);
|
|
} else {
|
|
// If loc is invalid, then this is a recall list unit (already been scoped)
|
|
matches = internal_matches_filter(cfg, loc, use_flat_tod);
|
|
}
|
|
|
|
// Handle [and], [or], and [not] with in-order precedence
|
|
vconfig::all_children_iterator cond = cfg.ordered_begin();
|
|
vconfig::all_children_iterator cond_end = cfg.ordered_end();
|
|
while(cond != cond_end)
|
|
{
|
|
|
|
const std::string& cond_name = cond.get_key();
|
|
const vconfig& cond_filter = cond.get_child();
|
|
|
|
// Handle [and]
|
|
if(cond_name == "and") {
|
|
matches = matches && matches_filter(cond_filter,loc,use_flat_tod);
|
|
}
|
|
// Handle [or]
|
|
else if(cond_name == "or") {
|
|
matches = matches || matches_filter(cond_filter,loc,use_flat_tod);
|
|
}
|
|
// Handle [not]
|
|
else if(cond_name == "not") {
|
|
matches = matches && !matches_filter(cond_filter,loc,use_flat_tod);
|
|
}
|
|
|
|
++cond;
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
bool unit::internal_matches_filter(const vconfig& cfg, const map_location& loc, bool use_flat_tod) const
|
|
{
|
|
config::attribute_value cfg_name = cfg["name"];
|
|
if (!cfg_name.blank() && cfg_name.str() != name_) {
|
|
return false;
|
|
}
|
|
|
|
const config::attribute_value cfg_id = cfg["id"];
|
|
if (!cfg_id.blank()) {
|
|
const std::string& id = cfg_id;
|
|
const std::string& this_id = this->id();
|
|
|
|
if (id == this_id) {
|
|
}
|
|
else if ( id.find(',') == std::string::npos ){
|
|
return false;
|
|
}
|
|
else {
|
|
const std::vector<std::string>& ids = utils::split(id);
|
|
if (std::find(ids.begin(), ids.end(), this_id) == ids.end()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allow 'speaker' as an alternative to id, since people use it so often
|
|
config::attribute_value cfg_speaker = cfg["speaker"];
|
|
if (!cfg_speaker.blank() && cfg_speaker.str() != id()) {
|
|
return false;
|
|
}
|
|
|
|
if(cfg.has_child("filter_location")) {
|
|
assert(resources::gameboard != NULL);
|
|
assert(resources::teams != NULL);
|
|
assert(resources::tod_manager != NULL);
|
|
assert(resources::units != NULL);
|
|
const vconfig& t_cfg = cfg.child("filter_location");
|
|
terrain_filter t_filter(t_cfg, *resources::units, use_flat_tod);
|
|
if(!t_filter.match(loc)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const vconfig& filter_side = cfg.child("filter_side");
|
|
if(!filter_side.null()) {
|
|
side_filter s_filter(filter_side);
|
|
if(!s_filter.match(this->side()))
|
|
return false;
|
|
}
|
|
|
|
// Also allow filtering on location ranges outside of the location filter
|
|
config::attribute_value cfg_x = cfg["x"];
|
|
config::attribute_value cfg_y = cfg["y"];
|
|
if (!cfg_x.blank() || !cfg_y.blank()){
|
|
if(cfg_x == "recall" && cfg_y == "recall") {
|
|
//locations on the map are considered to not be on a recall list
|
|
if ((!resources::gameboard && loc.valid()) ||
|
|
(resources::gameboard && resources::gameboard->map().on_board(loc)))
|
|
{
|
|
return false;
|
|
}
|
|
} else if(cfg_x.empty() && cfg_y.empty()) {
|
|
return false;
|
|
} else if(!loc.matches_range(cfg_x, cfg_y)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The type could be a comma separated list of types
|
|
config::attribute_value cfg_type = cfg["type"];
|
|
if (!cfg_type.blank())
|
|
{
|
|
const std::string type_ids = cfg_type.str();
|
|
const std::string& this_type = type_id();
|
|
|
|
// We only do the full CSV search if we find a comma in there,
|
|
// and if the subsequence is found within the main sequence.
|
|
// This is because doing the full CSV split is expensive.
|
|
if ( type_ids == this_type ) {
|
|
// pass
|
|
} else if ( type_ids.find(',') != std::string::npos &&
|
|
type_ids.find(this_type) != std::string::npos ) {
|
|
const std::vector<std::string>& vals = utils::split(type_ids);
|
|
|
|
if(std::find(vals.begin(),vals.end(),this_type) == vals.end()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The variation_type could be a comma separated list of types
|
|
config::attribute_value cfg_variation_type = cfg["variation"];
|
|
if (!cfg_variation_type.blank())
|
|
{
|
|
const std::string type_ids = cfg_variation_type.str();
|
|
const std::string& this_type = variation_;
|
|
|
|
// We only do the full CSV search if we find a comma in there,
|
|
// and if the subsequence is found within the main sequence.
|
|
// This is because doing the full CSV split is expensive.
|
|
if ( type_ids == this_type ) {
|
|
// pass
|
|
} else if ( type_ids.find(',') != std::string::npos &&
|
|
type_ids.find(this_type) != std::string::npos ) {
|
|
const std::vector<std::string>& vals = utils::split(type_ids);
|
|
|
|
if(std::find(vals.begin(),vals.end(),this_type) == vals.end()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The has_variation_type could be a comma separated list of types
|
|
config::attribute_value cfg_has_variation_type = cfg["has_variation"];
|
|
if (!cfg_has_variation_type.blank())
|
|
{
|
|
const std::string& var_ids = cfg_has_variation_type.str();
|
|
const std::string& this_var = variation_;
|
|
|
|
if ( var_ids == this_var ) {
|
|
// pass
|
|
} else {
|
|
|
|
bool match = false;
|
|
const std::vector<std::string>& variation_types = utils::split(var_ids);
|
|
// If this unit is a variation itself then search in the base unit's variations.
|
|
const unit_type* const type = this_var.empty() ? type_ : unit_types.find(type_->base_id());
|
|
assert(type);
|
|
|
|
BOOST_FOREACH(const std::string& variation_id, variation_types) {
|
|
if (type->has_variation(variation_id)) {
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!match) return false;
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_ability = cfg["ability"];
|
|
if (!cfg_ability.blank())
|
|
{
|
|
std::string ability = cfg_ability;
|
|
if(has_ability_by_id(ability)) {
|
|
// pass
|
|
} else if ( ability.find(',') != std::string::npos ) {
|
|
const std::vector<std::string>& vals = utils::split(ability);
|
|
bool has_ability = false;
|
|
for(std::vector<std::string>::const_iterator this_ability = vals.begin(); this_ability != vals.end(); ++this_ability) {
|
|
if(has_ability_by_id(*this_ability)) {
|
|
has_ability = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!has_ability) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_race = cfg["race"];
|
|
if (!cfg_race.blank()) {
|
|
std::string race = cfg_race;
|
|
|
|
if(race != race_->id()) {
|
|
const std::vector<std::string>& vals = utils::split(race);
|
|
if(std::find(vals.begin(), vals.end(), race_->id()) == vals.end()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_gender = cfg["gender"];
|
|
if (!cfg_gender.blank() && string_gender(cfg_gender) != gender()) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_side = cfg["side"];
|
|
if (!cfg_side.blank() && cfg_side.to_int() != side()) {
|
|
std::string side = cfg_side;
|
|
if ( side.find(',') == std::string::npos ) {
|
|
return false;
|
|
}
|
|
std::vector<std::string> vals = utils::split(side);
|
|
if (std::find(vals.begin(), vals.end(), str_cast(side_)) == vals.end()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_has_weapon = cfg["has_weapon"];
|
|
if (!cfg_has_weapon.blank()) {
|
|
std::string weapon = cfg_has_weapon;
|
|
bool has_weapon = false;
|
|
const std::vector<attack_type>& attacks = this->attacks();
|
|
for(std::vector<attack_type>::const_iterator i = attacks.begin();
|
|
i != attacks.end(); ++i) {
|
|
if(i->id() == weapon) {
|
|
has_weapon = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!has_weapon) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_role = cfg["role"];
|
|
if (!cfg_role.blank() && cfg_role.str() != role_) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_ai_special = cfg["ai_special"];
|
|
if (!cfg_ai_special.blank() && ((cfg_ai_special.str() == "guardian") != get_state(STATE_GUARDIAN))) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_canrecruit = cfg["canrecruit"];
|
|
if (!cfg_canrecruit.blank() && cfg_canrecruit.to_bool() != can_recruit()) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_recall_cost = cfg["recall_cost"];
|
|
if (!cfg_recall_cost.blank() && cfg_recall_cost.to_int(-1) != recall_cost_) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_level = cfg["level"];
|
|
if (!cfg_level.blank() && cfg_level.to_int(-1) != level_) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_defense = cfg["defense"];
|
|
if (!cfg_defense.blank() && cfg_defense.to_int(-1) != defense_modifier(resources::gameboard->map().get_terrain(loc))) {
|
|
return false;
|
|
}
|
|
|
|
config::attribute_value cfg_movement = cfg["movement_cost"];
|
|
if (!cfg_movement.blank() && cfg_movement.to_int(-1) != movement_cost(resources::gameboard->map().get_terrain(loc))) {
|
|
return false;
|
|
}
|
|
|
|
// Now start with the new WML based comparison.
|
|
// If a key is in the unit and in the filter, they should match
|
|
// filter only => not for us
|
|
// unit only => not filtered
|
|
const vconfig::child_list& wmlcfgs = cfg.get_children("filter_wml");
|
|
if (!wmlcfgs.empty()) {
|
|
config unit_cfg;
|
|
for (unsigned i = 0; i < wmlcfgs.size(); ++i)
|
|
{
|
|
config fwml = wmlcfgs[i].get_parsed_config();
|
|
/* Check if the filter only cares about variables.
|
|
If so, no need to serialize the whole unit. */
|
|
config::const_attr_itors ai = fwml.attribute_range();
|
|
config::all_children_itors ci = fwml.all_children_range();
|
|
if (std::distance(ai.first, ai.second) == 0 &&
|
|
std::distance(ci.first, ci.second) == 1 &&
|
|
ci.first->key == "variables") {
|
|
if (!variables_.matches(ci.first->cfg))
|
|
return false;
|
|
} else {
|
|
if (unit_cfg.empty())
|
|
write(unit_cfg);
|
|
if (!unit_cfg.matches(fwml))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cfg.has_child("filter_vision")) {
|
|
const vconfig::child_list& vis_filt = cfg.get_children("filter_vision");
|
|
vconfig::child_list::const_iterator i, i_end = vis_filt.end();
|
|
for (i = vis_filt.begin(); i != i_end; ++i) {
|
|
bool visible = (*i)["visible"].to_bool(true);
|
|
std::set<int> viewers;
|
|
// Use standard side filter
|
|
side_filter ssf(*i);
|
|
std::vector<int> sides = ssf.get_teams();
|
|
viewers.insert(sides.begin(), sides.end());
|
|
if (viewers.empty()) {
|
|
return false;
|
|
}
|
|
std::set<int>::const_iterator viewer, viewer_end = viewers.end();
|
|
for (viewer = viewers.begin(); viewer != viewer_end; ++viewer) {
|
|
bool fogged = teams_manager::get_teams()[*viewer - 1].fogged(loc);
|
|
bool hiding = this->invisible(loc/*, false(?) */);
|
|
bool unit_hidden = fogged || hiding;
|
|
if (visible == unit_hidden) return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cfg.has_child("filter_adjacent")) {
|
|
assert(resources::units && resources::gameboard);
|
|
const unit_map& units = *resources::units;
|
|
map_location adjacent[6];
|
|
get_adjacent_tiles(loc, adjacent);
|
|
vconfig::child_list::const_iterator i, i_end;
|
|
const vconfig::child_list& adj_filt = cfg.get_children("filter_adjacent");
|
|
for (i = adj_filt.begin(), i_end = adj_filt.end(); i != i_end; ++i) {
|
|
int match_count=0;
|
|
config::attribute_value i_adjacent = (*i)["adjacent"];
|
|
std::vector<map_location::DIRECTION> dirs = !i_adjacent.blank() ?
|
|
map_location::parse_directions(i_adjacent) : map_location::default_dirs();
|
|
std::vector<map_location::DIRECTION>::const_iterator j, j_end = dirs.end();
|
|
for (j = dirs.begin(); j != j_end; ++j) {
|
|
unit_map::const_iterator unit_itor = units.find(adjacent[*j]);
|
|
if (unit_itor == units.end()
|
|
|| !unit_itor->matches_filter(*i, unit_itor->get_location(), use_flat_tod)) {
|
|
continue;
|
|
}
|
|
config::attribute_value i_is_enemy = (*i)["is_enemy"];
|
|
if (i_is_enemy.blank() || i_is_enemy.to_bool() ==
|
|
teams_manager::get_teams()[this->side() - 1].is_enemy(unit_itor->side())) {
|
|
++match_count;
|
|
}
|
|
}
|
|
static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-6");
|
|
config::attribute_value i_count = (*i)["count"];
|
|
std::vector<std::pair<int,int> > counts = !i_count.blank()
|
|
? utils::parse_ranges(i_count) : default_counts;
|
|
if(!in_ranges(match_count, counts)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_find_in = cfg["find_in"];
|
|
if (!cfg_find_in.blank()) {
|
|
// Allow filtering by searching a stored variable of units
|
|
variable_info vi(cfg_find_in, false, variable_info::TYPE_CONTAINER);
|
|
if(!vi.is_valid) return false;
|
|
if(vi.explicit_index) {
|
|
config::const_child_iterator i = vi.vars->child_range(vi.key).first;
|
|
std::advance(i, vi.index);
|
|
if ((*i)["id"] != id_) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!vi.vars->find_child(vi.key, "id", id_))
|
|
return false;
|
|
}
|
|
}
|
|
config::attribute_value cfg_formula = cfg["formula"];
|
|
if (!cfg_formula.blank()) {
|
|
if (!formula_man_->matches_filter(cfg_formula, loc, *this)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
config::attribute_value cfg_lua_function = cfg["lua_function"];
|
|
if (!cfg_lua_function.blank()) {
|
|
bool b = resources::lua_kernel->run_filter(cfg_lua_function.str().c_str(), *this);
|
|
if (!b) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void unit::write(config& cfg) const
|
|
{
|
|
cfg.append(cfg_);
|
|
movement_type_.write(cfg);
|
|
|
|
if ( cfg["description"] == type().unit_description() ) {
|
|
cfg.remove_attribute("description");
|
|
}
|
|
|
|
cfg["hitpoints"] = hit_points_;
|
|
cfg["max_hitpoints"] = max_hit_points_;
|
|
|
|
cfg["experience"] = experience_;
|
|
cfg["max_experience"] = max_experience_;
|
|
cfg["recall_cost"] = recall_cost_;
|
|
|
|
cfg["side"] = side_;
|
|
|
|
cfg["type"] = type_id();
|
|
if ( type_id() != type().base_id() )
|
|
cfg["parent_type"] = type().base_id();
|
|
|
|
//support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
|
|
|
|
formula_man_->write(cfg);
|
|
|
|
|
|
cfg["gender"] = gender_string(gender_);
|
|
cfg["variation"] = variation_;
|
|
cfg["role"] = role_;
|
|
|
|
config status_flags;
|
|
std::map<std::string,std::string> all_states = get_states();
|
|
for(std::map<std::string,std::string>::const_iterator st = all_states.begin(); st != all_states.end(); ++st) {
|
|
status_flags[st->first] = st->second;
|
|
}
|
|
|
|
cfg.clear_children("variables");
|
|
cfg.add_child("variables",variables_);
|
|
cfg.clear_children("events");
|
|
cfg.append(events_);
|
|
cfg.clear_children("filter_recall");
|
|
cfg.add_child("filter_recall", filter_recall_);
|
|
cfg.clear_children("status");
|
|
cfg.add_child("status",status_flags);
|
|
|
|
cfg["overlays"] = utils::join(overlays_);
|
|
|
|
cfg["name"] = name_;
|
|
cfg["id"] = id_;
|
|
cfg["underlying_id"] = str_cast(underlying_id_);
|
|
|
|
if(can_recruit())
|
|
cfg["canrecruit"] = true;
|
|
|
|
cfg["extra_recruit"] = utils::join(recruit_list_);
|
|
|
|
cfg["facing"] = map_location::write_direction(facing_);
|
|
|
|
cfg["goto_x"] = goto_.x + 1;
|
|
cfg["goto_y"] = goto_.y + 1;
|
|
|
|
cfg["moves"] = movement_;
|
|
cfg["max_moves"] = max_movement_;
|
|
cfg["vision"] = vision_;
|
|
cfg["jamming"] = jamming_;
|
|
|
|
cfg["resting"] = resting_;
|
|
|
|
cfg["advances_to"] = utils::join(advances_to_);
|
|
|
|
cfg["race"] = race_->id();
|
|
cfg["language_name"] = type_name_;
|
|
cfg["undead_variation"] = undead_variation_;
|
|
cfg["level"] = level_;
|
|
cfg["alignment"] = lexical_cast<std::string> (alignment_);
|
|
cfg["flag_rgb"] = flag_rgb_;
|
|
cfg["unrenamable"] = unrenamable_;
|
|
cfg["alpha"] = str_cast(alpha_);
|
|
|
|
cfg["attacks_left"] = attacks_left_;
|
|
cfg["max_attacks"] = max_attacks_;
|
|
cfg["zoc"] = emit_zoc_;
|
|
cfg.clear_children("attack");
|
|
for(std::vector<attack_type>::const_iterator i = attacks_.begin(); i != attacks_.end(); ++i) {
|
|
cfg.add_child("attack",i->get_cfg());
|
|
}
|
|
cfg["cost"] = unit_value_;
|
|
cfg.clear_children("modifications");
|
|
cfg.add_child("modifications",modifications_);
|
|
|
|
}
|
|
|
|
const surface unit::still_image(bool scaled) const
|
|
{
|
|
image::locator image_loc;
|
|
|
|
#ifdef LOW_MEM
|
|
image_loc = image::locator(absolute_image());
|
|
#else
|
|
std::string mods=image_mods();
|
|
if(!mods.empty()){
|
|
image_loc = image::locator(absolute_image(),mods);
|
|
} else {
|
|
image_loc = image::locator(absolute_image());
|
|
}
|
|
#endif
|
|
|
|
surface unit_image(image::get_image(image_loc, scaled ? image::SCALED_TO_ZOOM : image::UNSCALED));
|
|
return unit_image;
|
|
}
|
|
|
|
void unit::set_facing(map_location::DIRECTION dir) const {
|
|
if(dir != map_location::NDIRECTIONS) {
|
|
facing_ = dir;
|
|
}
|
|
// Else look at yourself (not available so continue to face the same direction)
|
|
}
|
|
|
|
int unit::upkeep() const
|
|
{
|
|
// Leaders do not incur upkeep.
|
|
if(can_recruit()) {
|
|
return 0;
|
|
}
|
|
if(cfg_["upkeep"] == "full") {
|
|
return level();
|
|
}
|
|
if(cfg_["upkeep"] == "loyal") {
|
|
return 0;
|
|
}
|
|
if(cfg_["upkeep"] == "free") {
|
|
return 0;
|
|
}
|
|
return cfg_["upkeep"];
|
|
}
|
|
|
|
bool unit::loyal() const
|
|
{
|
|
return cfg_["upkeep"] == "loyal" || cfg_["upkeep"] == "free";
|
|
}
|
|
|
|
int unit::defense_modifier(const t_translation::t_terrain & terrain) const
|
|
{
|
|
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.
|
|
unit_ability_list defense_abilities = get_abilities("defense");
|
|
if (!defense_abilities.empty()) {
|
|
unit_abilities::effect defense_effect(defense_abilities, def, false);
|
|
def = defense_effect.get_composite_value();
|
|
}
|
|
#endif
|
|
return def;
|
|
}
|
|
|
|
bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std::string& damage_name, int res) const
|
|
{
|
|
if(!(cfg["active_on"]=="" || (attacker && cfg["active_on"]=="offense") || (!attacker && cfg["active_on"]=="defense"))) {
|
|
return false;
|
|
}
|
|
const std::string& apply_to = cfg["apply_to"];
|
|
if(!apply_to.empty()) {
|
|
if(damage_name != apply_to) {
|
|
if ( apply_to.find(',') != std::string::npos &&
|
|
apply_to.find(damage_name) != std::string::npos ) {
|
|
const std::vector<std::string>& vals = utils::split(apply_to);
|
|
if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!unit_abilities::filter_base_matches(cfg, res)) return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
int unit::resistance_against(const std::string& damage_name,bool attacker,const map_location& loc) const
|
|
{
|
|
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();) {
|
|
if(!resistance_filter_matches(*i->first, attacker, damage_name, 100-res)) {
|
|
i = resistance_abilities.erase(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
if(!resistance_abilities.empty()) {
|
|
unit_abilities::effect resist_effect(resistance_abilities, 100-res, false);
|
|
|
|
res = 100 - std::min<int>(resist_effect.get_composite_value(),
|
|
resistance_abilities.highest("max_value").first);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::map<std::string,std::string> unit::advancement_icons() const
|
|
{
|
|
std::map<std::string,std::string> temp;
|
|
if (!can_advance())
|
|
return temp;
|
|
|
|
if (!advances_to_.empty())
|
|
{
|
|
std::ostringstream tooltip;
|
|
const std::string &image = game_config::images::level;
|
|
BOOST_FOREACH(const std::string &s, advances_to())
|
|
{
|
|
if (!s.empty())
|
|
tooltip << s << '\n';
|
|
}
|
|
temp[image] = tooltip.str();
|
|
}
|
|
|
|
BOOST_FOREACH(const config &adv, get_modification_advances())
|
|
{
|
|
const std::string &image = adv["image"];
|
|
if (image.empty()) continue;
|
|
std::ostringstream tooltip;
|
|
tooltip << temp[image];
|
|
const std::string &tt = adv["description"];
|
|
if (!tt.empty())
|
|
tooltip << tt << '\n';
|
|
temp[image] = tooltip.str();
|
|
}
|
|
return(temp);
|
|
}
|
|
std::vector<std::pair<std::string,std::string> > unit::amla_icons() const
|
|
{
|
|
std::vector<std::pair<std::string,std::string> > temp;
|
|
std::pair<std::string,std::string> icon; // <image,tooltip>
|
|
|
|
BOOST_FOREACH(const config &adv, get_modification_advances())
|
|
{
|
|
icon.first = adv["icon"].str();
|
|
icon.second = adv["description"].str();
|
|
|
|
for (unsigned j = 0, j_count = modification_count("advance", adv["id"]);
|
|
j < j_count; ++j)
|
|
{
|
|
temp.push_back(icon);
|
|
}
|
|
}
|
|
return(temp);
|
|
}
|
|
|
|
std::vector<config> unit::get_modification_advances() const
|
|
{
|
|
std::vector<config> res;
|
|
BOOST_FOREACH(const config &adv, modification_advancements())
|
|
{
|
|
if (adv["strict_amla"].to_bool() && !advances_to_.empty())
|
|
continue;
|
|
if (modification_count("advance", adv["id"]) >= unsigned(adv["max_times"].to_int(1)))
|
|
continue;
|
|
|
|
std::vector<std::string> temp = utils::split(adv["require_amla"]);
|
|
if (temp.empty()) {
|
|
res.push_back(adv);
|
|
continue;
|
|
}
|
|
|
|
std::sort(temp.begin(), temp.end());
|
|
std::vector<std::string> uniq;
|
|
std::unique_copy(temp.begin(), temp.end(), std::back_inserter(uniq));
|
|
|
|
bool requirements_done = true;
|
|
BOOST_FOREACH(const std::string &s, uniq)
|
|
{
|
|
int required_num = std::count(temp.begin(), temp.end(), s);
|
|
int mod_num = modification_count("advance", s);
|
|
if (required_num > mod_num) {
|
|
requirements_done = false;
|
|
break;
|
|
}
|
|
}
|
|
if (requirements_done)
|
|
res.push_back(adv);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
|
|
{
|
|
size_t res = 0;
|
|
BOOST_FOREACH(const config &item, modifications_.child_range(mod_type)) {
|
|
if (item["id"] == id) {
|
|
++res;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
|
|
{
|
|
bool generate_description = true;
|
|
|
|
//some trait activate specific flags
|
|
if ( mod_type == "trait" ) {
|
|
const std::string& id = mod["id"];
|
|
is_fearless_ = is_fearless_ || id == "fearless";
|
|
is_healthy_ = is_healthy_ || id == "healthy";
|
|
if (!mod["generate_description"].empty()) {
|
|
generate_description = mod["generate_description"].to_bool();
|
|
}
|
|
}
|
|
|
|
config *new_child = NULL;
|
|
if(no_add == false) {
|
|
new_child = &modifications_.add_child(mod_type, mod);
|
|
}
|
|
bool set_poisoned = false; // Tracks if the poisoned state was set after the type or variation was changed.
|
|
config last_effect;
|
|
std::vector<t_string> effects_description;
|
|
BOOST_FOREACH(const config &effect, mod.child_range("effect"))
|
|
{
|
|
// Apply SUF.
|
|
if (const config &afilter = effect.child("filter"))
|
|
if (!matches_filter(vconfig(afilter), loc_)) continue;
|
|
|
|
const std::string &apply_to = effect["apply_to"];
|
|
const std::string &apply_times = effect["times"];
|
|
int times = 1;
|
|
t_string description;
|
|
|
|
if (apply_times == "per level")
|
|
times = level_;
|
|
if (times) {
|
|
while (times > 0) {
|
|
times --;
|
|
|
|
// Apply unit type/variation changes last to avoid double applying effects on advance.
|
|
if ((apply_to == "variation" || apply_to == "type") && no_add == false) {
|
|
set_poisoned = false;
|
|
last_effect = effect;
|
|
} else if(apply_to == "profile") {
|
|
if (const config::attribute_value *v = effect.get("portrait")) {
|
|
std::string big = *v, small = effect["small_portrait"];
|
|
adjust_profile(small, big, "");
|
|
cfg_["profile"] = big;
|
|
cfg_["small_profile"] = small;
|
|
}
|
|
if (const config::attribute_value *v = effect.get("description"))
|
|
cfg_["description"] = *v;
|
|
//help::unit_topic_generator(*this, (**i.first)["help_topic"]);
|
|
} else if(apply_to == "new_attack") {
|
|
attacks_.push_back(attack_type(effect));
|
|
} else if(apply_to == "remove_attacks") {
|
|
std::vector<attack_type>::iterator a = attacks_.begin();
|
|
while(a != attacks_.end()) {
|
|
if(a->matches_filter(effect)) {
|
|
a = attacks_.erase(a);
|
|
continue;
|
|
}
|
|
++a;
|
|
}
|
|
} else if(apply_to == "attack") {
|
|
|
|
bool first_attack = true;
|
|
|
|
std::string attack_names;
|
|
std::string desc;
|
|
for(std::vector<attack_type>::iterator a = attacks_.begin();
|
|
a != attacks_.end(); ++a) {
|
|
bool affected = a->apply_modification(effect, &desc);
|
|
if(affected && desc != "") {
|
|
if(first_attack) {
|
|
first_attack = false;
|
|
} else {
|
|
if (!times)
|
|
attack_names += t_string(N_(" and "), "wesnoth");
|
|
}
|
|
|
|
if (!times)
|
|
attack_names += t_string(a->name(), "wesnoth");
|
|
}
|
|
}
|
|
if (attack_names.empty() == false) {
|
|
utils::string_map symbols;
|
|
symbols["attack_list"] = attack_names;
|
|
symbols["effect_description"] = desc;
|
|
description += vgettext("$attack_list|: $effect_description", symbols);
|
|
}
|
|
} else if(apply_to == "hitpoints") {
|
|
LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_ << "\n";
|
|
const std::string &increase_hp = effect["increase"];
|
|
const std::string &increase_total = effect["increase_total"];
|
|
const std::string &set_hp = effect["set"];
|
|
const std::string &set_total = effect["set_total"];
|
|
|
|
// If the hitpoints are allowed to end up greater than max hitpoints
|
|
const bool violate_max = effect["violate_maximum"].to_bool();
|
|
|
|
if(set_hp.empty() == false) {
|
|
if(set_hp[set_hp.size()-1] == '%') {
|
|
hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
|
|
} else {
|
|
hit_points_ = lexical_cast_default<int>(set_hp);
|
|
}
|
|
}
|
|
if(set_total.empty() == false) {
|
|
if(set_total[set_total.size()-1] == '%') {
|
|
max_hit_points_ = lexical_cast_default<int>(set_total)*max_hit_points_/100;
|
|
} else {
|
|
max_hit_points_ = lexical_cast_default<int>(set_total);
|
|
}
|
|
}
|
|
|
|
if(increase_total.empty() == false) {
|
|
if (!times)
|
|
description += utils::print_modifier(increase_total) + " " +
|
|
t_string(N_("HP"), "wesnoth");
|
|
|
|
// A percentage on the end means increase by that many percent
|
|
max_hit_points_ = utils::apply_modifier(max_hit_points_, increase_total);
|
|
}
|
|
|
|
if(max_hit_points_ < 1)
|
|
max_hit_points_ = 1;
|
|
|
|
if (effect["heal_full"].to_bool()) {
|
|
heal_all();
|
|
}
|
|
|
|
if(increase_hp.empty() == false) {
|
|
hit_points_ = utils::apply_modifier(hit_points_, increase_hp);
|
|
}
|
|
|
|
LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_ << "\n";
|
|
if(hit_points_ > max_hit_points_ && !violate_max) {
|
|
LOG_UT << "resetting hp to max\n";
|
|
hit_points_ = max_hit_points_;
|
|
}
|
|
|
|
if(hit_points_ < 1)
|
|
hit_points_ = 1;
|
|
} else if(apply_to == "movement") {
|
|
const std::string &increase = effect["increase"];
|
|
|
|
if(increase.empty() == false) {
|
|
if (!times)
|
|
description += utils::print_modifier(increase) + " " +
|
|
t_string(N_("moves"), "wesnoth");
|
|
|
|
max_movement_ = utils::apply_modifier(max_movement_, increase, 1);
|
|
}
|
|
|
|
max_movement_ = effect["set"].to_int(max_movement_);
|
|
|
|
if(movement_ > max_movement_)
|
|
movement_ = max_movement_;
|
|
} else if(apply_to == "experience") {
|
|
const std::string &increase = effect["increase"];
|
|
const std::string &set = effect["set"];
|
|
|
|
if(set.empty() == false) {
|
|
if(set[set.size()-1] == '%') {
|
|
experience_ = lexical_cast_default<int>(set)*max_experience_/100;
|
|
} else {
|
|
experience_ = lexical_cast_default<int>(set);
|
|
}
|
|
}
|
|
|
|
if(increase.empty() == false) {
|
|
experience_ = utils::apply_modifier(experience_, increase, 1);
|
|
}
|
|
} else if(apply_to == "max_experience") {
|
|
const std::string &increase = effect["increase"];
|
|
|
|
if(increase.empty() == false) {
|
|
if (!times)
|
|
description += utils::print_modifier(increase) + " " +
|
|
t_string(N_("XP to advance"), "wesnoth");
|
|
|
|
max_experience_ = utils::apply_modifier(max_experience_, increase, 1);
|
|
}
|
|
|
|
} else if(apply_to == "loyal") {
|
|
cfg_["upkeep"] = "loyal";
|
|
} else if(apply_to == "status") {
|
|
const std::string &add = effect["add"];
|
|
const std::string &remove = effect["remove"];
|
|
|
|
if(add.empty() == false) {
|
|
set_state(add, true);
|
|
set_poisoned = set_poisoned || add == "poisoned";
|
|
}
|
|
|
|
if(remove.empty() == false) {
|
|
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") {
|
|
if (const config &ap = effect.child("movement_costs")) {
|
|
movement_type_.get_movement().merge(ap, effect["replace"].to_bool());
|
|
}
|
|
} else if (apply_to == "vision_costs") {
|
|
if (const config &ap = effect.child("vision_costs")) {
|
|
movement_type_.get_vision().merge(ap, effect["replace"].to_bool());
|
|
}
|
|
} else if (apply_to == "jamming_costs") {
|
|
if (const config &ap = effect.child("jamming_costs")) {
|
|
movement_type_.get_jamming().merge(ap, effect["replace"].to_bool());
|
|
}
|
|
} else if (apply_to == "defense") {
|
|
if (const config &ap = effect.child("defense")) {
|
|
movement_type_.get_defense().merge(ap, effect["replace"].to_bool());
|
|
}
|
|
} else if (apply_to == "resistance") {
|
|
if (const config &ap = effect.child("resistance")) {
|
|
movement_type_.get_resistances().merge(ap, effect["replace"].to_bool());
|
|
}
|
|
} else if (apply_to == "zoc") {
|
|
if (const config::attribute_value *v = effect.get("value")) {
|
|
emit_zoc_ = v->to_bool();
|
|
}
|
|
} else if (apply_to == "new_ability") {
|
|
config &ab = cfg_.child_or_add("abilities");
|
|
if (const config &ab_effect = effect.child("abilities")) {
|
|
config to_append;
|
|
BOOST_FOREACH(const config::any_child &ab, ab_effect.all_children_range()) {
|
|
if(!has_ability_by_id(ab.cfg["id"])) {
|
|
to_append.add_child(ab.key, ab.cfg);
|
|
}
|
|
}
|
|
ab.append(to_append);
|
|
}
|
|
} else if (apply_to == "remove_ability") {
|
|
if (const config &ab_effect = effect.child("abilities")) {
|
|
BOOST_FOREACH(const config::any_child &ab, ab_effect.all_children_range()) {
|
|
remove_ability_by_id(ab.cfg["id"]);
|
|
}
|
|
}
|
|
} else if (apply_to == "image_mod") {
|
|
LOG_UT << "applying image_mod \n";
|
|
std::string mod = effect["replace"];
|
|
if (!mod.empty()){
|
|
image_mods_ = mod;
|
|
}
|
|
LOG_UT << "applying image_mod \n";
|
|
mod = effect["add"].str();
|
|
if (!mod.empty()){
|
|
if(!image_mods_.empty()) {
|
|
image_mods_ += '~';
|
|
}
|
|
|
|
image_mods_ += mod;
|
|
}
|
|
|
|
game_config::add_color_info(effect);
|
|
LOG_UT << "applying image_mod \n";
|
|
} else if (apply_to == "new_animation") {
|
|
anim_comp_->apply_new_animation_effect(effect);
|
|
} else if (apply_to == "ellipse") {
|
|
cfg_["ellipse"] = effect["ellipse"];
|
|
|
|
} else if (apply_to == "halo") {
|
|
anim_comp_->clear_haloes();
|
|
cfg_["halo"] = effect["halo"];
|
|
|
|
} else if (apply_to == "overlay") {
|
|
const std::string &add = effect["add"];
|
|
const std::string &replace = effect["replace"];
|
|
|
|
if (!add.empty()) {
|
|
std::vector<std::string> temp_overlays = utils::parenthetical_split(add, ',');
|
|
std::vector<std::string>::iterator it;
|
|
for (it=temp_overlays.begin();it<temp_overlays.end();++it) {
|
|
overlays_.push_back( *it );
|
|
}
|
|
}
|
|
else if (!replace.empty()) {
|
|
overlays_ = utils::parenthetical_split(replace, ',');
|
|
}
|
|
}
|
|
} // end while
|
|
} else { // for times = per level & level = 0 we still need to rebuild the descriptions
|
|
if(apply_to == "attack") {
|
|
|
|
bool first_attack = true;
|
|
|
|
for(std::vector<attack_type>::iterator a = attacks_.begin();
|
|
a != attacks_.end(); ++a) {
|
|
std::string desc;
|
|
bool affected = a->describe_modification(effect, &desc);
|
|
if(affected && desc != "") {
|
|
if(first_attack) {
|
|
first_attack = false;
|
|
} else {
|
|
description += t_string(N_(" and "), "wesnoth");
|
|
}
|
|
|
|
description += t_string(a->name(), "wesnoth") + ": " + desc;
|
|
}
|
|
}
|
|
} else if(apply_to == "hitpoints") {
|
|
const std::string &increase_total = effect["increase_total"];
|
|
|
|
if(increase_total.empty() == false) {
|
|
description += utils::print_modifier(increase_total) + " " +
|
|
t_string(N_("HP"), "wesnoth");
|
|
}
|
|
} else if(apply_to == "movement") {
|
|
const std::string &increase = effect["increase"];
|
|
|
|
if(increase.empty() == false) {
|
|
description += utils::print_modifier(increase) + t_string(N_(" move"), "wesnoth");
|
|
}
|
|
} else if(apply_to == "max_experience") {
|
|
const std::string &increase = effect["increase"];
|
|
|
|
if(increase.empty() == false) {
|
|
description += utils::print_modifier(increase) + " " +
|
|
t_string(N_("XP to advance"), "wesnoth");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (apply_times == "per level" && !times) {
|
|
utils::string_map symbols;
|
|
symbols["effect_description"] = description;
|
|
description = vgettext("$effect_description per level", symbols);
|
|
}
|
|
if(!description.empty())
|
|
effects_description.push_back(description);
|
|
|
|
}
|
|
// Apply variations -- only apply if we are adding this for the first time.
|
|
if (!last_effect.empty() && no_add == false) {
|
|
if ((last_effect)["apply_to"] == "variation") {
|
|
variation_ = last_effect["name"].str();
|
|
const unit_type * base_type = unit_types.find(type().base_id());
|
|
assert(base_type != NULL);
|
|
advance_to(*base_type);
|
|
} else if ((last_effect)["apply_to"] == "type") {
|
|
config::attribute_value &prev_type = (*new_child)["prev_type"];
|
|
if (prev_type.blank()) prev_type = type().base_id();
|
|
const std::string& new_type_id = last_effect["name"];
|
|
const unit_type* new_type = unit_types.find(new_type_id);
|
|
if ( new_type ) {
|
|
const bool heal_full = last_effect["heal_full"].to_bool(false);
|
|
advance_to(*new_type);
|
|
preferences::encountered_units().insert(new_type_id);
|
|
if( heal_full ) {
|
|
heal_all();
|
|
}
|
|
} else {
|
|
WRN_UT << "unknown type= in [effect]apply_to=type, ignoring" << std::endl;
|
|
}
|
|
}
|
|
if ( set_poisoned )
|
|
// An effect explicitly set the poisoned state, and this
|
|
// should override the unit being immune to poison.
|
|
set_state(STATE_POISONED, true);
|
|
}
|
|
|
|
t_string description;
|
|
|
|
const t_string& mod_description = mod["description"];
|
|
if (!mod_description.empty()) {
|
|
description = mod_description + " ";
|
|
}
|
|
|
|
// Punctuation should be translatable: not all languages use Latin punctuation.
|
|
// (However, there maybe is a better way to do it)
|
|
if(effects_description.empty() == false && generate_description == true) {
|
|
for(std::vector<t_string>::const_iterator i = effects_description.begin();
|
|
i != effects_description.end(); ++i) {
|
|
description += *i;
|
|
if(i+1 != effects_description.end())
|
|
description += t_string(N_(" and "), "wesnoth");
|
|
}
|
|
}
|
|
|
|
// store trait info
|
|
if ( mod_type == "trait" ) {
|
|
add_trait_description(mod, description);
|
|
}
|
|
|
|
//NOTE: if not a trait, description is currently not used
|
|
}
|
|
|
|
void unit::add_trait_description(const config& trait, const t_string& description)
|
|
{
|
|
const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
|
|
t_string const &gender_specific_name = trait[gender_string];
|
|
|
|
// if this is a t_string& instead of a t_string, msvc9 compiled windows binaries
|
|
// choke on the case where both gender_specific_name and trait["name"] are empty.
|
|
const t_string name = gender_specific_name.empty() ?
|
|
trait["name"] : gender_specific_name;
|
|
|
|
if(!name.empty()) {
|
|
trait_names_.push_back(name);
|
|
trait_descriptions_.push_back(description);
|
|
}
|
|
}
|
|
|
|
std::string unit::absolute_image() const {
|
|
return cfg_["image_icon"].empty() ? cfg_["image"] : cfg_["image_icon"];
|
|
}
|
|
|
|
void unit::apply_modifications()
|
|
{
|
|
log_scope("apply mods");
|
|
|
|
for(size_t i = 0; i != NumModificationTypes; ++i) {
|
|
const std::string& mod = ModificationTypes[i];
|
|
BOOST_FOREACH(const config &m, modifications_.child_range(mod)) {
|
|
log_scope("add mod");
|
|
add_modification(ModificationTypes[i], m, true);
|
|
}
|
|
}
|
|
|
|
//apply the experience acceleration last
|
|
int exp_accel = unit_type::experience_accelerator::get_acceleration();
|
|
max_experience_ = std::max<int>(1, (max_experience_ * exp_accel + 50)/100);
|
|
}
|
|
|
|
bool unit::invisible(const map_location& loc, bool see_all) const
|
|
{
|
|
// This is a quick condition to check, and it does not depend on the
|
|
// location (so might as well bypass the location-based cache).
|
|
if ( get_state(STATE_UNCOVERED) )
|
|
return false;
|
|
|
|
// Fetch from cache
|
|
/**
|
|
* @todo FIXME: We use the cache only when using the default see_all=true
|
|
* Maybe add a second cache if the see_all=false become more frequent.
|
|
*/
|
|
if(see_all) {
|
|
std::map<map_location, bool>::const_iterator itor = invisibility_cache_.find(loc);
|
|
if(itor != invisibility_cache_.end()) {
|
|
return itor->second;
|
|
}
|
|
}
|
|
|
|
// Test hidden status
|
|
static const std::string hides("hides");
|
|
bool is_inv = get_ability_bool(hides,loc);
|
|
if(is_inv){
|
|
const std::vector<team>& teams = *resources::teams;
|
|
BOOST_FOREACH(const unit &u, *resources::units)
|
|
{
|
|
const map_location &u_loc = u.get_location();
|
|
if (teams[side_-1].is_enemy(u.side()) && !u.incapacitated() && tiles_adjacent(loc, u_loc)) {
|
|
// Enemy spotted in adjacent tiles, check if we can see him.
|
|
// Watch out to call invisible with see_all=true to avoid infinite recursive calls!
|
|
if(see_all) {
|
|
is_inv = false;
|
|
break;
|
|
} else if (!teams[side_-1].fogged(u_loc)
|
|
&& !u.invisible(u_loc, true)) {
|
|
is_inv = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(see_all) {
|
|
// Add to caches
|
|
if(invisibility_cache_.empty()) {
|
|
units_with_cache.push_back(this);
|
|
}
|
|
invisibility_cache_[loc] = is_inv;
|
|
}
|
|
|
|
return is_inv;
|
|
}
|
|
|
|
|
|
bool unit::is_visible_to_team(team const& team, gamemap const& map, bool const see_all) const
|
|
{
|
|
map_location const& loc = get_location();
|
|
if (!map.on_board(loc))
|
|
return false;
|
|
if (see_all)
|
|
return true;
|
|
if (team.is_enemy(side()) && invisible(loc))
|
|
return false;
|
|
if (team.is_enemy(side()) && team.fogged(loc))
|
|
return false;
|
|
if (team.fogged(loc) && !(*resources::teams)[side() - 1].share_view())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void unit::set_underlying_id() {
|
|
if(underlying_id_ == 0){
|
|
underlying_id_ = n_unit::id_manager::instance().next_id();
|
|
}
|
|
if (id_.empty()) {
|
|
std::stringstream ss;
|
|
ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_;
|
|
id_ = ss.str();
|
|
}
|
|
}
|
|
|
|
unit& unit::clone(bool is_temporary)
|
|
{
|
|
if(is_temporary) {
|
|
underlying_id_ = n_unit::id_manager::instance().next_fake_id();
|
|
} else {
|
|
underlying_id_ = n_unit::id_manager::instance().next_id();
|
|
std::string::size_type pos = id_.find_last_of('-');
|
|
if(pos != std::string::npos && pos+1 < id_.size()
|
|
&& id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
|
|
// this appears to be a duplicate of a generic unit, so give it a new id
|
|
WRN_UT << "assigning new id to clone of generic unit " << id_ << std::endl;
|
|
id_.clear();
|
|
set_underlying_id();
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
unit_movement_resetter::unit_movement_resetter(unit &u, bool operate) :
|
|
u_(u), moves_(u.movement_left(true))
|
|
{
|
|
if (operate) {
|
|
u.set_movement(u.total_movement());
|
|
}
|
|
}
|
|
|
|
unit_movement_resetter::~unit_movement_resetter()
|
|
{
|
|
assert(resources::units);
|
|
try {
|
|
|
|
if(!resources::units->has_unit(&u_)) {
|
|
/*
|
|
* It might be valid that the unit is not in the unit map.
|
|
* It might also mean a no longer valid unit will be assigned to.
|
|
*/
|
|
DBG_UT << "The unit to be removed is not in the unit map.\n";
|
|
}
|
|
u_.set_movement(moves_);
|
|
|
|
} catch (...) {}
|
|
}
|
|
|
|
bool unit::matches_id(const std::string& unit_id) const
|
|
{
|
|
return id_ == unit_id;
|
|
}
|
|
|
|
bool find_if_matches_helper(const UnitPtr & ptr, const std::string & unit_id)
|
|
{
|
|
return ptr->matches_id(unit_id);
|
|
}
|
|
|
|
/**
|
|
* Used to find units in vectors by their ID. (Convenience wrapper)
|
|
* @returns what std::find_if() returns.
|
|
*/
|
|
std::vector<UnitPtr >::iterator find_if_matches_id(
|
|
std::vector<UnitPtr > &unit_list, // Not const so we can get a non-const iterator to return.
|
|
const std::string &unit_id)
|
|
{
|
|
return std::find_if(unit_list.begin(), unit_list.end(),
|
|
boost::bind(&find_if_matches_helper, _1, unit_id));
|
|
}
|
|
|
|
/**
|
|
* Used to find units in vectors by their ID. (Convenience wrapper; const version)
|
|
* @returns what std::find_if() returns.
|
|
*/
|
|
std::vector<UnitPtr >::const_iterator find_if_matches_id(
|
|
const std::vector<UnitPtr > &unit_list,
|
|
const std::string &unit_id)
|
|
{
|
|
return std::find_if(unit_list.begin(), unit_list.end(),
|
|
boost::bind(&find_if_matches_helper, _1, unit_id));
|
|
}
|
|
|
|
/**
|
|
* Used to erase units from vectors by their ID. (Convenience wrapper)
|
|
* @returns what std::vector<>::erase() returns.
|
|
*/
|
|
std::vector<UnitPtr >::iterator erase_if_matches_id(
|
|
std::vector<UnitPtr > &unit_list,
|
|
const std::string &unit_id)
|
|
{
|
|
return unit_list.erase(std::remove_if(unit_list.begin(), unit_list.end(),
|
|
boost::bind(&find_if_matches_helper, _1, unit_id)),
|
|
unit_list.end());
|
|
}
|
|
|
|
int side_units(int side)
|
|
{
|
|
int res = 0;
|
|
BOOST_FOREACH(const unit &u, *resources::units) {
|
|
if (u.side() == side) ++res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int side_units_cost(int side)
|
|
{
|
|
int res = 0;
|
|
BOOST_FOREACH(const unit &u, *resources::units) {
|
|
if (u.side() == side) res += u.cost();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int side_upkeep(int side)
|
|
{
|
|
int res = 0;
|
|
BOOST_FOREACH(const unit &u, *resources::units) {
|
|
if (u.side() == side) res += u.upkeep();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
team_data calculate_team_data(const team& tm, int side)
|
|
{
|
|
team_data res;
|
|
res.units = side_units(side);
|
|
res.upkeep = side_upkeep(side);
|
|
res.villages = tm.villages().size();
|
|
res.expenses = std::max<int>(0,res.upkeep - tm.support());
|
|
res.net_income = tm.total_income() - res.expenses;
|
|
res.gold = tm.gold();
|
|
res.teamname = tm.user_team_name();
|
|
return res;
|
|
}
|
|
|
|
std::string unit::TC_image_mods() const{
|
|
std::stringstream modifier;
|
|
if(!flag_rgb_.empty()){
|
|
modifier << "~RC("<< flag_rgb_ << ">" << team::get_side_color_index(side()) << ")";
|
|
}
|
|
return modifier.str();
|
|
}
|
|
std::string unit::image_mods() const{
|
|
std::stringstream modifier;
|
|
if(!image_mods_.empty()){
|
|
modifier << "~" << image_mods_;
|
|
}
|
|
modifier << TC_image_mods();
|
|
return modifier.str();
|
|
}
|
|
|
|
const std::string& unit::effect_image_mods() const{
|
|
return image_mods_;
|
|
}
|
|
|
|
void unit::remove_attacks_ai()
|
|
{
|
|
if (attacks_left_ == max_attacks_) {
|
|
//TODO: add state_not_attacked
|
|
}
|
|
set_attacks(0);
|
|
}
|
|
|
|
|
|
void unit::remove_movement_ai()
|
|
{
|
|
if (movement_left() == total_movement()) {
|
|
set_state(STATE_NOT_MOVED,true);
|
|
}
|
|
set_movement(0, true);
|
|
}
|
|
|
|
|
|
void unit::set_hidden(bool state) const {
|
|
hidden_ = state;
|
|
if(!state) return;
|
|
// We need to get rid of haloes immediately to avoid display glitches
|
|
anim_comp_->clear_haloes();
|
|
}
|
|
|
|
// Filters unimportant stats from the unit config and returns a checksum of
|
|
// the remaining config.
|
|
std::string get_checksum(const unit& u) {
|
|
config unit_config;
|
|
config wcfg;
|
|
u.write(unit_config);
|
|
const std::string main_keys[] =
|
|
{ "advances_to",
|
|
"alignment",
|
|
"cost",
|
|
"experience",
|
|
"gender",
|
|
"hitpoints",
|
|
"ignore_race_traits",
|
|
"ignore_global_traits",
|
|
"level",
|
|
"recall_cost",
|
|
"max_attacks",
|
|
"max_experience",
|
|
"max_hitpoints",
|
|
"max_moves",
|
|
"movement",
|
|
"movement_type",
|
|
"race",
|
|
"random_traits",
|
|
"resting",
|
|
"undead_variation",
|
|
"upkeep",
|
|
"zoc",
|
|
""};
|
|
|
|
for (int i = 0; !main_keys[i].empty(); ++i)
|
|
{
|
|
wcfg[main_keys[i]] = unit_config[main_keys[i]];
|
|
}
|
|
const std::string attack_keys[] =
|
|
{ "name",
|
|
"type",
|
|
"range",
|
|
"damage",
|
|
"number",
|
|
""};
|
|
|
|
BOOST_FOREACH(const config &att, unit_config.child_range("attack"))
|
|
{
|
|
config& child = wcfg.add_child("attack");
|
|
for (int i = 0; !attack_keys[i].empty(); ++i) {
|
|
child[attack_keys[i]] = att[attack_keys[i]];
|
|
}
|
|
BOOST_FOREACH(const config &spec, att.child_range("specials")) {
|
|
config& child_spec = child.add_child("specials", spec);
|
|
child_spec.recursive_clear_value("description");
|
|
}
|
|
|
|
}
|
|
|
|
BOOST_FOREACH(const config &abi, unit_config.child_range("abilities"))
|
|
{
|
|
config& child = wcfg.add_child("abilities", abi);
|
|
child.recursive_clear_value("description");
|
|
child.recursive_clear_value("description_inactive");
|
|
child.recursive_clear_value("name");
|
|
child.recursive_clear_value("name_inactive");
|
|
}
|
|
|
|
BOOST_FOREACH(const config &trait, unit_config.child_range("trait"))
|
|
{
|
|
config& child = wcfg.add_child("trait", trait);
|
|
child.recursive_clear_value("description");
|
|
child.recursive_clear_value("male_name");
|
|
child.recursive_clear_value("female_name");
|
|
child.recursive_clear_value("name");
|
|
}
|
|
|
|
const std::string child_keys[] = {"advance_from", "defense", "movement_costs", "vision_costs", "jamming_costs" "resistance", ""};
|
|
|
|
for (int i = 0; !child_keys[i].empty(); ++i)
|
|
{
|
|
BOOST_FOREACH(const config &c, unit_config.child_range(child_keys[i])) {
|
|
wcfg.add_child(child_keys[i], c);
|
|
}
|
|
}
|
|
DBG_UT << wcfg;
|
|
|
|
return wcfg.hash();
|
|
}
|
|
|