1043 lines
29 KiB
C++
1043 lines
29 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
|
|
or at your option any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
//! @file unit_types.cpp
|
|
//! Handle unit-type specific attributes, animations, advancement.
|
|
|
|
#include "global.hpp"
|
|
|
|
#include "game_config.hpp"
|
|
#include "gettext.hpp"
|
|
#include "loadscreen.hpp"
|
|
#include "log.hpp"
|
|
#include "unit_types.hpp"
|
|
#include "util.hpp"
|
|
#include "wassert.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
#include "color_range.hpp"
|
|
#include "game_display.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
|
|
|
|
|
|
attack_type::attack_type(const config& cfg,const std::string& id, bool with_animations)
|
|
{
|
|
cfg_ = cfg;
|
|
if(cfg["range"] == "long" || cfg["range"] == "ranged") {
|
|
range_type_ = LONG_RANGE;
|
|
} else {
|
|
range_type_ = SHORT_RANGE;
|
|
}
|
|
if (with_animations) {
|
|
const config expanded_cfg = unit_animation::prepare_animation(cfg,"animation");
|
|
// TODO: prepare animation should be privatized once the code is removed
|
|
const config::child_list& animations = expanded_cfg.get_children("animation");
|
|
for(config::child_list::const_iterator d = animations.begin(); d != animations.end(); ++d) {
|
|
lg::wml_error<<"attack animation directly in attack is deprecated, support will be removed in 1.3.10 (in unit "<<id<<")\n";
|
|
lg::wml_error<<"please put it with an [attack_anim] tag in the [unit] and filter on the attack name\n";
|
|
animation_.push_back(unit_animation(**d));
|
|
animation_.back().back_compat_add_name(cfg["name"]);
|
|
}
|
|
if(cfg.child("frame") || cfg.child("missile_frame") || cfg.child("sound")) {
|
|
lg::wml_error<<"using frame directly in attack is VERY deprecated, support will be removed in 1.3.10 (in unit "<<id<<")\n";
|
|
if(animation_.empty()) {
|
|
animation_.push_back(unit_animation(cfg));
|
|
animation_.back().back_compat_add_name(cfg["name"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
id_ = cfg["name"];
|
|
description_ = cfg["description"];
|
|
if (description_.empty())
|
|
description_ = egettext(id_.c_str());
|
|
|
|
type_ = cfg["type"];
|
|
icon_ = cfg["icon"];
|
|
if(icon_.empty()){
|
|
if (id_ != "")
|
|
icon_ = "attacks/" + id_ + ".png";
|
|
else
|
|
icon_ = "attacks/blank-attack.png";
|
|
}
|
|
|
|
range_ = cfg["range"].base_str();
|
|
damage_ = atol(cfg["damage"].c_str());
|
|
num_attacks_ = atol(cfg["number"].c_str());
|
|
|
|
attack_weight_ = lexical_cast_default<double>(cfg["attack_weight"],1.0);
|
|
defense_weight_ = lexical_cast_default<double>(cfg["defense_weight"],1.0);
|
|
|
|
gamedata_=NULL;
|
|
unitmap_=NULL;
|
|
map_=NULL;
|
|
game_status_=NULL;
|
|
teams_=NULL;
|
|
other_attack_=NULL;
|
|
|
|
}
|
|
|
|
|
|
bool attack_type::matches_filter(const config& cfg,bool self) const
|
|
{
|
|
const std::vector<std::string>& filter_range = utils::split(cfg["range"]);
|
|
const std::vector<std::string> filter_name = utils::split(cfg["name"]);
|
|
const std::vector<std::string> filter_type = utils::split(cfg["type"]);
|
|
const std::string filter_special = cfg["special"];
|
|
|
|
if(filter_range.empty() == false && std::find(filter_range.begin(),filter_range.end(),range()) == filter_range.end())
|
|
return false;
|
|
|
|
if(filter_name.empty() == false && std::find(filter_name.begin(),filter_name.end(),id()) == filter_name.end())
|
|
return false;
|
|
|
|
if(filter_type.empty() == false && std::find(filter_type.begin(),filter_type.end(),type()) == filter_type.end())
|
|
return false;
|
|
|
|
if(!self && filter_special.empty() == false && !get_special_bool(filter_special,true))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool attack_type::apply_modification(const config& cfg,std::string* description)
|
|
{
|
|
if(!matches_filter(cfg,0))
|
|
return false;
|
|
|
|
const std::string& set_name = cfg["set_name"];
|
|
const t_string& set_desc = cfg["set_description"];
|
|
const std::string& set_type = cfg["set_type"];
|
|
const std::string& del_specials = cfg["remove_specials"];
|
|
const config* set_specials = cfg.child("set_specials");
|
|
const std::string& increase_damage = cfg["increase_damage"];
|
|
const std::string& increase_attacks = cfg["increase_attacks"];
|
|
const std::string& set_attack_weight = cfg["attack_weight"];
|
|
const std::string& set_defense_weight = cfg["defense_weight"];
|
|
|
|
std::stringstream desc;
|
|
|
|
if(set_name.empty() == false) {
|
|
id_ = set_name;
|
|
}
|
|
|
|
if(set_desc.empty() == false) {
|
|
description_ = set_desc;
|
|
}
|
|
|
|
if(set_type.empty() == false) {
|
|
type_ = set_type;
|
|
}
|
|
|
|
if(del_specials.empty() == false) {
|
|
const std::vector<std::string>& dsl = utils::split(del_specials);
|
|
config* specials = cfg_.child("specials");
|
|
if (specials != NULL) {
|
|
config new_specials;
|
|
for(config::all_children_iterator s = specials->ordered_begin(); s != specials->ordered_end(); ++s) {
|
|
const std::pair<const std::string*,const config*>& vp = *s;
|
|
std::vector<std::string>::const_iterator found_id =
|
|
std::find(dsl.begin(),dsl.end(),vp.second->get_attribute("id"));
|
|
if (found_id == dsl.end()) {
|
|
new_specials.add_child(*vp.first,*vp.second);
|
|
}
|
|
}
|
|
cfg_.clear_children("specials");
|
|
cfg_.add_child("specials",new_specials);
|
|
}
|
|
}
|
|
|
|
if (set_specials) {
|
|
const std::string& mode = set_specials->get_attribute("mode");
|
|
if ( mode != "append") {
|
|
cfg_.clear_children("specials");
|
|
}
|
|
config* new_specials = cfg_.child("specials");
|
|
if (new_specials == NULL) {
|
|
cfg_.add_child("specials");
|
|
new_specials = cfg_.child("specials");
|
|
}
|
|
for(config::all_children_iterator s = set_specials->ordered_begin(); s != set_specials->ordered_end(); ++s) {
|
|
const std::pair<const std::string*,const config*>& value = *s;
|
|
new_specials->add_child(*value.first,*value.second);
|
|
}
|
|
}
|
|
|
|
if(increase_damage.empty() == false) {
|
|
damage_ = utils::apply_modifier(damage_, increase_damage, 1);
|
|
|
|
if(description != NULL) {
|
|
desc << (increase_damage[0] == '-' ? "" : "+") << increase_damage << " " << _("damage");
|
|
}
|
|
}
|
|
|
|
if(increase_attacks.empty() == false) {
|
|
num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
|
|
|
|
if(description != NULL) {
|
|
desc << (increase_attacks[0] == '-' ? "" : "+") << increase_attacks << " " << _("strikes");
|
|
}
|
|
}
|
|
|
|
if(set_attack_weight.empty() == false) {
|
|
attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
|
|
}
|
|
|
|
if(set_defense_weight.empty() == false) {
|
|
defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
|
|
}
|
|
|
|
if(description != NULL) {
|
|
*description = desc.str();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Same as above, except only update the descriptions
|
|
bool attack_type::describe_modification(const config& cfg,std::string* description)
|
|
{
|
|
if(!matches_filter(cfg,0))
|
|
return false;
|
|
|
|
const std::string& increase_damage = cfg["increase_damage"];
|
|
const std::string& increase_attacks = cfg["increase_attacks"];
|
|
|
|
std::stringstream desc;
|
|
|
|
if(increase_damage.empty() == false) {
|
|
if(description != NULL) {
|
|
desc << (increase_damage[0] == '-' ? "" : "+") << increase_damage << " " << _("damage");
|
|
}
|
|
}
|
|
|
|
if(increase_attacks.empty() == false) {
|
|
if(description != NULL) {
|
|
desc << (increase_attacks[0] == '-' ? "" : "+") << increase_attacks << " " << _("strikes");
|
|
}
|
|
}
|
|
|
|
if(description != NULL) {
|
|
*description = desc.str();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool attack_type::has_special_by_id(const std::string& special) const
|
|
{
|
|
const config* abil = cfg_.child("specials");
|
|
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"] == special) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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,
|
|
t_translation::t_letter terrain, int recurse_count) const
|
|
{
|
|
const int impassable = 10000000;
|
|
|
|
const std::map<t_translation::t_letter, 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 t_translation::t_list& underlying = map.underlying_mvt_terrain(terrain);
|
|
if(underlying.size() != 1 || underlying.front() != 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(map,*i,recurse_count+1);
|
|
if(value < ret_value && !revert) {
|
|
ret_value = value;
|
|
} else if(value > ret_value && revert) {
|
|
ret_value = value;
|
|
}
|
|
}
|
|
|
|
moveCosts_.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 && parent_ != NULL) {
|
|
res = parent_->movement_cost(map,terrain);
|
|
}
|
|
|
|
if(res <= 0) {
|
|
res = impassable;
|
|
}
|
|
|
|
moveCosts_.insert(std::pair<t_translation::t_letter, int>(terrain,res));
|
|
|
|
return res;
|
|
}
|
|
|
|
int unit_movement_type::defense_modifier(const gamemap& map,
|
|
t_translation::t_letter terrain, int recurse_count) const
|
|
{
|
|
const std::map<t_translation::t_letter, 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 t_translation::t_list& underlying =
|
|
map.underlying_def_terrain(terrain);
|
|
|
|
if(underlying.size() != 1 || underlying.front() != terrain) {
|
|
bool revert = (underlying.front() == t_translation::MINUS ? true : false);
|
|
if(recurse_count >= 100) {
|
|
return 100;
|
|
}
|
|
|
|
int ret_value = revert?0:100;
|
|
for(t_translation::t_list::const_iterator i = underlying.begin();
|
|
i != underlying.end(); ++i) {
|
|
|
|
if(*i == t_translation::PLUS) {
|
|
revert = false;
|
|
continue;
|
|
} else if(*i == t_translation::MINUS) {
|
|
revert = true;
|
|
continue;
|
|
}
|
|
const int value = defense_modifier(map,*i,recurse_count+1);
|
|
if(value < ret_value && !revert) {
|
|
ret_value = value;
|
|
} else if(value > ret_value && revert) {
|
|
ret_value = value;
|
|
}
|
|
}
|
|
|
|
defenseMods_.insert(std::pair<t_translation::t_letter, 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 && parent_ != NULL) {
|
|
res = parent_->defense_modifier(map,terrain);
|
|
}
|
|
|
|
if(res <= 0) {
|
|
res = 50;
|
|
}
|
|
|
|
defenseMods_.insert(std::pair<t_translation::t_letter, int>(terrain, res));
|
|
|
|
return res;
|
|
}
|
|
|
|
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 utils::string_bool(flies);
|
|
}
|
|
|
|
unit_type::unit_type(const unit_type& o)
|
|
: variations_(o.variations_), cfg_(o.cfg_), race_(o.race_),
|
|
alpha_(o.alpha_), abilities_(o.abilities_),ability_tooltips_(o.ability_tooltips_),
|
|
hide_help_(o.hide_help_), advances_to_(o.advances_to_), advances_from_(o.advances_from_),
|
|
experience_needed_(o.experience_needed_), alignment_(o.alignment_),
|
|
movementType_(o.movementType_), possibleTraits_(o.possibleTraits_),
|
|
genders_(o.genders_), animations_(o.animations_),
|
|
|
|
flag_rgb_(o.flag_rgb_)
|
|
{
|
|
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)
|
|
{
|
|
config::const_child_iterator i;
|
|
for(i=traits.begin(); i != traits.end(); ++i)
|
|
{
|
|
possibleTraits_.add_child("trait", **i);
|
|
}
|
|
const config::child_list& variations = cfg.get_children("variation");
|
|
for(config::child_list::const_iterator var = variations.begin(); var != variations.end(); ++var) {
|
|
const config& var_cfg = **var;
|
|
if(var_cfg["inherit"] == "yes") {
|
|
config nvar_cfg(cfg);
|
|
nvar_cfg.merge_with(var_cfg);
|
|
nvar_cfg.clear_children("variation");
|
|
variations_.insert(std::pair<std::string,unit_type*>(nvar_cfg["variation_name"],
|
|
new unit_type(nvar_cfg,mv_types,races,traits)));
|
|
} else {
|
|
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) {
|
|
config m_cfg;
|
|
if((*male_cfg)["inherit"]=="no") {
|
|
m_cfg = *male_cfg;
|
|
} else {
|
|
m_cfg = cfg;
|
|
m_cfg.merge_with(*male_cfg);
|
|
}
|
|
m_cfg.clear_children("male");
|
|
m_cfg.clear_children("female");
|
|
gender_types_[unit_race::MALE] = new unit_type(m_cfg,mv_types,races,traits);
|
|
}
|
|
|
|
const config* const female_cfg = cfg.child("female");
|
|
if(female_cfg != NULL) {
|
|
config f_cfg;
|
|
if((*female_cfg)["inherit"]=="no") {
|
|
f_cfg = *female_cfg;
|
|
} else {
|
|
f_cfg = cfg;
|
|
f_cfg.merge_with(*female_cfg);
|
|
}
|
|
f_cfg.clear_children("male");
|
|
f_cfg.clear_children("female");
|
|
gender_types_[unit_race::FEMALE] = new unit_type(f_cfg,mv_types,races,traits);
|
|
}
|
|
|
|
const std::vector<std::string> genders = utils::split(cfg["gender"]);
|
|
for(std::vector<std::string>::const_iterator g = genders.begin(); g != genders.end(); ++g) {
|
|
genders_.push_back(string_gender(*g));
|
|
}
|
|
if(genders_.empty()) {
|
|
genders_.push_back(unit_race::MALE);
|
|
}
|
|
|
|
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 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();
|
|
}
|
|
if(utils::string_bool(cfg["ignore_race_traits"])) {
|
|
possibleTraits_.clear();
|
|
} else {
|
|
const config::child_list& traits = race_->additional_traits();
|
|
for(i=traits.begin(); i != traits.end(); ++i)
|
|
{
|
|
if(alignment_ != NEUTRAL || ((**i)["id"]) != "fearless")
|
|
possibleTraits_.add_child("trait", **i);
|
|
}
|
|
}
|
|
}
|
|
} 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");
|
|
for(i=unit_traits.begin(); i != unit_traits.end(); ++i)
|
|
{
|
|
possibleTraits_.add_child("trait", **i);
|
|
}
|
|
|
|
const config* abil_cfg = cfg.child("abilities");
|
|
if(abil_cfg) {
|
|
const config::child_map& abi = abil_cfg->all_children();
|
|
for(config::child_map::const_iterator j = abi.begin(); j != abi.end(); ++j) {
|
|
for(config::child_list::const_iterator k = j->second.begin(); k != j->second.end(); ++k) {
|
|
if((**k)["name"] != "") {
|
|
abilities_.push_back((**k)["name"]);
|
|
ability_tooltips_.push_back((**k)["description"]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(cfg_["zoc"] == "") {
|
|
zoc_ = lexical_cast_default<int>(cfg_["level"]) > 0;
|
|
} else {
|
|
zoc_ = false;
|
|
if(utils::string_bool(cfg_["zoc"])) {
|
|
zoc_ = true;
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
unit_animation::initialize_anims(animations_,cfg,attacks(true));
|
|
flag_rgb_ = cfg["flag_rgb"];
|
|
game_config::add_color_info(cfg);
|
|
// Deprecation messages, only seen when unit is parsed for the first time.
|
|
|
|
hide_help_= cfg_["hide_help"] == "true" ? true : false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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_;
|
|
}
|
|
|
|
|
|
#if 0
|
|
const std::string& unit_type::name() const
|
|
{
|
|
return cfg_["id"];
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
const std::string& unit_type::image_profile() const
|
|
{
|
|
const std::string& val = cfg_["profile"];
|
|
if(val.size() == 0)
|
|
return image();
|
|
else
|
|
return val;
|
|
}
|
|
|
|
const t_string& unit_type::unit_description() const
|
|
{
|
|
static const t_string default_val("No description available");
|
|
|
|
const t_string& desc = cfg_["unit_description"];
|
|
if(desc.empty())
|
|
return default_val;
|
|
else
|
|
return desc;
|
|
}
|
|
|
|
|
|
std::vector<attack_type> unit_type::attacks(bool with_animations) 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,id(), with_animations));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
namespace {
|
|
int experience_modifier = 100;
|
|
}
|
|
|
|
unit_type::experience_accelerator::experience_accelerator(int modifier) : old_value_(experience_modifier)
|
|
{
|
|
experience_modifier = modifier;
|
|
}
|
|
|
|
unit_type::experience_accelerator::~experience_accelerator()
|
|
{
|
|
experience_modifier = old_value_;
|
|
}
|
|
|
|
int unit_type::experience_accelerator::get_acceleration()
|
|
{
|
|
return experience_modifier;
|
|
}
|
|
|
|
int unit_type::experience_needed(bool with_acceleration) const
|
|
{
|
|
if(with_acceleration) {
|
|
int exp = (experience_needed_ * experience_modifier + 50) /100;
|
|
if(exp < 1) exp = 1;
|
|
return exp;
|
|
}
|
|
return experience_needed_;
|
|
}
|
|
|
|
|
|
|
|
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]);
|
|
}
|
|
|
|
bool unit_type::has_ability(const std::string& ability) const
|
|
{
|
|
const config* abil = cfg_.child("abilities");
|
|
if(abil) {
|
|
return (abil->get_children(ability).size() > 0);
|
|
}
|
|
return false;
|
|
}
|
|
bool unit_type::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;
|
|
}
|
|
|
|
|
|
const std::string& unit_type::race() const
|
|
{
|
|
if(race_ == NULL) {
|
|
static const std::string empty_string;
|
|
return empty_string;
|
|
}
|
|
|
|
return race_->id();
|
|
}
|
|
|
|
// Allow storing "advances from" info for convenience in Help.
|
|
void unit_type::add_advancesfrom(const unit_type &from_unit)
|
|
{
|
|
const std::string &from_id = from_unit.cfg_["id"];
|
|
advances_from_.push_back(from_id);
|
|
}
|
|
|
|
|
|
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");
|
|
|
|
config::const_child_itors i;
|
|
for(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));
|
|
increment_set_config_progress();
|
|
}
|
|
|
|
for(i = cfg.child_range("race"); i.first != i.second; ++i.first)
|
|
{
|
|
const unit_race race(**i.first);
|
|
races.insert(std::pair<std::string,unit_race>(race.id(),race));
|
|
increment_set_config_progress();
|
|
}
|
|
|
|
unsigned base_unit_count = 0;
|
|
for(i = cfg.child_range("unit"); i.first != i.second; ++i.first)
|
|
{
|
|
if((**i.first).child("base_unit"))
|
|
{
|
|
++base_unit_count;
|
|
}
|
|
else
|
|
{
|
|
// LOAD UNIT TYPES
|
|
const unit_type u_type(**i.first,movement_types,races,unit_traits);
|
|
unit_types.insert(std::pair<std::string,unit_type>(u_type.id(),u_type));
|
|
increment_set_config_progress();
|
|
}
|
|
}
|
|
while(base_unit_count > 0)
|
|
{
|
|
unsigned new_count = base_unit_count;
|
|
std::string skipped;
|
|
for(i = cfg.child_range("unit"); i.first != i.second; ++i.first)
|
|
{
|
|
const config *bu_cfg = (**i.first).child("base_unit");
|
|
if(bu_cfg)
|
|
{
|
|
const std::string &based_from = (*bu_cfg)["id"];
|
|
unit_type_map::iterator from_unit = unit_types.find(based_from);
|
|
if(from_unit != unit_types.end())
|
|
{
|
|
// Derive a new unit type from an existing base unit id
|
|
config merge_cfg = merged_units.add_child(based_from, from_unit->second.cfg_);
|
|
merge_cfg.merge_with(**i.first);
|
|
merge_cfg.clear_children("base_unit");
|
|
const unit_type u_type(merge_cfg,movement_types,races,unit_traits);
|
|
unit_types.insert(std::pair<std::string,unit_type>(u_type.id(),u_type));
|
|
increment_set_config_progress();
|
|
--new_count;
|
|
}
|
|
else if(skipped.empty())
|
|
{
|
|
skipped = based_from;
|
|
}
|
|
else
|
|
{
|
|
skipped += ',' + based_from;
|
|
}
|
|
}
|
|
}
|
|
// If we iterate through the whole list and no work was done, an error has occurred
|
|
if(new_count >= base_unit_count)
|
|
{
|
|
lg::warn(lg::config) << "unknown unit(s) " << skipped
|
|
<< " specified in [base_unit] id(s)\n";
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
base_unit_count = new_count;
|
|
}
|
|
}
|
|
|
|
// Fix up advance_from references
|
|
for(i = cfg.child_range("unit"); i.first != i.second; ++i.first)
|
|
{
|
|
config::const_child_itors af;
|
|
for(af = (*i.first)->child_range("advancefrom"); af.first != af.second; ++af.first)
|
|
{
|
|
const std::string &to = (**i.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);
|
|
increment_set_config_progress();
|
|
}
|
|
}
|
|
|
|
// For all unit types, store what units they advance from
|
|
unit_type_map::iterator from_unit;
|
|
for(from_unit = unit_types.begin(); from_unit != unit_types.end(); ++from_unit)
|
|
{
|
|
std::vector<std::string> to_units_ids = from_unit->second.advances_to();
|
|
std::vector<std::string>::iterator to_unit_id;
|
|
for(to_unit_id = to_units_ids.begin(); to_unit_id != to_units_ids.end(); ++to_unit_id)
|
|
{
|
|
unit_type_map::iterator to_unit = unit_types.find(*to_unit_id);
|
|
if(to_unit != unit_types.end())
|
|
{
|
|
to_unit->second.add_advancesfrom(from_unit->second);
|
|
}
|
|
else
|
|
{
|
|
lg::warn(lg::config) << "unknown unit " << *to_unit_id
|
|
<< " advanced to by unit " << from_unit->first << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void game_data::clear()
|
|
{
|
|
movement_types.clear();
|
|
unit_types.clear();
|
|
merged_units.clear();
|
|
races.clear();
|
|
}
|
|
|
|
// This function is only meant to return the likely state of not_living
|
|
// for a new recruit of this type. It should not be used to check if
|
|
// a particular unit is living or not, use get_state("not_living") for that.
|
|
|
|
bool unit_type::not_living() const
|
|
{
|
|
// If a unit hasn't been modified it starts out as living.
|
|
bool not_living = false;
|
|
|
|
// Look at all of the "musthave" traits to see if the not_living
|
|
// status gets changed. In the unlikely event it gets changed
|
|
// multiple times, we want to try to do it in the same order
|
|
// that unit::apply_modifications does things.
|
|
config::child_list const &mods = possible_traits();
|
|
config::child_list::const_iterator j, j_end = mods.end();
|
|
for(j = mods.begin(); j != j_end; ++j) {
|
|
const string_map *vals = &((**j).values);
|
|
string_map::const_iterator temp = vals->find("availability");
|
|
if (temp == vals->end() || (*temp).second != "musthave") {
|
|
continue;
|
|
}
|
|
config::const_child_itors i;
|
|
for(i = (**j).child_range("effect"); i.first != i.second; ++i.first) {
|
|
|
|
// See if the effect only applies to certain unit types
|
|
// But don't worry about gender checks, since we don't
|
|
// know what the gender of the hypothetical recruit is.
|
|
vals = &((**i.first).values);
|
|
temp = vals->find("unit_type");
|
|
if(temp != vals->end()) {
|
|
const std::vector<std::string>& types = utils::split((*temp).second);
|
|
if(std::find(types.begin(),types.end(),id()) == types.end()) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// We're only interested in status changes.
|
|
temp = vals->find("apply_to");
|
|
if (temp == vals->end() || (*temp).second != "status") {
|
|
continue;
|
|
}
|
|
temp = vals->find("add");
|
|
if (temp != vals->end() && (*temp).second == "not_living") {
|
|
not_living = true;
|
|
}
|
|
temp = vals->find("remove");
|
|
if (temp != vals->end() && (*temp).second == "not_living") {
|
|
not_living = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return not_living;
|
|
}
|