wesnoth/src/unit.cpp
Ali El Gariani db48432957 Add image type where it was implicit...
,,,for preparing a change of the default value It also fix a small bug
: unit overlay was ToD colored (but unit stuff are not)
2007-08-05 01:30:07 +00:00

3212 lines
99 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 - 2007 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 version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "global.hpp"
#include "game_config.hpp"
#include "game_errors.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "log.hpp"
#include "pathfind.hpp"
#include "random.hpp"
#include "unit.hpp"
#include "unit_types.hpp"
#include "unit_abilities.hpp"
#include "util.hpp"
#include "wassert.hpp"
#include "serialization/string_utils.hpp"
#include "halo.hpp"
#include "game_display.hpp"
#include "gamestatus.hpp"
#include "actions.hpp"
#include "game_events.hpp"
#include "sound.hpp"
#include "sdl_utils.hpp"
#include "terrain_filter.hpp"
#include "variable.hpp"
#include <ctime>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <iterator>
#define LOG_UT LOG_STREAM(info, engine)
namespace {
const std::string ModificationTypes[] = { "advance", "trait", "object" };
const size_t NumModificationTypes = sizeof(ModificationTypes)/
sizeof(*ModificationTypes);
// hold 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;
}
static bool compare_unit_values(unit const &a, unit const &b)
{
const int lvla = a.level();
const int lvlb = b.level();
const int xpa = a.max_experience() - a.experience();
const int xpb = b.max_experience() - b.experience();
return lvla > lvlb || (lvla == lvlb && xpa < xpb);
}
void sort_units(std::vector< unit > &units)
{
std::sort(units.begin(), units.end(), compare_unit_values);
}
// Copy constructor
unit::unit(const unit& o):
cfg_(o.cfg_),
movement_b_(o.movement_b_),
defense_b_(o.defense_b_),
resistance_b_(o.resistance_b_),
advances_to_(o.advances_to_),
id_(o.id_),
race_(o.race_),
name_(o.name_),
description_(o.description_),
custom_unit_description_(o.custom_unit_description_),
underlying_description_(o.underlying_description_),
language_name_(o.language_name_),
undead_variation_(o.undead_variation_),
variation_(o.variation_),
hit_points_(o.hit_points_),
max_hit_points_(o.max_hit_points_),
max_hit_points_b_(o.max_hit_points_b_),
experience_(o.experience_),
max_experience_(o.max_experience_),
max_experience_b_(o.max_experience_b_),
level_(o.level_),
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_),
recruits_(o.recruits_),
movement_(o.movement_),
max_movement_(o.max_movement_),
max_movement_b_(o.max_movement_b_),
movement_costs_(o.movement_costs_),
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_),
variables_(o.variables_),
emit_zoc_(o.emit_zoc_),
state_(o.state_),
overlays_(o.overlays_),
role_(o.role_),
ai_special_(o.ai_special_),
attacks_(o.attacks_),
attacks_b_(o.attacks_b_),
facing_(o.facing_),
traits_description_(o.traits_description_),
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_),
modification_descriptions_(o.modification_descriptions_),
defensive_animations_(o.defensive_animations_),
teleport_animations_(o.teleport_animations_),
extra_animations_(o.extra_animations_),
death_animations_(o.death_animations_),
movement_animations_(o.movement_animations_),
standing_animations_(o.standing_animations_),
leading_animations_(o.leading_animations_),
healing_animations_(o.healing_animations_),
victory_animations_(o.victory_animations_),
recruit_animations_(o.recruit_animations_),
idle_animations_(o.idle_animations_),
levelin_animations_(o.levelin_animations_),
levelout_animations_(o.levelout_animations_),
healed_animations_(o.healed_animations_),
poison_animations_(o.poison_animations_),
anim_(NULL),
frame_begin_time_(o.frame_begin_time_),
offset_(o.offset_),
unit_halo_(o.unit_halo_),
unit_anim_halo_(o.unit_anim_halo_),
getsHit_(o.getsHit_),
refreshing_(o.refreshing_),
hidden_(o.hidden_),
draw_bars_(o.draw_bars_),
modifications_(o.modifications_),
gamedata_(o.gamedata_),
units_(o.units_),
map_(o.map_),
gamestatus_(o.gamestatus_),
teams_(o.teams_)
{
next_idling_ = 0;
unit_halo_ = halo::NO_HALO;
unit_anim_halo_ = halo::NO_HALO;
}
// Initilizes a unit from a config
unit::unit(const game_data* gamedata, unit_map* unitmap, const gamemap* map,
const gamestatus* game_status, const std::vector<team>* teams,const config& cfg) :
movement_(0), hold_position_(false),resting_(false),state_(STATE_STANDING),
facing_(gamemap::location::NORTH_EAST),flying_(false),
anim_(NULL),next_idling_(0),frame_begin_time_(0),unit_halo_(halo::NO_HALO),
unit_anim_halo_(halo::NO_HALO), draw_bars_(false),gamedata_(gamedata),
units_(unitmap), map_(map), gamestatus_(game_status),teams_(teams)
{
read(cfg);
getsHit_=0;
end_turn_ = false;
refreshing_ = false;
hidden_ = false;
offset_ = 0;
if(race_->not_living()) {
set_state("not_living","yes");
}
game_config::add_color_info(cfg);
}
unit::unit(const game_data& gamedata,const config& cfg) : movement_(0),
hold_position_(false), resting_(false), state_(STATE_STANDING),
facing_(gamemap::location::NORTH_EAST),
flying_(false),anim_(NULL),next_idling_(0),frame_begin_time_(0),
unit_halo_(halo::NO_HALO),unit_anim_halo_(halo::NO_HALO),draw_bars_(false),
gamedata_(&gamedata), units_(NULL),map_(NULL), gamestatus_(NULL)
{
read(cfg);
getsHit_=0;
end_turn_ = false;
refreshing_ = false;
hidden_ = false;
offset_ = 0;
if(race_->not_living()) {
set_state("not_living","yes");
}
}
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_race::GENDER unit::generate_gender(const unit_type& type, bool gen)
{
const std::vector<unit_race::GENDER>& genders = type.genders();
if(genders.empty() == false) {
return gen ? genders[get_random()%genders.size()] : genders.front();
} else {
return unit_race::MALE;
}
}
// Initilizes a unit from a unit type
unit::unit(const game_data* gamedata, unit_map* unitmap, const gamemap* map,
const gamestatus* game_status, const std::vector<team>* teams, const unit_type* t,
int side, bool use_traits, bool dummy_unit, unit_race::GENDER gender) :
gender_(dummy_unit ? gender : generate_gender(*t,use_traits)), resting_(false), state_(STATE_STANDING), facing_(gamemap::location::NORTH_EAST),draw_bars_(false),
gamedata_(gamedata),units_(unitmap),map_(map),gamestatus_(game_status),teams_(teams)
{
goto_ = gamemap::location();
side_ = side;
movement_ = 0;
attacks_left_ = 0;
experience_ = 0;
cfg_["upkeep"]="full";
advance_to(&t->get_gender_unit_type(gender_));
if(dummy_unit == false) validate_side(side_);
if(use_traits) {
//units that don't have traits generated are just generic
//units, so they shouldn't get a description either.
custom_unit_description_ = generate_description();
generate_traits();
}else{
if (race_->name() == "undead") {
generate_traits();
}
}
if(underlying_description_.empty()){
char buf[80];
if(!custom_unit_description_.empty()){
snprintf(buf, sizeof(buf), "%s-%d-%s",type()->id().c_str(), get_random(), custom_unit_description_.c_str());
}else{
snprintf(buf, sizeof(buf), "%s-%d",type()->id().c_str(), get_random());
}
underlying_description_ = buf;
}
unrenamable_ = false;
anim_ = NULL;
getsHit_=0;
end_turn_ = false;
hold_position_ = false;
offset_ = 0;
next_idling_ = 0;
frame_begin_time_ = 0;
unit_halo_ = halo::NO_HALO;
unit_anim_halo_ = halo::NO_HALO;
}
unit::unit(const unit_type* t, int side, bool use_traits, bool dummy_unit, unit_race::GENDER gender) :
gender_(dummy_unit ? gender : generate_gender(*t,use_traits)),
state_(STATE_STANDING),facing_(gamemap::location::NORTH_EAST),draw_bars_(false),
gamedata_(NULL), units_(NULL),map_(NULL),gamestatus_(NULL),teams_(NULL)
{
goto_ = gamemap::location();
side_ = side;
movement_ = 0;
attacks_left_ = 0;
experience_ = 0;
cfg_["upkeep"]="full";
advance_to(&t->get_gender_unit_type(gender_));
if(dummy_unit == false) validate_side(side_);
if(use_traits) {
//units that don't have traits generated are just generic
//units, so they shouldn't get a description either.
custom_unit_description_ = generate_description();
generate_traits();
}else{
if (race_->name() == "undead") {
generate_traits();
}
}
if(underlying_description_.empty()){
char buf[80];
if(!custom_unit_description_.empty()){
snprintf(buf, sizeof(buf), "%s-%d-%s",type()->id().c_str(), get_random(), custom_unit_description_.c_str());
}else{
snprintf(buf, sizeof(buf), "%s-%d",type()->id().c_str(), get_random());
}
underlying_description_ = buf;
}
unrenamable_ = false;
next_idling_ = 0;
frame_begin_time_ = 0;
anim_ = NULL;
getsHit_=0;
end_turn_ = false;
hold_position_ = false;
offset_ = 0;
}
unit::~unit()
{
clear_haloes();
delete anim_;
// 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);
}
}
unit& unit::operator=(const unit& u)
{
// use copy constructor to make sure we are coherant
if (this != &u) {
this->~unit();
new (this) unit(u) ;
}
return *this ;
}
void unit::set_game_context(const game_data* gamedata, unit_map* unitmap, const gamemap* map, const gamestatus* game_status, const std::vector<team>* teams)
{
gamedata_ = gamedata;
units_ = unitmap;
map_ = map;
gamestatus_ = game_status;
teams_ = teams;
}
void unit::generate_traits()
{
if(!traits_description_.empty())
return;
wassert(gamedata_ != NULL);
const game_data::unit_type_map::const_iterator type = gamedata_->unit_types.find(id());
//calculate the unit's traits
std::vector<config*> candidate_traits = type->second.possible_traits();
std::vector<config*> traits;
const size_t num_traits = type->second.num_traits();
for(size_t n = 0; n != num_traits && candidate_traits.empty() == false; ++n) {
const int num = get_random()%candidate_traits.size();
traits.push_back(candidate_traits[num]);
candidate_traits.erase(candidate_traits.begin()+num);
}
for(std::vector<config*>::const_iterator j = traits.begin(); j != traits.end(); ++j) {
modifications_.add_child("trait",**j);
}
apply_modifications();
}
// Advances this unit to another type
void unit::advance_to(const unit_type* t)
{
t = &t->get_gender_unit_type(gender_).get_variation(variation_);
reset_modifications();
// remove old animations
cfg_.clear_children("defend");
cfg_.clear_children("teleport_anim");
cfg_.clear_children("extra_anim");
cfg_.clear_children("death");
cfg_.clear_children("movement_anim");
cfg_.clear_children("leading_anim");
cfg_.clear_children("healing_anim");
cfg_.clear_children("standing_anim");
cfg_.clear_children("attack");
cfg_.clear_children("abilities");
// clear cache of movement costs
movement_costs_.clear();
if(t->movement_type().get_parent()) {
cfg_.merge_with(t->movement_type().get_parent()->get_cfg());
}
//if unit has specific profile, remember it and have it after advaces
bool specific_profile = false;
std::string profile;
if (type() != NULL)
{
specific_profile = (cfg_["profile"] != type()->cfg_["profile"]);
if (specific_profile)
{
profile = cfg_["profile"];
}
}
cfg_.merge_with(t->cfg_);
if (specific_profile)
{
cfg_["profile"] = profile;
}
cfg_.clear_children("male");
cfg_.clear_children("female");
advances_to_ = t->advances_to();
race_ = t->race_;
language_name_ = t->language_name();
cfg_["unit_description"] = t->unit_description();
undead_variation_ = t->undead_variation();
max_experience_ = t->experience_needed(false);
level_ = t->level();
alignment_ = t->alignment();
alpha_ = t->alpha();
hit_points_ = t->hitpoints();
max_hit_points_ = t->hitpoints();
max_movement_ = t->movement();
emit_zoc_ = t->level();
attacks_ = t->attacks();
unit_value_ = t->cost();
flying_ = t->movement_type().is_flying();
max_attacks_ = lexical_cast_default<int>(t->cfg_["attacks"],1);
defensive_animations_ = t->defensive_animations_;
teleport_animations_ = t->teleport_animations_;
extra_animations_ = t->extra_animations_;
death_animations_ = t->death_animations_;
movement_animations_ = t->movement_animations_;
standing_animations_ = t->standing_animations_;
leading_animations_ = t->leading_animations_;
healing_animations_ = t->healing_animations_;
victory_animations_ = t->victory_animations_;
recruit_animations_ = t->recruit_animations_;
idle_animations_ = t->idle_animations_;
levelin_animations_ = t->levelin_animations_;
levelout_animations_ = t->levelout_animations_;
healed_animations_ = t->healed_animations_;
poison_animations_ = t->poison_animations_;
flag_rgb_ = t->flag_rgb();
backup_state();
//apply modifications etc, refresh the unit
apply_modifications();
if(id()!=t->id() || cfg_["gender"] != cfg_["gender_id"]) {
heal_all();
id_ = t->id();
cfg_["id"] = id_;
cfg_["gender_id"] = cfg_["gender"];
}
game_events::add_events(cfg_.get_children("event"),id_);
cfg_.clear_children("event");
set_state("poisoned","");
set_state("slowed","");
set_state("stoned","");
if(race_->not_living()) {
set_state("not_living","yes");
}
end_turn_ = false;
refreshing_ = false;
hidden_ = false;
}
const unit_type* unit::type() const
{
wassert(gamedata_ != NULL);
std::map<std::string,unit_type>::const_iterator i = gamedata_->unit_types.find(id());
if(i != gamedata_->unit_types.end()) {
return &i->second;
}
return NULL;
}
// the unit's profile
const std::string& unit::profile() const
{
if(cfg_["profile"] != "" && cfg_["profile"] != "unit_image") {
return cfg_["profile"];
}
return absolute_image();
}
SDL_Colour unit::hp_color() const
{
double unit_energy = 0.0;
SDL_Color energy_colour = {0,0,0,0};
if(max_hitpoints() > 0) {
unit_energy = double(hitpoints())/double(max_hitpoints());
}
if(1.0 == unit_energy){
energy_colour.r = 33;
energy_colour.g = 225;
energy_colour.b = 0;
} else if(unit_energy > 1.0) {
energy_colour.r = 100;
energy_colour.g = 255;
energy_colour.b = 100;
} else if(unit_energy >= 0.75) {
energy_colour.r = 170;
energy_colour.g = 255;
energy_colour.b = 0;
} else if(unit_energy >= 0.5) {
energy_colour.r = 255;
energy_colour.g = 155;
energy_colour.b = 0;
} else if(unit_energy >= 0.25) {
energy_colour.r = 255;
energy_colour.g = 175;
energy_colour.b = 0;
} else {
energy_colour.r = 255;
energy_colour.g = 0;
energy_colour.b = 0;
}
return energy_colour;
}
SDL_Colour unit::xp_color() const
{
const SDL_Color near_advance_colour = {255,255,255,0};
const SDL_Color mid_advance_colour = {150,255,255,0};
const SDL_Color far_advance_colour = {0,205,205,0};
const SDL_Color normal_colour = {0,160,225,0};
const SDL_Color near_amla_colour = {225,0,255,0};
const SDL_Color mid_amla_colour = {169,30,255,0};
const SDL_Color far_amla_colour = {139,0,237,0};
const SDL_Color amla_colour = {100,0,150,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 colour=normal_colour;
if(advances_to().size()){
if(near_advance){
colour=near_advance_colour;
} else if(mid_advance){
colour=mid_advance_colour;
} else if(far_advance){
colour=far_advance_colour;
}
} else if (get_modification_advances().size()){
if(near_advance){
colour=near_amla_colour;
} else if(mid_advance){
colour=mid_amla_colour;
} else if(far_advance){
colour=far_amla_colour;
} else {
colour=amla_colour;
}
}
return(colour);
}
void unit::set_movement(int moves)
{
hold_position_ = false;
end_turn_ = false;
movement_ = maximum<int>(0,minimum<int>(moves,max_movement_));
}
void unit::new_turn()
{
end_turn_ = false;
movement_ = total_movement();
attacks_left_ = max_attacks_;
if(has_ability_type("hides")) {
set_state("hides","yes");
}
if(incapacitated()) {
set_attacks(0);
}
if (hold_position_) {
end_turn_ = true;
}
}
void unit::end_turn()
{
set_state("slowed","");
if((movement_ != total_movement()) && !utils::string_bool(get_state("not_moved")) && (!is_healthy_ || attacks_left_ < max_attacks_)) {
resting_ = false;
}
set_state("not_moved","");
//clear interrupted move
set_interrupted_move(gamemap::location());
}
void unit::new_level()
{
role_ = "";
ai_special_ = "";
//set the goto command to be going to no-where
goto_ = gamemap::location();
remove_temporary_modifications();
//reapply all permanent modifications
apply_modifications();
heal_all();
set_state("slowed","");
set_state("poisoned","");
set_state("stoned","");
}
void unit::remove_temporary_modifications()
{
for(unsigned int i = 0; i != NumModificationTypes; ++i) {
const std::string& mod = ModificationTypes[i];
const config::child_list& mods = modifications_.get_children(mod);
for(size_t j = 0; j != mods.size(); ++j) {
if((*mods[j])["duration"] != "forever" && (*mods[j])["duration"] != "") {
modifications_.remove_child(mod,j);
--j;
}
}
}
}
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::string unit::get_state(const std::string& state) const
{
std::map<std::string,std::string>::const_iterator i = states_.find(state);
if(i != states_.end()) {
return i->second;
}
return "";
}
void unit::set_state(const std::string& state, const std::string& value)
{
if(value == "") {
std::map<std::string,std::string>::iterator i = states_.find(state);
if(i != states_.end()) {
states_.erase(i);
}
} else {
states_[state] = value;
}
}
bool unit::has_ability_by_id(const std::string& ability) const
{
const config* abil = cfg_.child("abilities");
if(abil) {
for(config::child_map::const_iterator i = abil->all_children().begin(); i != abil->all_children().end(); ++i) {
for(config::child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
if((**j)["id"] == ability) {
return true;
}
}
}
}
return false;
}
bool unit::matches_filter(const vconfig& cfg, const gamemap::location& loc, bool use_flat_tod) const
{
bool matches = true;
if(loc.valid()) {
wassert(units_ != NULL);
scoped_xy_unit auto_store("this_unit", loc.x, loc.y, *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
config::all_children_iterator cond = cfg.get_config().ordered_begin();
config::all_children_iterator cond_end = cfg.get_config().ordered_end();
while(cond != cond_end)
{
const std::string& cond_name = *((*cond).first);
const vconfig cond_filter(&(*((*cond).second)));
//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 gamemap::location& loc, bool use_flat_tod) const
{
const t_string& t_description = cfg["description"];
const t_string& t_speaker = cfg["speaker"];
const t_string& t_type = cfg["type"];
const t_string& t_ability = cfg["ability"];
const t_string& t_side = cfg["side"];
const t_string& t_weapon = cfg["has_weapon"];
const t_string& t_role = cfg["role"];
const t_string& t_ai_special = cfg["ai_special"];
const t_string& t_race = cfg["race"];
const t_string& t_gender = cfg["gender"];
const t_string& t_canrecruit = cfg["canrecruit"];
const t_string& t_level = cfg["level"];
const std::string& description = t_description;
const std::string& speaker = t_speaker;
const std::string& type = t_type;
const std::string& ability = t_ability;
const std::string& side = t_side;
const std::string& weapon = t_weapon;
const std::string& role = t_role;
const std::string& ai_special = t_ai_special;
const std::string& race = t_race;
const std::string& gender = t_gender;
const std::string& canrecruit = t_canrecruit;
const std::string& level = t_level;
if(description.empty() == false && description != this->underlying_description()) {
return false;
}
//allow 'speaker' as an alternative to description, since people use it so often
if(speaker.empty() == false && speaker != this->underlying_description()) {
return false;
}
if(cfg.has_child("filter_location")) {
wassert(map_ != NULL);
wassert(gamestatus_ != NULL);
wassert(units_ != NULL);
bool res = terrain_matches_filter(*map_, loc, cfg.child("filter_location"), *gamestatus_, *units_, use_flat_tod);
if(res == false) {
return false;
}
}
//Also allow filtering on location ranges outside of the location filter
if(!cfg["x"].empty() || !cfg["y"].empty()){
if(!loc.matches_range(cfg["x"], cfg["y"])) {
return false;
}
}
const std::string& this_type = id();
//the type could be a comma separated list of types
if(type.empty() == false && type != this_type) {
//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(std::find(type.begin(),type.end(),',') != type.end() &&
std::search(type.begin(),type.end(),this_type.begin(),
this_type.end()) != type.end()) {
const std::vector<std::string>& vals = utils::split(type);
if(std::find(vals.begin(),vals.end(),this_type) == vals.end()) {
return false;
}
} else {
return false;
}
}
if(ability.empty() == false && has_ability_by_id(ability) == false) {
if(std::find(ability.begin(),ability.end(),',') != ability.end()) {
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;
}
}
if(race.empty() == false && race_->name() != race) {
return false;
}
if(gender.empty() == false) {
if(string_gender(gender) != this->gender()) {
return false;
}
}
if(side.empty() == false && this->side() != (unsigned)atoi(side.c_str()))
{
if(std::find(side.begin(),side.end(),',') != side.end()) {
const std::vector<std::string>& vals = utils::split(side);
std::ostringstream s;
s << (this->side());
if(std::find(vals.begin(),vals.end(),s.str()) == vals.end()) {
return false;
}
} else {
return false;
}
}
if(weapon.empty() == false) {
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;
}
}
if(!has_weapon) {
return false;
}
}
if(role.empty() == false && role_ != role) {
return false;
}
if(ai_special.empty() == false && ai_special_ != ai_special) {
return false;
}
if(canrecruit.empty() == false && utils::string_bool(canrecruit) != can_recruit()) {
return false;
}
if(level.empty() == false && level_ != lexical_cast_default<int>(level,-1)) {
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("wml_filter");
if (!wmlcfgs.empty()) {
config unit_cfg;
write(unit_cfg);
//now, match the kids, WML based
for(unsigned int i=0; i < wmlcfgs.size(); ++i) {
if(!unit_cfg.matches(wmlcfgs[i].get_parsed_config())) {
return false;
}
}
}
if(cfg.has_attribute("find_in")) {
//allow filtering by searching a stored variable of units
wassert(gamestatus_ != NULL);
variable_info vi = gamestatus_->sog().get_variable_info(cfg["find_in"], false,
variable_info::TYPE_CONTAINER);
if(!vi.is_valid) return false;
if(vi.explicit_index) {
if(description_ != (vi.vars->get_children(vi.key)[vi.index])->get_attribute("description")) {
return false;
}
} else {
config::child_itors ch_itors = vi.vars->child_range(vi.key);
for(; ch_itors.first != ch_itors.second; ++ch_itors.first) {
if(description_ == (*ch_itors.first)->get_attribute("description")) {
break;
}
}
if(ch_itors.first == ch_itors.second) {
return false;
}
}
}
return true;
}
/**
* Initializes this unit from a cfg object.
*
* \param cfg Configuration object from which to read the unit
*/
void unit::read(const config& cfg)
{
if(cfg["id"]=="" && cfg["type"]=="") {
throw game::load_game_failed("Attempt to de-serialize an empty unit");
}
cfg_ = cfg;
side_ = lexical_cast_default<int>(cfg["side"]);
if(side_ <= 0) {
side_ = 1;
}
validate_side(side_);
bool id_set = cfg["id"] != "";
/* prevent un-initialized variables */
max_hit_points_=1;
hit_points_=1;
max_movement_=0;
max_experience_=0;
/* */
gender_ = string_gender(cfg["gender"]);
variation_ = cfg["variation"];
wassert(gamedata_ != NULL);
description_ = cfg["description"];
custom_unit_description_ = cfg["user_description"];
std::string custom_unit_desc = cfg["unit_description"];
underlying_description_ = cfg["description"];
if(underlying_description_.empty()){
char buf[80];
snprintf(buf, sizeof(buf), "%s-%d",cfg["type"].c_str(), get_random());
underlying_description_ = buf;
}
if(description_.empty()) {
description_ = cfg["type"].c_str();
}
role_ = cfg["role"];
ai_special_ = cfg["ai_special"];
overlays_ = utils::split(cfg["overlays"]);
if(overlays_.size() == 1 && overlays_.front() == "") {
overlays_.clear();
}
const config* const variables = cfg.child("variables");
if(variables != NULL) {
variables_ = *variables;
cfg_.remove_child("variables",0);
} else {
variables_.clear();
}
advances_to_ = utils::split(cfg["advances_to"]);
if(advances_to_.size() == 1 && advances_to_.front() == "") {
advances_to_.clear();
}
name_ = cfg["name"];
language_name_ = cfg["language_name"];
undead_variation_ = cfg["undead_variation"];
flag_rgb_ = cfg["flag_rgb"];
alpha_ = lexical_cast_default<fixed_t>(cfg["alpha"]);
level_ = lexical_cast_default<int>(cfg["level"]);
unit_value_ = lexical_cast_default<int>(cfg["value"]);
facing_ = gamemap::location::parse_direction(cfg["facing"]);
if(facing_ == gamemap::location::NDIRECTIONS) facing_ = gamemap::location::NORTH_EAST;
recruits_ = utils::split(cfg["recruits"]);
if(recruits_.size() == 1 && recruits_.front() == "") {
recruits_.clear();
}
attacks_left_ = lexical_cast_default<int>(cfg["attacks_left"]);
const config* mods = cfg.child("modifications");
if(mods) {
modifications_ = *mods;
cfg_.remove_child("modifications",0);
}
bool type_set = false;
id_ = "";
wassert(gamedata_ != NULL);
if(cfg["type"] != "" && cfg["type"] != cfg["id"] || cfg["gender"] != cfg["gender_id"]) {
std::map<std::string,unit_type>::const_iterator i = gamedata_->unit_types.find(cfg["type"]);
if(i != gamedata_->unit_types.end()) {
advance_to(&i->second.get_gender_unit_type(gender_));
type_set = true;
} else {
LOG_STREAM(err, engine) << "unit of type " << cfg["type"] << " not found!\n";
throw game::game_error("Unknown unit type '" + cfg["type"] + "'");
}
attacks_left_ = max_attacks_;
if(cfg["moves"]=="") {
movement_ = max_movement_;
}
if(movement_ < 0) {
attacks_left_ = 0;
movement_ = 0;
}
}
if(cfg_["id"]=="") {
id_ = cfg_["type"];
} else {
id_ = cfg_["id"];
}
if(!type_set || cfg["race"] != "") {
const race_map::const_iterator race_it = gamedata_->races.find(cfg["race"]);
if(race_it != gamedata_->races.end()) {
race_ = &race_it->second;
} else {
static const unit_race dummy_race;
race_ = &dummy_race;
}
}
variation_ = cfg["variation"];
if(!type_set || cfg["max_attacks"] != "") {
max_attacks_ = minimum<int>(1,lexical_cast_default<int>(cfg["max_attacks"]));
}
if(!type_set || cfg["zoc"] != "") {
emit_zoc_ = lexical_cast_default<int>(cfg["zoc"]);
}
if(cfg["max_hitpoints"] != "") {
max_hit_points_ = lexical_cast_default<int>(cfg["max_hitpoints"]);
}
if(cfg["max_moves"] != "") {
max_movement_ = lexical_cast_default<int>(cfg["max_moves"]);
}
if(cfg["max_experience"] != "") {
max_experience_ = lexical_cast_default<int>(cfg["max_experience"]);
}
if(cfg["flying"] != "") {
flying_ = utils::string_bool(cfg["flying"]);
}
if(custom_unit_desc != "") {
cfg_["unit_description"] = custom_unit_desc;
}
if(cfg["profile"] != "" && !id_set) {
cfg_["profile"] = cfg["profile"];
}
std::map<std::string,unit_type>::const_iterator uti = gamedata_->unit_types.find(cfg["type"]);
const unit_type* ut = NULL;
if(uti != gamedata_->unit_types.end()) {
ut = &uti->second.get_gender_unit_type(gender_).get_variation(variation_);
}
if(!type_set) {
if(ut) {
if(cfg_["unit_description"] == "") {
cfg_["unit_description"] = ut->unit_description();
}
config t_atks;
config u_atks;
config::const_child_itors range;
for(range = ut->cfg_.child_range("attack");
range.first != range.second; ++range.first) {
t_atks.add_child("attack",**range.first);
}
for(range = cfg.child_range("attack");
range.first != range.second; ++range.first) {
u_atks.add_child("attack",**range.first);
}
t_atks.merge_with(u_atks);
for(range = t_atks.child_range("attack");
range.first != range.second; ++range.first) {
attacks_.push_back(attack_type(**range.first,id(),image_fighting((**range.first)["range"] == "ranged" ? attack_type::LONG_RANGE : attack_type::SHORT_RANGE)));
}
std::vector<attack_type>::iterator at;
for(at = attacks_.begin(); at != attacks_.end(); ++at) {
at->get_cfg().clear_children("animation");
}
for(at = attacks_b_.begin(); at != attacks_b_.end(); ++at) {
at->get_cfg().clear_children("animation");
}
} else {
for(config::const_child_itors range = cfg.child_range("attack");
range.first != range.second; ++range.first) {
attacks_.push_back(attack_type(**range.first,id(),image_fighting((**range.first)["range"] == "ranged" ? attack_type::LONG_RANGE : attack_type::SHORT_RANGE)));
}
}
} else {
std::vector<attack_type>::iterator at;
for(at = attacks_.begin(); at != attacks_.end(); ++at) {
at->get_cfg().clear_children("animation");
}
for(at = attacks_b_.begin(); at != attacks_b_.end(); ++at) {
at->get_cfg().clear_children("animation");
}
}
cfg_.clear_children("attack");
const config* status_flags = cfg.child("status");
if(status_flags) {
for(string_map::const_iterator st = status_flags->values.begin(); st != status_flags->values.end(); ++st) {
// backwards compatibility
if(st->first == "stone") {
states_["stoned"] = st->second;
} else {
states_[st->first] = st->second;
}
}
cfg_.remove_child("status",0);
}
if(cfg["ai_special"] == "guardian") {
set_state("guardian","yes");
}
if(!type_set) {
backup_state();
apply_modifications();
}
if(utils::string_bool(cfg["random_traits"]) ||
race_->name() == "undead") {
generate_traits();
cfg_["random_traits"] = "";
}
if(cfg["hitpoints"] != "") {
hit_points_ = lexical_cast_default<int>(cfg["hitpoints"]);
} else {
hit_points_ = max_hit_points_;
}
goto_.x = lexical_cast_default<int>(cfg["goto_x"]) - 1;
goto_.y = lexical_cast_default<int>(cfg["goto_y"]) - 1;
if(cfg["moves"] != "") {
movement_ = lexical_cast_default<int>(cfg["moves"]);
if(movement_ < 0) {
attacks_left_ = 0;
movement_ = 0;
}
} else {
movement_ = max_movement_;
}
experience_ = lexical_cast_default<int>(cfg["experience"]);
resting_ = utils::string_bool(cfg["resting"]);
unrenamable_ = utils::string_bool(cfg["unrenamable"]);
if(cfg["alignment"]=="lawful") {
alignment_ = unit_type::LAWFUL;
} else if(cfg["alignment"]=="neutral") {
alignment_ = unit_type::NEUTRAL;
} else if(cfg["alignment"]=="chaotic") {
alignment_ = unit_type::CHAOTIC;
} else if(cfg["type"]=="") {
alignment_ = unit_type::NEUTRAL;
}
if(utils::string_bool(cfg["generate_description"])) {
custom_unit_description_ = generate_description();
cfg_["generate_description"] = "";
}
if(!type_set) {
if(ut) {
defensive_animations_ = ut->defensive_animations_;
teleport_animations_ = ut->teleport_animations_;
extra_animations_ = ut->extra_animations_;
death_animations_ = ut->death_animations_;
movement_animations_ = ut->movement_animations_;
standing_animations_ = ut->standing_animations_;
leading_animations_ = ut->leading_animations_;
healing_animations_ = ut->healing_animations_;
victory_animations_ = ut->victory_animations_;
recruit_animations_ = ut->recruit_animations_;
idle_animations_ = ut->idle_animations_;
levelin_animations_ = ut->levelin_animations_;
levelout_animations_ = ut->levelout_animations_;
healed_animations_ = ut->healed_animations_;
poison_animations_ = ut->poison_animations_;
// remove animations from private cfg, since they're not needed there now
cfg_.clear_children("defend");
cfg_.clear_children("teleport_anim");
cfg_.clear_children("extra_anim");
cfg_.clear_children("death");
cfg_.clear_children("movement_anim");
cfg_.clear_children("standing_anim");
cfg_.clear_children("leading_anim");
cfg_.clear_children("healing_anim");
cfg_.clear_children("victory_anim");
cfg_.clear_children("recruit_anim");
cfg_.clear_children("idle_anim");
cfg_.clear_children("levelin_anim");
cfg_.clear_children("levelout_anim");
cfg_.clear_children("healed_anim");
cfg_.clear_children("poison_anim");
} else {
const config::child_list& defends = cfg_.get_children("defend");
const config::child_list& teleports = cfg_.get_children("teleport_anim");
const config::child_list& extra_anims = cfg_.get_children("extra_anim");
const config::child_list& deaths = cfg_.get_children("death");
const config::child_list& movement_anims = cfg_.get_children("movement_anim");
const config::child_list& standing_anims = cfg_.get_children("standing_anim");
const config::child_list& leading_anims = cfg_.get_children("leading_anim");
const config::child_list& healing_anims = cfg_.get_children("healing_anim");
const config::child_list& victory_anims = cfg_.get_children("victory_anim");
const config::child_list& recruit_anims = cfg_.get_children("recruit_anim");
const config::child_list& idle_anims = cfg_.get_children("idle_anim");
const config::child_list& levelin_anims = cfg_.get_children("levelin_anim");
const config::child_list& levelout_anims = cfg_.get_children("levelout_anim");
const config::child_list& healed_anims = cfg_.get_children("healed_anim");
const config::child_list& poison_anims = cfg_.get_children("poison_anim");
for(config::child_list::const_iterator d = defends.begin(); d != defends.end(); ++d) {
defensive_animations_.push_back(defensive_animation(**d));
}
if(defensive_animations_.empty()) {
defensive_animations_.push_back(defensive_animation(-150,unit_frame(absolute_image(),300)));
// always have a defensive animation
}
for(config::child_list::const_iterator t = teleports.begin(); t != teleports.end(); ++t) {
teleport_animations_.push_back(unit_animation(**t));
}
if(teleport_animations_.empty()) {
teleport_animations_.push_back(unit_animation(-20,unit_frame(absolute_image(),40)));
// always have a teleport animation
}
for(config::child_list::const_iterator extra_anim = extra_anims.begin(); extra_anim != extra_anims.end(); ++extra_anim) {
extra_animations_.insert(std::pair<std::string,unit_animation>((**extra_anim)["flag"],unit_animation(**extra_anim)));
}
for(config::child_list::const_iterator death = deaths.begin(); death != deaths.end(); ++death) {
death_animations_.push_back(death_animation(**death));
}
if(death_animations_.empty()) {
death_animations_.push_back(death_animation(0,unit_frame(absolute_image(),10)));
// always have a death animation
}
for(config::child_list::const_iterator movement_anim = movement_anims.begin(); movement_anim != movement_anims.end(); ++movement_anim) {
movement_animations_.push_back(movement_animation(**movement_anim));
}
if(movement_animations_.empty()) {
movement_animations_.push_back(movement_animation(0,unit_frame(absolute_image(),150)));
// always have a movement animation
}
for(config::child_list::const_iterator standing_anim = standing_anims.begin(); standing_anim != standing_anims.end(); ++standing_anim) {
standing_animations_.push_back(standing_animation(**standing_anim));
}
if(standing_animations_.empty()) {
standing_animations_.push_back(standing_animation(0,unit_frame(absolute_image(),0)));
// always have a standing animation
}
for(config::child_list::const_iterator leading_anim = leading_anims.begin(); leading_anim != leading_anims.end(); ++leading_anim) {
leading_animations_.push_back(leading_animation(**leading_anim));
}
if(leading_animations_.empty()) {
leading_animations_.push_back(leading_animation(0,unit_frame(absolute_image(),150)));
// always have a leading animation
}
for(config::child_list::const_iterator victory_anim = victory_anims.begin(); victory_anim != victory_anims.end(); ++victory_anim) {
victory_animations_.push_back(victory_animation(**victory_anim));
}
if(victory_animations_.empty()) {
victory_animations_.push_back(victory_animation(0,unit_frame(absolute_image(),150)));
// always have a victory animation
}
for(config::child_list::const_iterator healing_anim = healing_anims.begin(); healing_anim != healing_anims.end(); ++healing_anim) {
healing_animations_.push_back(healing_animation(**healing_anim));
}
if(healing_animations_.empty()) {
healing_animations_.push_back(healing_animation(0,unit_frame(absolute_image(),500)));
// always have a healing animation
}
for(config::child_list::const_iterator recruit_anim = recruit_anims.begin(); recruit_anim != recruit_anims.end(); ++recruit_anim) {
recruit_animations_.push_back(recruit_animation(**recruit_anim));
}
if(recruit_animations_.empty()) {
recruit_animations_.push_back(recruit_animation(0,unit_frame(absolute_image(),600,"0~1:600")));
// always have a recruit animation
}
for(config::child_list::const_iterator idle_anim = idle_anims.begin(); idle_anim != idle_anims.end(); ++idle_anim) {
idle_animations_.push_back(idle_animation(**idle_anim));
}
// idle animations can be empty
for(config::child_list::const_iterator levelin_anim = levelin_anims.begin(); levelin_anim != levelin_anims.end(); ++levelin_anim) {
levelin_animations_.push_back(levelin_animation(**levelin_anim));
}
if(levelin_animations_.empty()) {
levelin_animations_.push_back(levelin_animation(0,unit_frame(absolute_image(),600,"1.0","",game_display::rgb(255,255,255),"1~0:600")));
// always have a levelin animation
}
for(config::child_list::const_iterator levelout_anim = levelout_anims.begin(); levelout_anim != levelout_anims.end(); ++levelout_anim) {
levelout_animations_.push_back(levelout_animation(**levelout_anim));
}
if(levelout_animations_.empty()) {
levelout_animations_.push_back(levelout_animation(0,unit_frame(absolute_image(),600,"1.0","",game_display::rgb(255,255,255),"0~1:600")));
// always have a levelout animation
}
for(config::child_list::const_iterator healed_anim = healed_anims.begin(); healed_anim != healed_anims.end(); ++healed_anim) {
healed_animations_.push_back(healed_animation(**healed_anim));
}
if(healed_animations_.empty()) {
healed_animations_.push_back(healed_animation(0,unit_frame(absolute_image(),240,"1.0","",game_display::rgb(255,255,255),"0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30")));
// always have a healed animation
}
for(config::child_list::const_iterator poison_anim = poison_anims.begin(); poison_anim != poison_anims.end(); ++poison_anim) {
poison_animations_.push_back(poison_animation(**poison_anim));
}
if(poison_animations_.empty()) {
poison_animations_.push_back(poison_animation(0,unit_frame(absolute_image(),240,"1.0","",game_display::rgb(0,255,0),"0:30,0.5:30,0:30,0.5:30,0:30,0.5:30,0:30,0.5:30")));
// always have a healed animation
}
}
} else {
// remove animations from private cfg, since they're not needed there now
cfg_.clear_children("defend");
cfg_.clear_children("teleport_anim");
cfg_.clear_children("extra_anim");
cfg_.clear_children("death");
cfg_.clear_children("movement_anim");
cfg_.clear_children("standing_anim");
cfg_.clear_children("leading_anim");
cfg_.clear_children("healing_anim");
cfg_.clear_children("victory_anim");
cfg_.clear_children("recruit_anim");
cfg_.clear_children("idle_anim");
cfg_.clear_children("levelin_anim");
cfg_.clear_children("levelout_anim");
cfg_.clear_children("healed_anim");
cfg_.clear_children("poison_anim");
}
game_events::add_events(cfg_.get_children("event"),id_);
cfg_.clear_children("event");
}
void unit::write(config& cfg) const
{
// if a location has been saved in the config, keep it
std::string x = cfg["x"];
std::string y = cfg["y"];
cfg.append(cfg_);
cfg.clear_children("movement_costs");
cfg.clear_children("defense");
cfg.clear_children("resistance");
cfg.add_child("movement_costs",movement_b_);
cfg.add_child("defense",defense_b_);
cfg.add_child("resistance",resistance_b_);
cfg["x"] = x;
cfg["y"] = y;
cfg["id"] = id();
cfg["type"] = id();
std::map<std::string,unit_type>::const_iterator uti = gamedata_->unit_types.find(id());
const unit_type* ut = NULL;
if(uti != gamedata_->unit_types.end()) {
ut = &uti->second.get_gender_unit_type(gender_).get_variation(variation_);
}
if(ut && cfg["unit_description"] == ut->unit_description()) {
cfg["unit_description"] = "";
}
std::stringstream hp;
hp << hit_points_;
cfg["hitpoints"] = hp.str();
std::stringstream hpm;
hpm << max_hit_points_b_;
cfg["max_hitpoints"] = hpm.str();
std::stringstream xp;
xp << experience_;
cfg["experience"] = xp.str();
std::stringstream xpm;
xpm << max_experience_b_;
cfg["max_experience"] = xpm.str();
std::stringstream sd;
sd << side_;
cfg["side"] = sd.str();
cfg["gender"] = gender_string(gender_);
cfg["gender_id"] = gender_string(gender_);
cfg["variation"] = variation_;
cfg["role"] = role_;
cfg["ai_special"] = ai_special_;
cfg["flying"] = flying_ ? "yes" : "no";
config status_flags;
for(std::map<std::string,std::string>::const_iterator st = states_.begin(); st != states_.end(); ++st) {
status_flags[st->first] = st->second;
}
cfg.clear_children("variables");
cfg.add_child("variables",variables_);
cfg.clear_children("status");
cfg.add_child("status",status_flags);
cfg["overlays"] = utils::join(overlays_);
cfg["user_description"] = custom_unit_description_;
cfg["description"] = underlying_description_;
if(can_recruit())
cfg["canrecruit"] = "1";
cfg["facing"] = gamemap::location::write_direction(facing_);
cfg["goto_x"] = lexical_cast_default<std::string>(goto_.x+1);
cfg["goto_y"] = lexical_cast_default<std::string>(goto_.y+1);
cfg["moves"] = lexical_cast_default<std::string>(movement_);
cfg["max_moves"] = lexical_cast_default<std::string>(max_movement_b_);
cfg["resting"] = resting_ ? "yes" : "no";
cfg["advances_to"] = utils::join(advances_to_);
cfg["race"] = race_->name();
cfg["name"] = name_;
cfg["language_name"] = language_name_;
cfg["undead_variation"] = undead_variation_;
cfg["variation"] = variation_;
cfg["level"] = lexical_cast_default<std::string>(level_);
switch(alignment_) {
case unit_type::LAWFUL:
cfg["alignment"] = "lawful";
break;
case unit_type::NEUTRAL:
cfg["alignment"] = "neutral";
break;
case unit_type::CHAOTIC:
cfg["alignment"] = "chaotic";
break;
default:
cfg["alignment"] = "neutral";
}
cfg["flag_rgb"] = flag_rgb_;
cfg["unrenamable"] = unrenamable_ ? "yes" : "no";
cfg["alpha"] = lexical_cast_default<std::string>(alpha_);
cfg["recruits"] = utils::join(recruits_);
cfg["attacks_left"] = lexical_cast_default<std::string>(attacks_left_);
cfg["max_attacks"] = lexical_cast_default<std::string>(max_attacks_);
cfg["zoc"] = lexical_cast_default<std::string>(emit_zoc_);
cfg.clear_children("attack");
for(std::vector<attack_type>::const_iterator i = attacks_b_.begin(); i != attacks_b_.end(); ++i) {
cfg.add_child("attack",i->get_cfg());
}
cfg["value"] = lexical_cast_default<std::string>(unit_value_);
cfg["cost"] = lexical_cast_default<std::string>(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.size()){
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_standing(const game_display &disp,const gamemap::location& loc, bool with_bars)
{
state_ = STATE_STANDING;
offset_=0;
start_animation(disp,loc,stand_animation(disp,loc),with_bars);
}
void unit::set_defending(const game_display &disp,const gamemap::location& loc, int damage,const attack_type* attack,const attack_type* secondary_attack,int swing_num)
{
state_ = STATE_DEFENDING;
fighting_animation::hit_type hit_type;
if(damage >= hitpoints()) {
hit_type = fighting_animation::KILL;
} else if(damage > 0) {
hit_type = fighting_animation::HIT;
}else {
hit_type = fighting_animation::MISS;
}
start_animation(disp,loc,defend_animation(disp,loc,hit_type,attack,secondary_attack,swing_num,damage),true);
// add a blink on damage effect
const image::locator image_loc = anim_->get_last_frame().image();
if(damage) {
anim_->add_frame(100,unit_frame(image_loc,100,"1.0","",game_display::rgb(255,0,0),"0.5:50,0.0:50"));
}
}
void unit::set_extra_anim(const game_display &disp,const gamemap::location& loc, std::string flag)
{
state_ = STATE_EXTRA;
start_animation(disp,loc,extra_animation(disp,loc,flag),false);
}
const unit_animation & unit::set_attacking(const game_display &disp,const gamemap::location& loc,int damage,const attack_type& type,const attack_type* secondary_attack,int swing_num)
{
state_ = STATE_ATTACKING;
fighting_animation::hit_type hit_type;
if(damage >= hitpoints()) {
hit_type = fighting_animation::KILL;
} else if(damage > 0) {
hit_type = fighting_animation::HIT;
}else {
hit_type = fighting_animation::MISS;
}
return *start_animation(disp,loc,type.animation(disp,loc,this,hit_type,secondary_attack,swing_num,damage),true,true);
}
void unit::set_leading(const game_display &disp,const gamemap::location& loc)
{
state_ = STATE_LEADING;
start_animation(disp,loc,lead_animation(disp,loc),true);
}
void unit::set_leveling_in(const game_display &disp,const gamemap::location& loc)
{
state_ = STATE_LEVELIN;
start_animation(disp,loc,levelingin_animation(disp,loc),false);
}
void unit::set_leveling_out(const game_display &disp,const gamemap::location& loc)
{
state_ = STATE_LEVELOUT;
start_animation(disp,loc,levelingout_animation(disp,loc),false);
}
void unit::set_recruited(const game_display &disp,const gamemap::location& loc)
{
state_ = STATE_RECRUITED;
start_animation(disp,loc,recruiting_animation(disp,loc),false);
}
void unit::set_healed(const game_display &disp,const gamemap::location& loc, int healing)
{
state_ = STATE_HEALED;
start_animation(disp,loc,get_healed_animation(disp,loc,healing),true);
}
void unit::set_poisoned(const game_display &disp,const gamemap::location& loc, int damage)
{
state_ = STATE_POISONED;
start_animation(disp,loc,poisoned_animation(disp,loc,damage),true);
}
void unit::set_teleporting(const game_display &disp,const gamemap::location& loc)
{
state_ = STATE_TELEPORT;
start_animation(disp,loc,teleport_animation(disp,loc),false);
}
void unit::set_dying(const game_display &disp,const gamemap::location& loc,const attack_type* attack,const attack_type* secondary_attack)
{
state_ = STATE_DYING;
start_animation(disp,loc,die_animation(disp,loc,fighting_animation::KILL,attack,secondary_attack),true);
image::locator image_loc = anim_->get_last_frame().image();
anim_->add_frame(600,unit_frame(image_loc,600,"1~0:600"));
}
void unit::set_healing(const game_display &disp,const gamemap::location& loc,int healing)
{
state_ = STATE_HEALING;
start_animation(disp,loc,heal_animation(disp,loc,healing),true);
}
void unit::set_victorious(const game_display &disp,const gamemap::location& loc,const attack_type* attack,const attack_type* secondary_attack)
{
state_ = STATE_VICTORIOUS;
start_animation(disp,loc,victorious_animation(disp,loc,fighting_animation::KILL,attack,secondary_attack),true);
}
void unit::set_walking(const game_display &disp,const gamemap::location& loc)
{
if(state_ == STATE_WALKING && anim_ != NULL && anim_->matches(disp,loc,this) >=0) {
return; // finish current animation, don't start a new one
}
state_ = STATE_WALKING;
start_animation(disp,loc,move_animation(disp,loc),false);
}
void unit::set_idling(const game_display &disp,const gamemap::location& loc)
{
state_ = STATE_IDLING;
start_animation(disp,loc,idling_animation(disp,loc),true);
}
const unit_animation* unit::start_animation(const game_display &disp, const gamemap::location &loc,const unit_animation * animation,bool with_bars,bool is_attack_anim)
{
draw_bars_ = with_bars;
if(anim_) delete anim_;
if(animation && !is_attack_anim) {
anim_ = new unit_animation(*animation);
}else if(animation && is_attack_anim) {
//TODO this, the is_attack_anim param and the return value are ugly hacks that need to be taken care of eventually
anim_ = new attack_animation(*(const attack_animation*)animation);
} else {
set_standing(disp,loc,with_bars);
return NULL;
}
anim_->start_animation(anim_->get_begin_time(), false, disp.turbo_speed());
frame_begin_time_ = anim_->get_begin_time() -1;
next_idling_= get_current_animation_tick() +20000 +rand()%20000;
if(is_attack_anim) {
return &((attack_animation*)anim_)->get_missile_anim();
} else {
return NULL;
}
}
void unit::restart_animation(const game_display& disp,int start_time) {
if(!anim_) return;
anim_->start_animation(start_time,false,disp.turbo_speed());
frame_begin_time_ = start_time -1;
}
void unit::set_facing(gamemap::location::DIRECTION dir) {
wassert(dir != gamemap::location::NDIRECTIONS);
facing_ = dir;
}
void unit::redraw_unit(game_display& disp, const gamemap::location& loc)
{
const gamemap & map = disp.get_map();
if(!loc.valid() || hidden_ || disp.fogged(loc) ||
(invisible(loc,disp.get_units(),disp.get_teams()) &&
disp.get_teams()[disp.viewing_team()].is_enemy(side())) ){
clear_haloes();
if(anim_) anim_->update_last_draw_time();
return;
}
if(refreshing_) return;
refreshing_ = true;
bool facing_west = facing_ == gamemap::location::NORTH_WEST || facing_ == gamemap::location::SOUTH_WEST;
const gamemap::location dst = loc.get_direction(facing_);
const int xsrc = disp.get_location_x(loc);
const int ysrc = disp.get_location_y(loc);
const int xdst = disp.get_location_x(dst);
const int ydst = disp.get_location_y(dst);
const t_translation::t_letter terrain = map.get_terrain(loc);
const terrain_type& terrain_info = map.get_terrain_info(terrain);
const double submerge = is_flying() ? 0.0 : terrain_info.unit_submerge();
int height_adjust = static_cast<int>(terrain_info.unit_height_adjust() * disp.get_zoom_factor());
if (is_flying() && height_adjust < 0) height_adjust = 0;
const int ysrc_adjusted = ysrc - height_adjust;
if(!anim_) set_standing(disp,loc);
const unit_frame& current_frame = anim_->get_current_frame();
if(frame_begin_time_ != anim_->get_current_frame_begin_time()) {
frame_begin_time_ = anim_->get_current_frame_begin_time();
if(!current_frame.sound().empty()) {
sound::play_sound(current_frame.sound());
}
}
double tmp_offset = current_frame.offset(anim_->get_current_frame_time());
if(tmp_offset == -20.0) tmp_offset = offset_;
int d2 = disp.hex_size() / 2;
const int x = static_cast<int>(tmp_offset * xdst + (1.0-tmp_offset) * xsrc) + d2;
const int y = static_cast<int>(tmp_offset * ydst + (1.0-tmp_offset) * ysrc) + d2 - height_adjust;
if(unit_halo_ == halo::NO_HALO && !image_halo().empty()) {
unit_halo_ = halo::add(0, 0, image_halo(), gamemap::location(-1, -1));
}
if(unit_halo_ != halo::NO_HALO) {
halo::set_location(unit_halo_, x, y);
}
if(unit_anim_halo_ != halo::NO_HALO) {
halo::remove(unit_anim_halo_);
unit_anim_halo_ = halo::NO_HALO;
}
if(!current_frame.halo(anim_->get_current_frame_time()).empty()) {
int ft = anim_->get_current_frame_time();
int dx = static_cast<int>(current_frame.halo_x(ft) * disp.get_zoom_factor());
int dy = static_cast<int>(current_frame.halo_y(ft) * disp.get_zoom_factor());
if (facing_west) dx = -dx;
unit_anim_halo_ = halo::add(x + dx, y + dy,
current_frame.halo(ft), gamemap::location(-1, -1),
facing_west ? halo::HREVERSE : halo::NORMAL);
}
image::locator image_loc;
image_loc = current_frame.image();
if(image_loc.is_void()) {
image_loc = absolute_image();
}
#ifndef LOW_MEM
std::string mod=image_mods();
if(mod.size()){
image_loc = image::locator(image_loc,mod);
}
#endif
surface image(image::get_image(image_loc,
image::SCALED_TO_ZOOM,
#ifndef LOW_MEM
true
#else
state_ == STATE_STANDING?true:false
#endif
));
if(image == NULL) {
image = still_image(true);
}
bool stoned = utils::string_bool(get_state("stoned"));
fixed_t highlight_ratio = minimum<fixed_t>(alpha(),current_frame.highlight_ratio(anim_->get_current_frame_time()));
if(invisible(loc,disp.get_units(),disp.get_teams()) &&
highlight_ratio > ftofxp(0.5)) {
highlight_ratio = ftofxp(0.5);
}
if(loc == disp.selected_hex() && highlight_ratio == ftofxp(1.0)) {
highlight_ratio = ftofxp(1.5);
}
Uint32 blend_with = current_frame.blend_with();
double blend_ratio = current_frame.blend_ratio(anim_->get_current_frame_time());
//if(blend_ratio == 0) { blend_with = disp.rgb(0,0,0); }
if (utils::string_bool(get_state("poisoned")) && blend_ratio == 0){
blend_with = disp.rgb(0,255,0);
blend_ratio = 0.25;
}
// we draw bars only if wanted and visible on the map view
bool draw_bars = draw_bars_;
if (draw_bars) {
const int d = disp.hex_size();
SDL_Rect unit_rect = {xsrc, ysrc_adjusted, d, d};
draw_bars = rects_overlap(unit_rect, disp.map_outside_area());
}
surface ellipse_front(NULL);
surface ellipse_back(NULL);
int ellipse_floating = 0;
if(draw_bars && preferences::show_side_colours()) {
// the division by 2 seems to have no real meaning,
// it just works fine with the current center of ellipse
// and prevent a too large adjust if submerge = 1.0
ellipse_floating = static_cast<int>(submerge * disp.hex_size() / 2);
std::string ellipse=image_ellipse();
if(ellipse.empty()){
ellipse="misc/ellipse";
}
const char* const selected = disp.selected_hex() == loc ? "selected-" : "";
// load the ellipse parts recolored to match team color
char buf[100];
std::string tc=team::get_side_colour_index(side_);
snprintf(buf,sizeof(buf),"%s-%stop.png~RC(ellipse_red>%s)",ellipse.c_str(),selected,tc.c_str());
ellipse_back.assign(image::get_image(image::locator(buf), image::SCALED_TO_ZOOM));
snprintf(buf,sizeof(buf),"%s-%sbottom.png~RC(ellipse_red>%s)",ellipse.c_str(),selected,tc.c_str());
ellipse_front.assign(image::get_image(image::locator(buf), image::SCALED_TO_ZOOM));
}
if (ellipse_back != NULL) {
disp.video().blit_surface(xsrc, ysrc_adjusted-ellipse_floating, ellipse_back);
}
if (image != NULL) {
int tmp_x = x - image->w/2;
int tmp_y = y - image->h/2;
disp.render_unit_image(tmp_x, tmp_y, image, facing_west, stoned,
highlight_ratio, blend_with, blend_ratio, submerge);
}
if (ellipse_front != NULL) {
disp.video().blit_surface(xsrc, ysrc_adjusted-ellipse_floating, ellipse_front);
}
if(draw_bars) {
const std::string* movement_file = NULL;
const std::string* energy_file = &game_config::energy_image;
const fixed_t bar_alpha = highlight_ratio < ftofxp(1.0) && blend_with == 0 ? highlight_ratio : (loc == disp.mouseover_hex() ? ftofxp(1.0): ftofxp(0.7));
if(size_t(side()) != disp.viewing_team()+1) {
if(disp.team_valid() &&
disp.get_teams()[disp.viewing_team()].is_enemy(side())) {
movement_file = &game_config::enemy_ball_image;
} else {
movement_file = &game_config::ally_ball_image;
}
} else {
movement_file = &game_config::moved_ball_image;
if(disp.playing_team() == disp.viewing_team() && !user_end_turn()) {
if (movement_left() == total_movement()) {
movement_file = &game_config::unmoved_ball_image;
} else if(unit_can_move(loc,disp.get_units(),map,disp.get_teams())) {
movement_file = &game_config::partmoved_ball_image;
}
}
}
surface orb(image::get_image(*movement_file,image::SCALED_TO_ZOOM));
if (orb != NULL) {
disp.video().blit_surface(xsrc, ysrc_adjusted, orb);
}
double unit_energy = 0.0;
if(max_hitpoints() > 0) {
unit_energy = double(hitpoints())/double(max_hitpoints());
}
#ifdef USE_TINY_GUI
const int bar_shift = static_cast<int>(-2.5*disp.get_zoom_factor());
#else
const int bar_shift = static_cast<int>(-5*disp.get_zoom_factor());
#endif
disp.draw_bar(*energy_file, xsrc+bar_shift, ysrc_adjusted, (max_hitpoints()*2)/3, unit_energy,hp_color(), bar_alpha);
if(experience() > 0 && can_advance()) {
const double filled = double(experience())/double(max_experience());
const int level = maximum<int>(level_,1);
SDL_Color colour=xp_color();
disp.draw_bar(*energy_file, xsrc, ysrc_adjusted, max_experience()/(level*2), filled, colour, bar_alpha);
}
if (can_recruit()) {
surface crown(image::get_image("misc/leader-crown.png",image::SCALED_TO_ZOOM));
if(!crown.null()) {
//if(bar_alpha != ftofxp(1.0)) {
// crown = adjust_surface_alpha(crown, bar_alpha);
//}
disp.video().blit_surface(xsrc,ysrc_adjusted,crown);
}
}
for(std::vector<std::string>::const_iterator ov = overlays().begin(); ov != overlays().end(); ++ov) {
const surface ov_img(image::get_image(*ov, image::SCALED_TO_ZOOM));
if(ov_img != NULL) {
disp.video().blit_surface(xsrc, ysrc_adjusted, ov_img);
}
}
}
refreshing_ = false;
anim_->update_last_draw_time();
}
void unit::clear_haloes()
{
if(unit_halo_ != halo::NO_HALO) {
halo::remove(unit_halo_);
unit_halo_ = halo::NO_HALO;
}
if(unit_anim_halo_ != halo::NO_HALO) {
halo::remove(unit_anim_halo_);
unit_anim_halo_ = halo::NO_HALO;
}
}
std::set<gamemap::location> unit::overlaps(const gamemap::location &loc) const
{
std::set<gamemap::location> over;
if (state_ == STATE_STANDING) {
// Standing units only overlaps if height is adjusted
int height_adjust = map_->get_terrain_info(map_->get_terrain(loc)).unit_height_adjust();
if (is_flying() && height_adjust < 0) height_adjust = 0;
if (height_adjust > 0) {
over.insert(loc.get_direction(gamemap::location::NORTH));
over.insert(loc.get_direction(gamemap::location::NORTH_WEST));
over.insert(loc.get_direction(gamemap::location::NORTH_EAST));
} else if (height_adjust < 0) {
over.insert(loc.get_direction(gamemap::location::SOUTH));
over.insert(loc.get_direction(gamemap::location::SOUTH_WEST));
over.insert(loc.get_direction(gamemap::location::SOUTH_EAST));
}
} else {
// animated units overlaps adjacent hexes
gamemap::location arr[6];
get_adjacent_tiles(loc, arr);
for (unsigned int i = 0; i < 6; i++) {
over.insert(arr[i]);
}
}
//very early calls, anim not initialized yet
double tmp_offset=offset_;
if(anim_)tmp_offset= anim_->get_current_frame().offset(anim_->get_animation_time());
if(tmp_offset == -20.0) tmp_offset = offset_;
// invalidate adjacent neighbours if we don't stay in our hex
if(tmp_offset != 0) {
gamemap::location::DIRECTION dir = (tmp_offset > 0) ? facing_ : loc.get_opposite_dir(facing_);
gamemap::location adj_loc = loc.get_direction(dir);
over.insert(adj_loc);
gamemap::location arr[6];
get_adjacent_tiles(adj_loc, arr);
for (unsigned int i = 0; i < 6; i++) {
over.insert(arr[i]);
}
}
return over;
}
int unit::upkeep() const
{
if(cfg_["upkeep"] == "full") {
return level();
}
if(cfg_["upkeep"] == "loyal") {
return 0;
}
return lexical_cast_default<int>(cfg_["upkeep"]);
}
int unit::movement_cost_internal(const t_translation::t_letter terrain, const int recurse_count) const
{
const int impassable = 10000000;
const std::map<t_translation::t_letter,int>::const_iterator i =
movement_costs_.find(terrain);
if(i != movement_costs_.end()) {
return i->second;
}
wassert(map_ != NULL);
//if this is an alias, then select the best of all underlying terrains
const t_translation::t_list& underlying = map_->underlying_mvt_terrain(terrain);
wassert(!underlying.empty());
if(underlying.size() != 1 || underlying.front() != terrain) { // We fail here but first test underlying_mvt_terrain
bool revert = (underlying.front() == t_translation::MINUS ? true : false);
if(recurse_count >= 100) {
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(*i,recurse_count+1);
if(value < ret_value && !revert) {
ret_value = value;
} else if(value > ret_value && revert) {
ret_value = value;
}
}
movement_costs_.insert(std::pair<t_translation::t_letter, int>(terrain, ret_value));
return ret_value;
}
const config* movement_costs = cfg_.child("movement_costs");
int res = -1;
if(movement_costs != NULL) {
if(underlying.size() != 1) {
LOG_STREAM(err, config) << "terrain '" << terrain << "' has "
<< underlying.size() << " underlying names - 0 expected\n";
return impassable;
}
const std::string& id = map_->get_terrain_info(underlying.front()).id();
const std::string& val = (*movement_costs)[id];
if(val != "") {
res = atoi(val.c_str());
}
}
if(res <= 0) {
res = impassable;
}
movement_costs_.insert(std::pair<t_translation::t_letter, int>(terrain,res));
return res;
}
int unit::movement_cost(const t_translation::t_letter terrain) const
{
const int res = movement_cost_internal(terrain, 0);
if(utils::string_bool(get_state("slowed"))) {
return res*2;
}
return res;
}
int unit::defense_modifier(t_translation::t_letter terrain, int recurse_count) const
{
// const std::map<terrain_type::TERRAIN,int>::const_iterator i = defense_mods_.find(terrain);
// if(i != defense_mods_.end()) {
// return i->second;
// }
wassert(map_ != NULL);
//if this is an alias, then select the best of all underlying terrains
const t_translation::t_list& underlying = map_->underlying_def_terrain(terrain);
wassert(underlying.size() > 0);
if(underlying.size() != 1 || underlying.front() != terrain) {
bool revert = (underlying.front() == t_translation::MINUS ? true : false);
if(recurse_count >= 90) {
LOG_STREAM(err, config) << "infinite defense_modifier recursion: " << t_translation::write_letter(terrain) << " depth " << recurse_count << "\n";
}
if(recurse_count >= 100) {
return 100;
}
int ret_value = revert?0:100;
t_translation::t_list::const_iterator i = underlying.begin();
for(; i != underlying.end(); ++i) {
if(*i == t_translation::PLUS) {
revert = false;
continue;
} else if(*i == t_translation::MINUS) {
revert = true;
continue;
}
const int value = defense_modifier(*i,recurse_count+1);
if(value < ret_value && !revert) {
ret_value = value;
} else if(value > ret_value && revert) {
ret_value = value;
}
}
// defense_mods_.insert(std::pair<terrain_type::TERRAIN,int>(terrain,ret_value));
return ret_value;
}
int res = -1;
const config* const defense = cfg_.child("defense");
if(defense != NULL) {
if(underlying.size() != 1) {
LOG_STREAM(err, config) << "terrain '" << terrain << "' has "
<< underlying.size() << " underlying names - 0 expected\n";
return 100;
}
const std::string& id = map_->get_terrain_info(underlying.front()).id();
const std::string& val = (*defense)[id];
if(val != "") {
res = atoi(val.c_str());
}
}
if(res <= 0) {
res = 50;
}
// defense_mods_.insert(std::pair<terrain_type::TERRAIN,int>(terrain,res));
return res;
}
bool unit::resistance_filter_matches(const config& cfg,bool attacker,const attack_type& damage_type) 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"];
const std::string& damage_name = damage_type.type();
if(!apply_to.empty()) {
if(damage_name != apply_to) {
if(std::find(apply_to.begin(),apply_to.end(),',') != apply_to.end() &&
std::search(apply_to.begin(),apply_to.end(),
damage_name.begin(),damage_name.end()) != apply_to.end()) {
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;
}
}
}
return true;
}
int unit::resistance_against(const attack_type& damage_type,bool attacker,const gamemap::location& loc) const
{
int res = 100;
const std::string& damage_name = damage_type.type();
const config* const resistance = cfg_.child("resistance");
if(resistance != NULL) {
const std::string& val = (*resistance)[damage_name];
if(val != "") {
res = 100 - lexical_cast_default<int>(val);
}
}
unit_ability_list resistance_abilities = get_abilities("resistance",loc);
for(std::vector<std::pair<config*,gamemap::location> >::iterator i = resistance_abilities.cfgs.begin(); i != resistance_abilities.cfgs.end();) {
if(!resistance_filter_matches(*i->first,attacker,damage_type)) {
i = resistance_abilities.cfgs.erase(i);
} else {
++i;
}
}
if(!resistance_abilities.empty()) {
unit_abilities::effect resist_effect(resistance_abilities,res,false);
res = minimum<int>(resist_effect.get_composite_value(),resistance_abilities.highest("max_value").first);
}
return 100 - res;
}
#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;
std::string image;
if(can_advance()){
if(advances_to_.empty()==false){
std::stringstream tooltip;
image=game_config::level_image;
std::vector<std::string> adv=advances_to();
for(std::vector<std::string>::const_iterator i=adv.begin();i!=adv.end();i++){
if((*i).size()){
tooltip<<(*i).c_str()<<"\n";
}
}
temp[image]=tooltip.str();
}
const config::child_list mods=get_modification_advances();
for(config::child_list::const_iterator i = mods.begin(); i != mods.end(); ++i) {
image=(**i)["image"];
if(image.size()){
std::stringstream tooltip;
tooltip<<temp[image];
std::string tt=(**i)["description"];
if(tt.size()){
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>
const config::child_list& advances = get_modification_advances();
for(config::child_list::const_iterator i = advances.begin(); i != advances.end(); ++i) {
icon.first=(**i)["icon"];
icon.second=(**i)["description"];
for(unsigned int j=0;j<(modification_count("advance",(**i)["id"]));j++) {
temp.push_back(icon);
}
}
return(temp);
}
void unit::reset_modifications()
{
max_hit_points_ = max_hit_points_b_;
max_experience_ = max_experience_b_;
max_movement_ = max_movement_b_;
attacks_ = attacks_b_;
cfg_.clear_children("movement_costs");
cfg_.clear_children("defense");
cfg_.clear_children("resistance");
cfg_.add_child("movement_costs",movement_b_);
cfg_.add_child("defense",defense_b_);
cfg_.add_child("resistance",resistance_b_);
}
void unit::backup_state()
{
max_hit_points_b_ = max_hit_points_;
max_experience_b_ = max_experience_;
max_movement_b_ = max_movement_;
attacks_b_ = attacks_;
if(cfg_.child("movement_costs")) {
movement_b_ = *cfg_.child("movement_costs");
} else {
movement_b_ = config();
}
if(cfg_.child("defense")) {
defense_b_ = *cfg_.child("defense");
} else {
defense_b_ = config();
}
if(cfg_.child("resistance")) {
resistance_b_ = *cfg_.child("resistance");
} else {
resistance_b_ = config();
}
}
config::child_list unit::get_modification_advances() const
{
config::child_list res;
const config::child_list& advances = modification_advancements();
for(config::child_list::const_iterator i = advances.begin(); i != advances.end(); ++i) {
if (!utils::string_bool((**i)["strict_amla"]) || advances_to_.empty()) {
if(modification_count("advance",(**i)["id"]) < lexical_cast_default<size_t>((**i)["max_times"],1)) {
std::vector<std::string> temp = utils::split((**i)["require_amla"]);
if(temp.size()){
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;
for(std::vector<std::string>::const_iterator ii = uniq.begin(); ii != uniq.end(); ii++){
int required_num = std::count(temp.begin(),temp.end(),*ii);
int mod_num = modification_count("advance",*ii);
if(required_num>mod_num){
requirements_done=false;
}
}
if(requirements_done){
res.push_back(*i);
}
}else{
res.push_back(*i);
}
}
}
}
return res;
}
size_t unit::modification_count(const std::string& type, const std::string& id) const
{
size_t res = 0;
const config::child_list& items = modifications_.get_children(type);
for(config::child_list::const_iterator i = items.begin(); i != items.end(); ++i) {
if((**i)["id"] == id) {
++res;
}
}
return res;
}
/* Helper function for add_modifications */
static void mod_mdr_merge(config& dst, const config& mod, bool delta)
{
string_map::const_iterator iter = mod.values.begin();
string_map::const_iterator end = mod.values.end();
for (; iter != end; iter++) {
dst[iter->first] =
lexical_cast_default<std::string>(
(delta == true)*lexical_cast_default<int>(dst[iter->first])
+ lexical_cast_default<int>(iter->second)
);
}
}
void unit::add_modification(const std::string& type, const config& mod,
bool no_add)
{
if(no_add == false) {
modifications_.add_child(type,mod);
}
std::vector<t_string> effects_description;
for(config::const_child_itors i = mod.child_range("effect");
i.first != i.second; ++i.first) {
//see if the effect only applies to certain unit types
const std::string& type_filter = (**i.first)["unit_type"];
if(type_filter.empty() == false) {
const std::vector<std::string>& types = utils::split(type_filter);
if(std::find(types.begin(),types.end(),id()) == types.end()) {
continue;
}
}
//see if the effect only applies to certain genders
const std::string& gender_filter = (**i.first)["unit_gender"];
if(gender_filter.empty() == false) {
const std::string& gender = gender_string(gender_);
const std::vector<std::string>& genders = utils::split(gender_filter);
if(std::find(genders.begin(),genders.end(),gender) == genders.end()) {
continue;
}
}
const std::string& apply_to = (**i.first)["apply_to"];
const std::string& apply_times = (**i.first)["times"];
int times = 1;
t_string description;
if (apply_times == "per level")
times = level_;
if (times) {
while (times > 0) {
times --;
//apply variations -- only apply if we are adding this
//for the first time.
if(apply_to == "variation" && no_add == false) {
variation_ = (**i.first)["name"];
wassert(gamedata_ != NULL);
const game_data::unit_type_map::const_iterator var = gamedata_->unit_types.find(id());
advance_to(&var->second.get_variation(variation_));
} else if(apply_to == "profile") {
const std::string& portrait = (**i.first)["portrait"];
const std::string& description = (**i.first)["description"];
if(!portrait.empty()) cfg_["profile"] = portrait;
if(!description.empty()) cfg_["unit_description"] = description;
//help::unit_topic_generator(*this, (**i.first)["help_topic"]);
} else if(apply_to == "new_attack") {
attacks_.push_back(attack_type(**i.first,id(),image_fighting((**i.first)["range"]=="ranged" ? attack_type::LONG_RANGE : attack_type::SHORT_RANGE)));
} else if(apply_to == "remove_attacks") {
int num_attacks= attacks_.size();
for(std::vector<attack_type>::iterator a = attacks_.begin(); a != attacks_.end(); ++a) {
if (a->matches_filter(**i.first,false)) {
if (num_attacks > 1) {
attacks_.erase(a--);
num_attacks--;
} else {
// Don't remove the last attack
LOG_STREAM(err, config) << "[effect] tried to remove the last attack : ignored.\n";
}
}
}
} else 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->apply_modification(**i.first,&desc);
if(affected && desc != "") {
if(first_attack) {
first_attack = false;
} else {
if (!times)
description += t_string(N_("; "), "wesnoth");
}
if (!times)
description += t_string(a->name(), "wesnoth") + " " + desc;
}
}
} else if(apply_to == "hitpoints") {
LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_ << "\n";
const std::string& increase_hp = (**i.first)["increase"];
const std::string& heal_full = (**i.first)["heal_full"];
const std::string& increase_total = (**i.first)["increase_total"];
const std::string& set_hp = (**i.first)["set"];
const std::string& set_total = (**i.first)["set_total"];
//if the hitpoints are allowed to end up greater than max hitpoints
const std::string& violate_max = (**i.first)["violate_maximum"];
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 += (increase_total[0] != '-' ? "+" : "") +
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(heal_full.empty() == false && utils::string_bool(heal_full,true)) {
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.empty()) {
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 = (**i.first)["increase"];
const std::string& set_to = (**i.first)["set"];
if(increase.empty() == false) {
if (!times)
description += (increase[0] != '-' ? "+" : "") +
increase + " " +
t_string(N_("Moves"), "wesnoth");
max_movement_ = utils::apply_modifier(max_movement_, increase, 1);
}
if(set_to.empty() == false) {
max_movement_ = atoi(set_to.c_str());
}
if(movement_ > max_movement_)
movement_ = max_movement_;
} else if(apply_to == "max_experience") {
const std::string& increase = (**i.first)["increase"];
if(increase.empty() == false) {
if (!times)
description += (increase[0] != '-' ? "+" : "") +
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 = (**i.first)["add"];
const std::string& remove = (**i.first)["remove"];
if(add.empty() == false) {
set_state(add,"yes");
}
if(remove.empty() == false) {
set_state(remove,"");
}
} else if (apply_to == "movement_costs") {
config *mv = cfg_.child("movement_costs");
config *ap = (**i.first).child("movement_costs");
const std::string& replace = (**i.first)["replace"];
if(!mv) {
mv = &cfg_.add_child("movement_costs");
}
if (ap) {
mod_mdr_merge(*mv, *ap, !utils::string_bool(replace));
}
movement_costs_.clear();
} else if (apply_to == "defense") {
config *mv = cfg_.child("defense");
config *ap = (**i.first).child("defense");
const std::string& replace = (**i.first)["replace"];
if(!mv) {
mv = &cfg_.add_child("defense");
}
if (ap) {
mod_mdr_merge(*mv, *ap, !utils::string_bool(replace));
}
} else if (apply_to == "resistance") {
config *mv = cfg_.child("resistance");
config *ap = (**i.first).child("resistance");
const std::string& replace = (**i.first)["replace"];
if(!mv) {
mv = &cfg_.add_child("resistance");
}
if (ap) {
mod_mdr_merge(*mv, *ap, !utils::string_bool(replace));
}
} else if (apply_to == "zoc") {
const std::string& zoc_value = (**i.first)["value"];
if(!zoc_value.empty()) {
emit_zoc_ = lexical_cast_default<int>(zoc_value);
}
} else if (apply_to == "image_mod") {
LOG_UT << "applying image_mod \n";
std::string mod = (**i.first)["replace"];
if (!mod.empty()){
image_mods_ = mod;
}
LOG_UT << "applying image_mod \n";
mod = (**i.first)["add"];
if (!mod.empty()){
image_mods_ += mod;
}
game_config::add_color_info(**i.first);
LOG_UT << "applying image_mod \n";
}
} /* 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(**i.first,&desc);
if(affected && desc != "") {
if(first_attack) {
first_attack = false;
} else {
description += t_string(N_("; "), "wesnoth");
}
description += t_string(a->name(), "wesnoth") + " " + desc;
}
}
} else if(apply_to == "hitpoints") {
const std::string& increase_total = (**i.first)["increase_total"];
if(increase_total.empty() == false) {
description += (increase_total[0] != '-' ? "+" : "") +
increase_total + " " +
t_string(N_("HP"), "wesnoth");
}
} else if(apply_to == "movement") {
const std::string& increase = (**i.first)["increase"];
if(increase.empty() == false) {
description += (increase[0] != '-' ? "+" : "") +
increase + " " +
t_string(N_("Moves"), "wesnoth");
}
} else if(apply_to == "max_experience") {
const std::string& increase = (**i.first)["increase"];
if(increase.empty() == false) {
description += (increase[0] != '-' ? "+" : "") +
increase + " " +
t_string(N_("XP to advance"), "wesnoth");
}
}
}
if (apply_times == "per level" && !times)
description += t_string(N_("/level"), "wesnoth");
if(!description.empty())
effects_description.push_back(description);
}
t_string& description = modification_descriptions_[type];
// Punctuation should be translatable: not all languages use latin punctuation.
// (however, there maybe is a better way to do it)
if (!mod["description"].empty()) {
description += mod["description"] + " ";
}
if(effects_description.empty() == false) {
description += t_string(N_("("), "wesnoth");
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_("; "), "wesnoth");
}
description += t_string(N_(")"), "wesnoth");
}
description += "\n";
}
const t_string& unit::modification_description(const std::string& type) const
{
const string_map::const_iterator i = modification_descriptions_.find(type);
if(i == modification_descriptions_.end()) {
static const t_string empty_string;
return empty_string;
} else {
return i->second;
}
}
const std::string& unit::image_fighting(attack_type::RANGE range) const
{
static const std::string short_range("image_short");
static const std::string long_range("image_long");
const std::string& str = range == attack_type::LONG_RANGE ?
long_range : short_range;
const std::string& val = cfg_[str];
if(!val.empty()) {
return val;
} else {
return absolute_image();
}
}
const defensive_animation* unit::defend_animation(const game_display& disp, const gamemap::location& loc,
fighting_animation::hit_type hits, const attack_type* attack,const attack_type* secondary_attack, int swing_num,int damage) const
{
//select one of the matching animations at random
std::vector<const defensive_animation*> options;
int max_val = -3;
for(std::vector<defensive_animation>::const_iterator i = defensive_animations_.begin(); i != defensive_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this,hits,attack,secondary_attack,swing_num,damage);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const unit_animation* unit::teleport_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const unit_animation*> options;
int max_val = -3;
for(std::vector<unit_animation>::const_iterator i = teleport_animations_.begin(); i != teleport_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const unit_animation* unit::extra_animation(const game_display& disp, const gamemap::location& loc,const std::string &flag) const
{
//select one of the matching animations at random
std::vector<const unit_animation*> options;
int max_val = -3;
std::multimap<std::string,unit_animation>::const_iterator i;
for(i = extra_animations_.lower_bound(flag); i != extra_animations_.upper_bound(flag); ++i) {
int matching = i->second.matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&i->second);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&i->second);
}
}
if(extra_animations_.lower_bound(flag) == extra_animations_.end()) { return NULL;}
return options[rand()%options.size()];
}
const death_animation* unit::die_animation(const game_display& disp, const gamemap::location& loc,
fighting_animation::hit_type hits,const attack_type* attack,const attack_type* secondary_attack) const
{
//select one of the matching animations at random
std::vector<const death_animation*> options;
int max_val = -3;
for(std::vector<death_animation>::const_iterator i = death_animations_.begin(); i != death_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this,hits,attack,secondary_attack,0,0);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const movement_animation* unit::move_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const movement_animation*> options;
int max_val = -3;
for(std::vector<movement_animation>::const_iterator i = movement_animations_.begin(); i != movement_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const standing_animation* unit::stand_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const standing_animation*> options;
int max_val = -3;
for(std::vector<standing_animation>::const_iterator i = standing_animations_.begin(); i != standing_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const leading_animation* unit::lead_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const leading_animation*> options;
int max_val = -3;
for(std::vector<leading_animation>::const_iterator i = leading_animations_.begin(); i != leading_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const victory_animation* unit::victorious_animation(const game_display& disp, const gamemap::location& loc,
fighting_animation::hit_type hits,const attack_type* attack,const attack_type* secondary_attack) const
{
//select one of the matching animations at random
std::vector<const victory_animation*> options;
int max_val = -3;
for(std::vector<victory_animation>::const_iterator i = victory_animations_.begin(); i != victory_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this,hits,attack,secondary_attack,0,0);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const healing_animation* unit::heal_animation(const game_display& disp, const gamemap::location& loc,int damage) const
{
//select one of the matching animations at random
std::vector<const healing_animation*> options;
int max_val = -3;
for(std::vector<healing_animation>::const_iterator i = healing_animations_.begin(); i != healing_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this,damage);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const recruit_animation* unit::recruiting_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const recruit_animation*> options;
int max_val = -3;
for(std::vector<recruit_animation>::const_iterator i = recruit_animations_.begin(); i != recruit_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const idle_animation* unit::idling_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const idle_animation*> options;
int max_val = -3;
for(std::vector<idle_animation>::const_iterator i = idle_animations_.begin(); i != idle_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const levelin_animation* unit::levelingin_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const levelin_animation*> options;
int max_val = -3;
for(std::vector<levelin_animation>::const_iterator i = levelin_animations_.begin(); i != levelin_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const levelout_animation* unit::levelingout_animation(const game_display& disp, const gamemap::location& loc) const
{
//select one of the matching animations at random
std::vector<const levelout_animation*> options;
int max_val = -3;
for(std::vector<levelout_animation>::const_iterator i = levelout_animations_.begin(); i != levelout_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const healed_animation* unit::get_healed_animation(const game_display& disp, const gamemap::location& loc,int healing) const
{
//select one of the matching animations at random
std::vector<const healed_animation*> options;
int max_val = -3;
for(std::vector<healed_animation>::const_iterator i = healed_animations_.begin(); i != healed_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this,healing);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
const poison_animation* unit::poisoned_animation(const game_display& disp, const gamemap::location& loc,int damage) const
{
//select one of the matching animations at random
std::vector<const poison_animation*> options;
int max_val = -3;
for(std::vector<poison_animation>::const_iterator i = poison_animations_.begin(); i != poison_animations_.end(); ++i) {
int matching = i->matches(disp,loc,this,damage);
if(matching == max_val) {
options.push_back(&*i);
} else if(matching > max_val) {
max_val = matching;
options.erase(options.begin(),options.end());
options.push_back(&*i);
}
}
if(options.empty()) return NULL;
return options[rand()%options.size()];
}
void unit::apply_modifications()
{
log_scope("apply mods");
reset_modifications();
modification_descriptions_.clear();
for(size_t i = 0; i != NumModificationTypes; ++i) {
const std::string& mod = ModificationTypes[i];
const config::child_list& mods = modifications_.get_children(mod);
for(config::child_list::const_iterator j = mods.begin(); j != mods.end(); ++j) {
log_scope("add mod");
add_modification(ModificationTypes[i],**j,true);
}
}
traits_description_ = "";
std::vector< t_string > traits;
is_fearless_ = false;
is_healthy_ = false;
config::child_list const &mods = modifications_.get_children("trait");
for(config::child_list::const_iterator j = mods.begin(), j_end = mods.end(); j != j_end; ++j) {
is_fearless_ = is_fearless_ || (**j)["id"] == "fearless";
is_healthy_ = is_healthy_ || (**j)["id"] == "healthy";
const std::string gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
t_string const &gender_specific_name = (**j)[gender_string];
if (!gender_specific_name.empty()) {
traits.push_back(gender_specific_name);
} else {
t_string const &name = (**j)["name"];
if (!name.empty())
traits.push_back(name);
}
}
std::vector< t_string >::iterator k = traits.begin(), k_end = traits.end();
if (k != k_end) {
// we want to make sure the traits always have a consistent ordering
// try out not sorting, since quick,resilient can give
// different HP to resilient,quick so rather preserve order
// std::sort(k, k_end);
for(;;) {
traits_description_ += *(k++);
if (k == k_end) break;
traits_description_ += ", ";
}
}
}
bool unit::invisible(const gamemap::location& loc,
const unit_map& units,const std::vector<team>& teams, bool see_all) const
{
// fetch from cache
// 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<gamemap::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 = (utils::string_bool(get_state(hides)) && get_ability_bool(hides,loc));
if(is_inv){
for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
if(teams[side_-1].is_enemy(u->second.side()) && tiles_adjacent(loc,u->first)) {
// 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->first.x,u->first.y)
&& !u->second.invisible(u->first, units,teams,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;
}
int team_units(const unit_map& units, unsigned int side)
{
int res = 0;
for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
if(i->second.side() == side) {
++res;
}
}
return res;
}
int team_upkeep(const unit_map& units, unsigned int side)
{
int res = 0;
for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
if(i->second.side() == side) {
res += i->second.upkeep();
}
}
return res;
}
unit_map::const_iterator team_leader(unsigned int side, const unit_map& units)
{
for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
if(i->second.can_recruit() && i->second.side() == side) {
return i;
}
}
return units.end();
}
unit_map::iterator find_visible_unit(unit_map& units,
const gamemap::location loc,
const gamemap& map,
const std::vector<team>& teams, const team& current_team,
bool see_all)
{
unit_map::iterator u = units.find(loc);
if(map.on_board(loc) && !see_all){
if(u != units.end()){
if(current_team.fogged(loc.x, loc.y)){
return units.end();
}
if(current_team.is_enemy(u->second.side()) &&
u->second.invisible(loc,units,teams)) {
return units.end();
}
}
}
return u;
}
unit_map::const_iterator find_visible_unit(const unit_map& units,
const gamemap::location loc,
const gamemap& map,
const std::vector<team>& teams, const team& current_team,
bool see_all)
{
unit_map::const_iterator u = units.find(loc);
if(map.on_board(loc) && !see_all){
if(u != units.end()){
if(current_team.fogged(loc.x, loc.y)){
return units.end();
}
if(current_team.is_enemy(u->second.side()) &&
u->second.invisible(loc,units,teams)) {
return units.end();
}
}
}
return u;
}
team_data calculate_team_data(const team& tm, int side, const unit_map& units)
{
team_data res;
res.units = team_units(units,side);
res.upkeep = team_upkeep(units,side);
res.villages = tm.villages().size();
res.expenses = maximum<int>(0,res.upkeep - res.villages);
res.net_income = tm.income() - res.expenses;
res.gold = tm.gold();
res.teamname = tm.user_team_name();
return res;
}
temporary_unit_placer::temporary_unit_placer(unit_map& m, const gamemap::location& loc, const unit& u)
: m_(m), loc_(loc), temp_(m.extract(loc))
{
m.add(new std::pair<gamemap::location,unit>(loc,u));
}
temporary_unit_placer::~temporary_unit_placer()
{
m_.erase(loc_);
if(temp_) {
m_.add(temp_);
}
}
std::string unit::image_mods() const{
std::stringstream modifier;
if(flag_rgb_.size()){
modifier << "~RC("<< flag_rgb_ << ">" << team::get_side_colour_index(side()) << ")";
}
if(image_mods_.size()){
modifier << "~" << image_mods_;
}
return modifier.str();
}
void unit::set_hidden(bool state) {
hidden_ = state;
if(!state) return;
// we need to get rid of haloes immediately to avoid display glitches
clear_haloes();
}
std::string get_checksum(const unit& u, const bool discard_description)
{
config unit_config;
u.write(unit_config);
unit_config["controller"] = "";
// since the ai messes up the 'moves' attribute, ignore that for the checksum
unit_config["moves"] = "";
if(discard_description) {
unit_config["description"] = "";
unit_config["user_description"] = "";
}
return unit_config.hash();
}