wesnoth/src/unit_types.cpp

1254 lines
31 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@verizon.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.
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 "gettext.hpp"
#include "log.hpp"
#include "unit_types.hpp"
#include "util.hpp"
#include "wassert.hpp"
#include "serialization/string_utils.hpp"
#include <algorithm>
#include <cstdlib>
#include <iostream>
unit_animation::frame::frame(const config& cfg)
{
xoffset = atoi(cfg["xoffset"].c_str());
image = cfg["image"];
image_diagonal = cfg["image_diagonal"];
halo = cfg["halo"];
halo_x = atoi(cfg["halo_x"].c_str());
halo_y = atoi(cfg["halo_y"].c_str());
}
unit_animation::unit_animation()
{}
unit_animation::unit_animation(const config& cfg)
{
config::const_child_itors range = cfg.child_range("frame");
int last_end = INT_MIN;
for(; range.first != range.second; ++range.first) {
unit_frames_.add_frame(atoi((**range.first)["begin"].c_str()), frame(**range.first));
last_end = maximum<int>(atoi((**range.first)["end"].c_str()), last_end);
}
//unit_frames_.set_animation_end(last_end);
unit_frames_.add_frame(last_end);
last_end = INT_MIN;
range = cfg.child_range("missile_frame");
for(; range.first != range.second; ++range.first) {
missile_frames_.add_frame(atoi((**range.first)["begin"].c_str()), frame(**range.first));
last_end = maximum<int>(atoi((**range.first)["end"].c_str()), last_end);
}
missile_frames_.add_frame(last_end);
range = cfg.child_range("sound");
for(; range.first != range.second; ++range.first){
sfx sound;
sound.time = atoi((**range.first)["time"].c_str());
sound.on_hit = (**range.first)["sound"];
sound.on_miss = (**range.first)["sound_miss"];
if(sound.on_miss.empty())
sound.on_miss = sound.on_hit;
if(sound.on_miss == "null")
sound.on_miss = "";
sfx_.push_back(sound);
}
}
int unit_animation::get_first_frame_time(unit_animation::FRAME_TYPE type) const
{
if(type == UNIT_FRAME) {
return unit_frames_.get_first_frame_time();
} else {
return missile_frames_.get_first_frame_time();
}
}
int unit_animation::get_last_frame_time(unit_animation::FRAME_TYPE type) const
{
if(type == UNIT_FRAME) {
return unit_frames_.get_last_frame_time();
} else {
return missile_frames_.get_last_frame_time();
}
}
void unit_animation::start_animation(int start_frame, FRAME_TYPE type, int acceleration)
{
if (type == UNIT_FRAME) {
unit_frames_.start_animation(start_frame, 1, acceleration);
} else {
missile_frames_.start_animation(start_frame, 1, acceleration);
}
}
void unit_animation::update_current_frames()
{
unit_frames_.update_current_frame();
missile_frames_.update_current_frame();
}
bool unit_animation::animation_finished() const
{
return unit_frames_.animation_finished() && missile_frames_.animation_finished();
}
const unit_animation::frame& unit_animation::get_current_frame(FRAME_TYPE type) const
{
if(type == UNIT_FRAME) {
return unit_frames_.get_current_frame();
} else {
return missile_frames_.get_current_frame();
}
}
int unit_animation::get_animation_time() const
{
return unit_frames_.get_animation_time();
}
const std::vector<unit_animation::sfx>& unit_animation::sound_effects() const
{
return sfx_;
}
attack_type::attack_type(const config& cfg)
{
const config::child_list& animations = cfg.get_children("animation");
for(config::child_list::const_iterator an = animations.begin(); an != animations.end(); ++an) {
const std::string& dir = (**an)["direction"];
if(dir == "") {
animation_.push_back(unit_animation(**an));
} else {
const std::vector<std::string>& directions = utils::split(dir);
for(std::vector<std::string>::const_iterator i = directions.begin(); i != directions.end(); ++i) {
const gamemap::location::DIRECTION d = gamemap::location::parse_direction(*i);
if(d != gamemap::location::NDIRECTIONS) {
direction_animation_[d].push_back(unit_animation(**an));
}
}
}
}
if(animation_.empty()) {
animation_.push_back(unit_animation(cfg));
}
id_ = cfg["name"];
description_ = cfg["description"];
if (description_.empty())
description_ = egettext(id_.c_str());
type_ = cfg["type"];
special_ = cfg["special"];
backstab_ = special_ == "backstab";
slow_ = special_ == "slow";
icon_ = cfg["icon"];
if(icon_.empty())
icon_ = "attacks/" + id_ + ".png";
range_ = cfg["range"] == "long" ? LONG_RANGE : SHORT_RANGE;
damage_ = atol(cfg["damage"].c_str());
num_attacks_ = atol(cfg["number"].c_str());
attack_weight_ = atof(cfg["attack_weight"].c_str());
defense_weight_ = atof(cfg["defense_weight"].c_str());
if ( ! attack_weight_ )
attack_weight_ = 1.0;
if ( ! defense_weight_ )
defense_weight_ = 1.0;
}
const t_string& attack_type::name() const
{
return description_;
}
const std::string& attack_type::id() const
{
return id_;
}
const std::string& attack_type::type() const
{
return type_;
}
const std::string& attack_type::special() const
{
return special_;
}
const std::string& attack_type::icon() const
{
return icon_;
}
attack_type::RANGE attack_type::range() const
{
return range_;
}
int attack_type::damage() const
{
return damage_;
}
int attack_type::num_attacks() const
{
return num_attacks_;
}
double attack_type::attack_weight() const
{
return attack_weight_;
}
double attack_type::defense_weight() const
{
return defense_weight_;
}
bool attack_type::backstab() const
{
return backstab_;
}
bool attack_type::slow() const
{
return slow_;
}
const unit_animation& attack_type::animation(const gamemap::location::DIRECTION dir) const
{
const std::vector<unit_animation>* animation = &animation_;
if(dir < sizeof(direction_animation_)/sizeof(*direction_animation_) &&
direction_animation_[dir].empty() == false) {
animation = &direction_animation_[dir];
}
wassert(animation->empty() == false);
return (*animation)[rand()%animation->size()];
}
bool attack_type::matches_filter(const config& cfg) const
{
const std::string& filter_range = cfg["range"];
const t_string& filter_name = cfg["name"];
const std::string& filter_type = cfg["type"];
const std::string& filter_special = cfg["special"];
if(filter_range.empty() == false) {
if(filter_range == "short" && range() == LONG_RANGE ||
filter_range == "long" && range() == SHORT_RANGE) {
return false;
}
}
if(filter_name.empty() == false && filter_name != name())
return false;
if(filter_type.empty() == false && filter_type != type())
return false;
if(filter_special.empty() == false && filter_special != special())
return false;
return true;
}
bool attack_type::apply_modification(const config& cfg, std::string* description)
{
if(!matches_filter(cfg))
return false;
const t_string& set_name = cfg["set_name"];
const std::string& set_type = cfg["set_type"];
const std::string& set_special = cfg["set_special"];
const std::string& increase_damage = cfg["increase_damage"];
const std::string& increase_attacks = cfg["increase_attacks"];
std::stringstream desc;
if(set_name.empty() == false) {
description_ = set_name;
id_ = set_name;
}
if(set_type.empty() == false) {
type_ = set_type;
}
if(set_special.empty() == false) {
special_ = set_special;
}
if(increase_damage.empty() == false) {
int increase = 0;
if(increase_damage[increase_damage.size()-1] == '%') {
const std::string inc(increase_damage.begin(),increase_damage.end()-1);
increase = div100(damage_ * atoi(inc.c_str()));
} else {
increase = atoi(increase_damage.c_str());
}
damage_ += increase;
if(damage_ < 1) {
damage_ = 1;
}
if(description != NULL) {
desc << (increase_damage[0] == '-' ? "" : "+") << increase_damage << " " << _("damage");
}
}
if(increase_attacks.empty() == false) {
int increase = 0;
if(increase_attacks[increase_attacks.size()-1] == '%') {
const std::string inc(increase_attacks.begin(),increase_attacks.end()-1);
increase = div100(num_attacks_ * atoi(inc.c_str()));
} else {
increase = atoi(increase_attacks.c_str());
}
num_attacks_ += increase;
if(num_attacks_ < 1) {
num_attacks_ = 1;
}
if(description != NULL) {
desc << (increase_attacks[0] == '-' ? "" : "+") << increase_attacks << " " << _("strikes");
}
}
if(description != NULL) {
*description = desc.str();
}
return true;
}
unit_movement_type::unit_movement_type(const config& cfg, const unit_movement_type* parent)
: cfg_(cfg), parent_(parent)
{}
const t_string& unit_movement_type::name() const
{
const t_string& res = cfg_["name"];
if(res == "" && parent_ != NULL)
return parent_->name();
else
return res;
}
int unit_movement_type::movement_cost(const gamemap& map,gamemap::TERRAIN terrain,int recurse_count) const
{
const int impassable = 10000000;
const std::map<gamemap::TERRAIN,int>::const_iterator i = moveCosts_.find(terrain);
if(i != moveCosts_.end()) {
return i->second;
}
//if this is an alias, then select the best of all underlying terrains
const std::string& underlying = map.underlying_terrain(terrain);
if(underlying.size() != 1 || underlying[0] != terrain) {
if(recurse_count >= 100) {
return impassable;
}
int min_value = impassable;
for(std::string::const_iterator i = underlying.begin(); i != underlying.end(); ++i) {
const int value = movement_cost(map,*i,recurse_count+1);
if(value < min_value) {
min_value = value;
}
}
moveCosts_.insert(std::pair<gamemap::TERRAIN,int>(terrain,min_value));
return min_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[0]).id();
const std::string& val = (*movement_costs)[id];
if(val != "") {
res = atoi(val.c_str());
}
}
if(res <= 0 && parent_ != NULL) {
res = parent_->movement_cost(map,terrain);
}
if(res <= 0) {
res = impassable;
}
moveCosts_.insert(std::pair<gamemap::TERRAIN,int>(terrain,res));
return res;
}
int unit_movement_type::defense_modifier(const gamemap& map,gamemap::TERRAIN terrain, int recurse_count) const
{
const std::map<gamemap::TERRAIN,int>::const_iterator i = defenseMods_.find(terrain);
if(i != defenseMods_.end()) {
return i->second;
}
//if this is an alias, then select the best of all underlying terrains
const std::string& underlying = map.underlying_terrain(terrain);
if(underlying.size() != 1 || underlying[0] != terrain) {
if(recurse_count >= 100) {
return 100;
}
int min_value = 100;
for(std::string::const_iterator i = underlying.begin(); i != underlying.end(); ++i) {
const int value = defense_modifier(map,*i,recurse_count+1);
if(value < min_value) {
min_value = value;
}
}
defenseMods_.insert(std::pair<gamemap::TERRAIN,int>(terrain,min_value));
return min_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[0]).id();
const std::string& val = (*defense)[id];
if(val != "") {
res = atoi(val.c_str());
}
}
if(res <= 0 && parent_ != NULL) {
res = parent_->defense_modifier(map,terrain);
}
if(res <= 0) {
res = 50;
}
defenseMods_.insert(std::pair<gamemap::TERRAIN,int>(terrain,res));
return res;
}
int unit_movement_type::damage_against(const attack_type& attack) const
{
return resistance_against(attack);
}
int unit_movement_type::resistance_against(const attack_type& attack) const
{
bool result_found = false;
int res = 0;
const config* const resistance = cfg_.child("resistance");
if(resistance != NULL) {
const std::string& val = (*resistance)[attack.type()];
if(val != "") {
res = atoi(val.c_str());
result_found = true;
}
}
if(!result_found && parent_ != NULL) {
res = parent_->resistance_against(attack);
}
return res;
}
string_map unit_movement_type::damage_table() const
{
string_map res;
if(parent_ != NULL)
res = parent_->damage_table();
const config* const resistance = cfg_.child("resistance");
if(resistance != NULL) {
for(string_map::const_iterator i = resistance->values.begin(); i != resistance->values.end(); ++i) {
res[i->first] = i->second;
}
}
return res;
}
bool unit_movement_type::is_flying() const
{
const std::string& flies = cfg_["flies"];
if(flies == "" && parent_ != NULL)
return parent_->is_flying();
return flies == "true";
}
void unit_movement_type::set_parent(const unit_movement_type* parent)
{
parent_ = parent;
}
unit_type::unit_type(const unit_type& o)
: variations_(o.variations_), cfg_(o.cfg_), race_(o.race_),
alpha_(o.alpha_), abilities_(o.abilities_),
max_heals_(o.max_heals_), heals_(o.heals_), regenerates_(o.regenerates_),
leadership_(o.leadership_), illuminates_(o.illuminates_),
skirmish_(o.skirmish_), teleport_(o.teleport_),
nightvision_(o.nightvision_), steadfast_(o.steadfast_),
advances_to_(o.advances_to_), experience_needed_(o.experience_needed_),
alignment_(o.alignment_),
movementType_(o.movementType_), possibleTraits_(o.possibleTraits_),
genders_(o.genders_), defensive_animations_(o.defensive_animations_),
teleport_animations_(o.teleport_animations_), death_animations_(o.death_animations_)
{
gender_types_[0] = o.gender_types_[0] != NULL ? new unit_type(*o.gender_types_[0]) : NULL;
gender_types_[1] = o.gender_types_[1] != NULL ? new unit_type(*o.gender_types_[1]) : NULL;
for(variations_map::const_iterator i = o.variations_.begin(); i != o.variations_.end(); ++i) {
variations_[i->first] = new unit_type(*i->second);
}
}
unit_type::unit_type(const config& cfg, const movement_type_map& mv_types,
const race_map& races, const std::vector<config*>& traits)
: cfg_(cfg), alpha_(ftofxp(1.0)), movementType_(cfg), possibleTraits_(traits)
{
const config::child_list& variations = cfg.get_children("variation");
for(config::child_list::const_iterator var = variations.begin(); var != variations.end(); ++var) {
variations_.insert(std::pair<std::string,unit_type*>((**var)["variation_name"],new unit_type(**var,mv_types,races,traits)));
}
gender_types_[0] = NULL;
gender_types_[1] = NULL;
const config* const male_cfg = cfg.child("male");
if(male_cfg != NULL) {
gender_types_[unit_race::MALE] = new unit_type(*male_cfg,mv_types,races,traits);
}
const config* const female_cfg = cfg.child("female");
if(female_cfg != NULL) {
gender_types_[unit_race::FEMALE] = new unit_type(*female_cfg,mv_types,races,traits);
}
const std::vector<std::string> genders = utils::split(cfg["gender"]);
for(std::vector<std::string>::const_iterator i = genders.begin();
i != genders.end(); ++i) {
if(*i == "male") {
genders_.push_back(unit_race::MALE);
} else if(*i == "female") {
genders_.push_back(unit_race::FEMALE);
}
}
if(genders_.empty()) {
genders_.push_back(unit_race::MALE);
}
const race_map::const_iterator race_it = races.find(cfg["race"]);
if(race_it != races.end()) {
race_ = &race_it->second;
if(race_ != NULL) {
if(race_->uses_global_traits() == false) {
possibleTraits_.clear();
}
const config::child_list& traits = race_->additional_traits();
possibleTraits_.insert(possibleTraits_.end(),traits.begin(),traits.end());
}
} else {
static const unit_race dummy_race;
race_ = &dummy_race;
}
//insert any traits that are just for this unit type
const config::child_list& unit_traits = cfg.get_children("trait");
possibleTraits_.insert(possibleTraits_.end(),unit_traits.begin(),unit_traits.end());
abilities_ = utils::split(cfg_["ability"]);
//if the string was empty, split will give us one empty string in the list,
//remove it.
if(!abilities_.empty() && abilities_.back() == "")
abilities_.pop_back();
if(has_ability("heals")) {
heals_ = game_config::healer_heals_per_turn;
max_heals_ = game_config::heal_amount;
} else if(has_ability("cures")) {
heals_ = game_config::curer_heals_per_turn;
max_heals_ = game_config::cure_amount;
} else {
heals_ = 0;
max_heals_ = 0;
}
regenerates_ = has_ability("regenerates");
leadership_ = has_ability("leadership");
illuminates_ = has_ability("illuminates");
skirmish_ = has_ability("skirmisher");
teleport_ = has_ability("teleport");
nightvision_ = has_ability("night vision");
steadfast_ = has_ability("steadfast");
const std::string& align = cfg_["alignment"];
if(align == "lawful")
alignment_ = LAWFUL;
else if(align == "chaotic")
alignment_ = CHAOTIC;
else if(align == "neutral")
alignment_ = NEUTRAL;
else {
LOG_STREAM(err, config) << "Invalid alignment found for " << id() << ": '" << align << "'\n";
alignment_ = NEUTRAL;
}
const std::string& alpha_blend = cfg_["alpha"];
if(alpha_blend.empty() == false) {
alpha_ = ftofxp(atof(alpha_blend.c_str()));
}
const std::string& move_type = cfg_["movement_type"];
const movement_type_map::const_iterator it = mv_types.find(move_type);
if(it != mv_types.end()) {
movementType_.set_parent(&(it->second));
}
const std::string& advance_to_val = cfg_["advanceto"];
if(advance_to_val != "null" && advance_to_val != "")
advances_to_ = utils::split(advance_to_val);
experience_needed_=lexical_cast_default<int>(cfg_["experience"],500);
const config::child_list& defends = cfg_.get_children("defend");
for(config::child_list::const_iterator d = defends.begin(); d != defends.end(); ++d) {
defensive_animations_.push_back(defensive_animation(**d));
}
const config::child_list& teleports = cfg_.get_children("teleport_anim");
for(config::child_list::const_iterator t = teleports.begin(); t != teleports.end(); ++t) {
teleport_animations_.push_back(unit_animation(**t));
}
const config::child_list& deaths = cfg_.get_children("death");
for(config::child_list::const_iterator death = deaths.begin(); death != deaths.end(); ++death) {
death_animations_.push_back(death_animation(**death));
}
}
unit_type::~unit_type()
{
delete gender_types_[unit_race::MALE];
delete gender_types_[unit_race::FEMALE];
for(variations_map::iterator i = variations_.begin(); i != variations_.end(); ++i) {
delete i->second;
}
}
const unit_type& unit_type::get_gender_unit_type(unit_race::GENDER gender) const
{
const size_t i = gender;
if(i < sizeof(gender_types_)/sizeof(*gender_types_) && gender_types_[i] != NULL) {
return *gender_types_[i];
}
return *this;
}
const unit_type& unit_type::get_variation(const std::string& name) const
{
const variations_map::const_iterator i = variations_.find(name);
if(i != variations_.end()) {
return *i->second;
} else {
return *this;
}
}
int unit_type::num_traits() const {
return (cfg_["num_traits"].size() ? atoi(cfg_["num_traits"].c_str()) : race_->num_traits());
}
std::string unit_type::generate_description() const
{
return race_->generate_name(cfg_["gender"] == "female" ? unit_race::FEMALE : unit_race::MALE);
}
const std::string& unit_type::id() const
{
if(id_.empty()) {
id_ = cfg_["id"];
if(id_.empty()) {
// this code is only for compatibility with old unit defs and savefiles
id_ = cfg_["name"];
}
//id_.erase(std::remove(id_.begin(),id_.end(),' '),id_.end());
}
return id_;
}
const t_string& unit_type::language_name() const
{
return cfg_["name"];
}
#if 0
const std::string& unit_type::name() const
{
return cfg_["id"];
}
#endif
const std::string& unit_type::image() const
{
return cfg_["image"];
}
const std::string& unit_type::image_halo() const
{
return cfg_["halo"];
}
const std::string& unit_type::image_moving() const
{
const std::string& res = cfg_["image_moving"];
if(res.empty()) {
return image();
} else {
return res;
}
}
const std::string& unit_type::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 image();
}
const std::string& unit_type::image_defensive(attack_type::RANGE range) const
{
{
static const std::string short_range("image_defensive_short");
static const std::string long_range("image_defensive_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;
}
const std::string& val = cfg_["image_defensive"];
if(val.empty())
return cfg_["image"];
else
return val;
}
const std::string& unit_type::image_leading() const
{
const std::string& val = cfg_["image_leading"];
if(val.empty()) {
return image();
} else {
return val;
}
}
const std::string& unit_type::image_healing() const
{
const std::string& val = cfg_["image_healing"];
if(val.empty()) {
return image();
} else {
return val;
}
}
const std::string& unit_type::image_halo_healing() const
{
return cfg_["image_halo_healing"];
}
const std::string& unit_type::image_profile() const
{
const std::string& val = cfg_["profile"];
if(val.size() == 0)
return image();
else
return val;
}
const std::string& unit_type::unit_description() const
{
static const std::string default_val("No description available");
const std::string& desc = cfg_["unit_description"];
if(desc.empty())
return default_val;
else
return desc;
}
const std::string& unit_type::get_hit_sound() const
{
return cfg_["get_hit_sound"];
}
const std::string& unit_type::die_sound() const
{
return cfg_["die_sound"];
}
int unit_type::hitpoints() const
{
return atoi(cfg_["hitpoints"].c_str());
}
std::vector<attack_type> unit_type::attacks() const
{
std::vector<attack_type> res;
for(config::const_child_itors range = cfg_.child_range("attack");
range.first != range.second; ++range.first) {
res.push_back(attack_type(**range.first));
}
return res;
}
const unit_movement_type& unit_type::movement_type() const
{
return movementType_;
}
int unit_type::cost() const
{
return atoi(cfg_["cost"].c_str());
}
namespace {
int experience_modifier = 100;
}
unit_type::experience_accelerator::experience_accelerator(int modifier) : old_value_(experience_modifier)
{
experience_modifier = (experience_modifier*modifier)/100;
}
unit_type::experience_accelerator::~experience_accelerator()
{
experience_modifier = old_value_;
}
int unit_type::experience_needed() const
{
return (experience_needed_ * experience_modifier) /100;
}
std::vector<std::string> unit_type::advances_to() const
{
return advances_to_;
}
const config::child_list& unit_type::modification_advancements() const
{
return cfg_.get_children("advancement");
}
const std::string& unit_type::undead_variation() const
{
return cfg_["undead_variation"];
}
const std::string& unit_type::usage() const
{
return cfg_["usage"];
}
int unit_type::level() const
{
return atoi(cfg_["level"].c_str());
}
int unit_type::movement() const
{
return atoi(cfg_["movement"].c_str());
}
unit_type::ALIGNMENT unit_type::alignment() const
{
return alignment_;
}
const char* unit_type::alignment_description(unit_type::ALIGNMENT align)
{
static const char* aligns[] = { N_("lawful"), N_("neutral"), N_("chaotic") };
return (gettext(aligns[align]));
}
const char* unit_type::alignment_id(unit_type::ALIGNMENT align)
{
static const char* aligns[] = { "lawful", "neutral", "chaotic" };
return (aligns[align]);
}
fixed_t unit_type::alpha() const
{
return alpha_;
}
const std::vector<std::string>& unit_type::abilities() const
{
return abilities_;
}
int unit_type::max_unit_healing() const
{
return max_heals_;
}
int unit_type::heals() const
{
return heals_;
}
bool unit_type::regenerates() const
{
return regenerates_;
}
bool unit_type::is_leader() const
{
return leadership_;
}
bool unit_type::illuminates() const
{
return illuminates_;
}
bool unit_type::is_skirmisher() const
{
return skirmish_;
}
bool unit_type::teleports() const
{
return teleport_;
}
bool unit_type::nightvision() const
{
return nightvision_;
}
bool unit_type::steadfast() const
{
return steadfast_;
}
bool unit_type::not_living() const
{
return race_->not_living();
}
bool unit_type::can_advance() const
{
return !advances_to_.empty();
}
bool unit_type::has_zoc() const
{
return level() > 0;
}
bool unit_type::has_ability(const std::string& ability) const
{
return std::find(abilities_.begin(),abilities_.end(),ability) != abilities_.end();
}
const std::vector<config*>& unit_type::possible_traits() const
{
return possibleTraits_;
}
const std::vector<unit_race::GENDER>& unit_type::genders() const
{
return genders_;
}
const std::string& unit_type::race() const
{
if(race_ == NULL) {
static const std::string empty_string;
return empty_string;
}
return race_->name();
}
unit_type::defensive_animation::defensive_animation(const config& cfg) : hits(HIT_OR_MISS), range(SHORT_OR_LONG), animation(cfg)
{
const std::string& hits_str = cfg["hits"];
if(hits_str.empty() == false) {
hits = (hits_str == "yes") ? HIT : MISS;
}
const std::string& range_str = cfg["range"];
if(range_str.empty() == false) {
range = (range_str == "short") ? SHORT : LONG;
}
}
bool unit_type::defensive_animation::matches(bool h, attack_type::RANGE r) const
{
if(hits == HIT && h == false || hits == MISS && h == true || range == SHORT && r == attack_type::LONG_RANGE || range == LONG && r == attack_type::SHORT_RANGE) {
return false;
} else {
return true;
}
}
unit_type::death_animation::death_animation(const config& cfg)
: damage_type(utils::split(cfg["damage_type"])), special(utils::split(cfg["attack_special"])), animation(cfg)
{
}
bool unit_type::death_animation::matches(const attack_type* attack) const
{
if(attack == NULL) {
return true;
}
if(damage_type.empty() == false && std::find(damage_type.begin(),damage_type.end(),attack->type()) == damage_type.end()) {
return false;
}
if(special.empty() == false && std::find(special.begin(),special.end(),attack->special()) == special.end()) {
return false;
}
return true;
}
const unit_animation* unit_type::defend_animation(bool hits, attack_type::RANGE range) const
{
//select one of the matching animations at random
const unit_animation* res = NULL;
std::vector<const unit_animation*> options;
for(std::vector<defensive_animation>::const_iterator i = defensive_animations_.begin(); i != defensive_animations_.end(); ++i) {
if(i->matches(hits,range)) {
if(res != NULL) {
options.push_back(res);
}
res = &i->animation;
}
}
if(options.empty()) {
return res;
} else {
options.push_back(res);
return options[rand()%options.size()];
}
}
const unit_animation* unit_type::teleport_animation( ) const
{
if (teleport_animations_.empty()) return NULL;
return &teleport_animations_[rand() % teleport_animations_.size()];
}
const unit_animation* unit_type::die_animation(const attack_type* attack) const
{
if(death_animations_.empty()) {
return NULL;
}
if(attack == NULL) {
return &death_animations_[rand()%death_animations_.size()].animation;
}
const unit_animation* res = NULL;
std::vector<const unit_animation*> options;
for(std::vector<death_animation>::const_iterator i = death_animations_.begin(); i != death_animations_.end(); ++i) {
if(i->matches(attack)) {
if(res != NULL) {
options.push_back(res);
}
res = &i->animation;
}
}
if(options.empty()) {
return res;
} else {
options.push_back(res);
return options[rand()%options.size()];
}
}
void unit_type::add_advancement(const unit_type &to_unit,int xp)
{
const std::string &to_id = to_unit.cfg_["id"];
const std::string &from_id = cfg_["id"];
// add extra advancement path to this unit type
lg::info(lg::config) << "adding advancement from " << from_id << " to " << to_id << "\n";
advances_to_.push_back(to_id);
if(xp>0 && experience_needed_>xp) experience_needed_=xp;
// add advancements to gendered subtypes, if supported by to_unit
for(int gender=0; gender<=1; ++gender) {
if(gender_types_[gender] == NULL) continue;
if(to_unit.gender_types_[gender] == NULL) {
lg::warn(lg::config) << to_id << " does not support gender " << gender << "\n";
continue;
}
lg::info(lg::config) << "gendered advancement " << gender << ": ";
gender_types_[gender]->add_advancement(*(to_unit.gender_types_[gender]),xp);
}
// add advancements to variation subtypes
// since these are still a rare and special-purpose feature,
// we assume that the unit designer knows what they're doing,
// and don't block advancements that would remove a variation
for(variations_map::iterator v=variations_.begin();
v!=variations_.end(); ++v) {
lg::info(lg::config) << "variation advancement: ";
v->second->add_advancement(to_unit,xp);
}
}
game_data::game_data()
{}
game_data::game_data(const config& cfg)
{
set_config(cfg);
}
void game_data::set_config(const config& cfg)
{
static const std::vector<config*> dummy_traits;
const config::child_list& unit_traits = cfg.get_children("trait");
for(config::const_child_itors i = cfg.child_range("movetype");
i.first != i.second; ++i.first) {
const unit_movement_type move_type(**i.first);
movement_types.insert(
std::pair<std::string,unit_movement_type>(move_type.name(),
move_type));
}
for(config::const_child_itors r = cfg.child_range("race");
r.first != r.second; ++r.first) {
const unit_race race(**r.first);
races.insert(std::pair<std::string,unit_race>(race.name(),race));
}
for(config::const_child_itors j = cfg.child_range("unit");
j.first != j.second; ++j.first) {
const unit_type u_type(**j.first,movement_types,races,unit_traits);
unit_types.insert(std::pair<std::string,unit_type>(u_type.id(),u_type));
}
// fix up advance_from references
for(config::const_child_itors k = cfg.child_range("unit");
k.first != k.second; ++k.first)
for(config::const_child_itors af = (*k.first)->child_range("advancefrom");
af.first != af.second; ++af.first) {
const std::string &to = (**k.first)["id"];
const std::string &from = (**af.first)["unit"];
const int xp = lexical_cast_default<int>((**af.first)["experience"],0);
unit_type_map::iterator from_unit = unit_types.find(from);
unit_type_map::iterator to_unit = unit_types.find(to);
if(from_unit==unit_types.end()) {
lg::warn(lg::config) << "unknown unit " << from << " in advancefrom\n";
continue;
}
wassert(to_unit!=unit_types.end());
from_unit->second.add_advancement(to_unit->second,xp);
}
}
void game_data::clear()
{
movement_types.clear();
unit_types.clear();
races.clear();
}