mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-18 13:35:02 +00:00
525 lines
16 KiB
C++
525 lines
16 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
#define GETTEXT_DOMAIN "wesnoth-lib"
|
|
|
|
#include "global.hpp"
|
|
|
|
#include "actions.hpp"
|
|
#include "config.hpp"
|
|
#include "foreach.hpp"
|
|
#include "log.hpp"
|
|
#include "map.hpp"
|
|
#include "resources.hpp"
|
|
#include "side_filter.hpp"
|
|
#include "team.hpp"
|
|
#include "terrain_filter.hpp"
|
|
#include "tod_manager.hpp"
|
|
#include "variable.hpp"
|
|
|
|
static lg::log_domain log_engine("engine");
|
|
#define ERR_NG LOG_STREAM(err, log_engine)
|
|
#define WRN_NG LOG_STREAM(warn, log_engine)
|
|
|
|
terrain_filter::~terrain_filter()
|
|
{
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
// This is a workaround for a VC bug; this constructor is never called
|
|
// and so we don't care about the warnings this quick fix generates
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4413)
|
|
terrain_filter::terrain_filter():
|
|
cfg_(vconfig::unconstructed_vconfig()),
|
|
units_(unit_map()),
|
|
cache_(),
|
|
max_loop_(),
|
|
flat_()
|
|
{
|
|
assert(false);
|
|
}
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
|
|
terrain_filter::terrain_filter(const vconfig& cfg, const unit_map& units,
|
|
const bool flat_tod, const size_t max_loop) :
|
|
cfg_(cfg),
|
|
units_(units),
|
|
cache_(),
|
|
max_loop_(max_loop),
|
|
flat_(flat_tod)
|
|
{
|
|
}
|
|
|
|
terrain_filter::terrain_filter(const vconfig& cfg, const terrain_filter& original) :
|
|
cfg_(cfg),
|
|
units_(original.units_),
|
|
cache_(),
|
|
max_loop_(original.max_loop_),
|
|
flat_(original.flat_)
|
|
{
|
|
}
|
|
|
|
terrain_filter::terrain_filter(const terrain_filter& other) :
|
|
xy_pred(), // We should construct this too, since it has no datamembers
|
|
// use the default constructor.
|
|
cfg_(other.cfg_),
|
|
units_(other.units_),
|
|
cache_(),
|
|
max_loop_(other.max_loop_),
|
|
flat_(other.flat_)
|
|
{
|
|
}
|
|
|
|
terrain_filter& terrain_filter::operator=(const terrain_filter& other)
|
|
{
|
|
// Use copy constructor to make sure we are coherant
|
|
if (this != &other) {
|
|
this->~terrain_filter();
|
|
new (this) terrain_filter(other) ;
|
|
}
|
|
return *this ;
|
|
}
|
|
|
|
namespace {
|
|
struct cfg_isor {
|
|
bool operator() (std::pair<const std::string,const vconfig> val) {
|
|
return val.first == "or";
|
|
}
|
|
};
|
|
} //end anonymous namespace
|
|
|
|
bool terrain_filter::match_internal(const map_location& loc, const bool ignore_xy) const
|
|
{
|
|
if(cfg_.has_attribute("terrain")) {
|
|
if(cache_.parsed_terrain == NULL) {
|
|
cache_.parsed_terrain = new t_translation::t_match(cfg_["terrain"]);
|
|
}
|
|
if(!cache_.parsed_terrain->is_empty) {
|
|
const t_translation::t_terrain letter = resources::game_map->get_terrain_info(loc).number();
|
|
if(!t_translation::terrain_matches(letter, *cache_.parsed_terrain)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Allow filtering on location ranges
|
|
if(!ignore_xy) {
|
|
if(!loc.matches_range(cfg_["x"], cfg_["y"])) {
|
|
return false;
|
|
}
|
|
//allow filtering by searching a stored variable of locations
|
|
if(cfg_.has_attribute("find_in")) {
|
|
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
|
|
if(!vi.is_valid) return false;
|
|
if(vi.explicit_index) {
|
|
if(map_location(vi.as_container(),NULL) != loc) {
|
|
return false;
|
|
}
|
|
} else {
|
|
bool found = false;
|
|
foreach (const config &cfg, vi.as_array()) {
|
|
if (map_location(cfg, NULL) == loc) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Allow filtering on unit
|
|
if(cfg_.has_child("filter")) {
|
|
const vconfig& unit_filter = cfg_.child("filter");
|
|
const unit_map::const_iterator u = units_.find(loc);
|
|
if (u == units_.end() || !u->matches_filter(unit_filter, loc, flat_))
|
|
return false;
|
|
}
|
|
|
|
// Allow filtering on visibility to a side
|
|
if (cfg_.has_child("filter_vision")) {
|
|
const vconfig::child_list& vis_filt = cfg_.get_children("filter_vision");
|
|
vconfig::child_list::const_iterator i, i_end = vis_filt.end();
|
|
for (i = vis_filt.begin(); i != i_end; ++i) {
|
|
bool visible = (*i)["visible"].to_bool(true);
|
|
bool respect_fog = (*i)["respect_fog"].to_bool(true);
|
|
|
|
side_filter ssf(*i);
|
|
std::vector<int> sides = ssf.get_teams();
|
|
|
|
foreach(const int side, sides) {
|
|
const team &viewing_team = resources::teams->at(side - 1);
|
|
bool viewer_sees = respect_fog ? !viewing_team.fogged(loc) : !viewing_team.shrouded(loc);
|
|
if (visible != viewer_sees) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Allow filtering on adjacent locations
|
|
if(cfg_.has_child("filter_adjacent_location")) {
|
|
map_location adjacent[6];
|
|
get_adjacent_tiles(loc, adjacent);
|
|
const vconfig::child_list& adj_cfgs = cfg_.get_children("filter_adjacent_location");
|
|
vconfig::child_list::const_iterator i, i_end, i_begin = adj_cfgs.begin();
|
|
for (i = i_begin, i_end = adj_cfgs.end(); i != i_end; ++i) {
|
|
int match_count = 0;
|
|
vconfig::child_list::difference_type index = i - i_begin;
|
|
static std::vector<map_location::DIRECTION> default_dirs
|
|
= map_location::parse_directions("n,ne,se,s,sw,nw");
|
|
std::vector<map_location::DIRECTION> dirs = (*i).has_attribute("adjacent")
|
|
? map_location::parse_directions((*i)["adjacent"]) : default_dirs;
|
|
std::vector<map_location::DIRECTION>::const_iterator j, j_end = dirs.end();
|
|
for (j = dirs.begin(); j != j_end; ++j) {
|
|
map_location &adj = adjacent[*j];
|
|
if (resources::game_map->on_board(adj)) {
|
|
if(cache_.adjacent_matches == NULL) {
|
|
while(index >= std::distance(cache_.adjacent_match_cache.begin(), cache_.adjacent_match_cache.end())) {
|
|
const vconfig& adj_cfg = adj_cfgs[cache_.adjacent_match_cache.size()];
|
|
std::pair<terrain_filter, std::map<map_location,bool> > amc_pair(
|
|
terrain_filter(adj_cfg, *this),
|
|
std::map<map_location,bool>());
|
|
cache_.adjacent_match_cache.push_back(amc_pair);
|
|
}
|
|
terrain_filter &amc_filter = cache_.adjacent_match_cache[index].first;
|
|
std::map<map_location,bool> &amc = cache_.adjacent_match_cache[index].second;
|
|
std::map<map_location,bool>::iterator lookup = amc.find(adj);
|
|
if(lookup == amc.end()) {
|
|
if(amc_filter(adj)) {
|
|
amc[adj] = true;
|
|
++match_count;
|
|
} else {
|
|
amc[adj] = false;
|
|
}
|
|
} else if(lookup->second) {
|
|
++match_count;
|
|
}
|
|
} else {
|
|
assert(index < std::distance(cache_.adjacent_matches->begin(), cache_.adjacent_matches->end()));
|
|
std::set<map_location> &amc = (*cache_.adjacent_matches)[index];
|
|
if(amc.find(adj) != amc.end()) {
|
|
++match_count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-6");
|
|
std::vector<std::pair<int,int> > counts = (*i).has_attribute("count")
|
|
? utils::parse_ranges((*i)["count"]) : default_counts;
|
|
if(!in_ranges(match_count, counts)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const t_string& t_tod_type = cfg_["time_of_day"];
|
|
const t_string& t_tod_id = cfg_["time_of_day_id"];
|
|
const std::string& tod_type = t_tod_type;
|
|
const std::string& tod_id = t_tod_id;
|
|
static config const dummy_cfg;
|
|
time_of_day tod(dummy_cfg);
|
|
if(!tod_type.empty() || !tod_id.empty()) {
|
|
if(flat_) {
|
|
tod = resources::tod_manager->get_time_of_day(loc);
|
|
} else {
|
|
tod = resources::tod_manager->get_illuminated_time_of_day(loc);
|
|
}
|
|
}
|
|
if(!tod_type.empty()) {
|
|
const std::vector<std::string>& vals = utils::split(tod_type);
|
|
if(tod.lawful_bonus<0) {
|
|
if(std::find(vals.begin(),vals.end(),std::string("chaotic")) == vals.end()) {
|
|
return false;
|
|
}
|
|
} else if(tod.lawful_bonus>0) {
|
|
if(std::find(vals.begin(),vals.end(),std::string("lawful")) == vals.end()) {
|
|
return false;
|
|
}
|
|
} else if(std::find(vals.begin(),vals.end(),std::string("neutral")) == vals.end()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!tod_id.empty()) {
|
|
if(tod_id != tod.id) {
|
|
if(std::find(tod_id.begin(),tod_id.end(),',') != tod_id.end() &&
|
|
std::search(tod_id.begin(),tod_id.end(),
|
|
tod.id.begin(),tod.id.end()) != tod_id.end()) {
|
|
const std::vector<std::string>& vals = utils::split(tod_id);
|
|
if(std::find(vals.begin(),vals.end(),tod.id) == vals.end()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//allow filtering on owner (for villages)
|
|
const std::string owner_side = cfg_["owner_side"].str();
|
|
const vconfig& filter_owner = cfg_.child("filter_owner");
|
|
if(!filter_owner.null()) {
|
|
if(!owner_side.empty()) {
|
|
WRN_NG << "duplicate side information in a SLF, ignoring inline owner_side=\n";
|
|
}
|
|
if(!resources::game_map->is_village(loc))
|
|
return false;
|
|
side_filter ssf(filter_owner);
|
|
const std::vector<int>& sides = ssf.get_teams();
|
|
bool found = false;
|
|
if(sides.empty() && village_owner(loc, *resources::teams) == -1)
|
|
found = true;
|
|
foreach(const int side, sides) {
|
|
if(resources::teams->at(side - 1).owns_village(loc)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found)
|
|
return false;
|
|
}
|
|
else if(!owner_side.empty()) {
|
|
const int side_index = lexical_cast_default<int>(owner_side,0) - 1;
|
|
if(village_owner(loc, *resources::teams) != side_index) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool terrain_filter::match(const map_location& loc) const
|
|
{
|
|
if(cfg_["x"] == "recall" && cfg_["y"] == "recall") {
|
|
return !resources::game_map->on_board(loc);
|
|
}
|
|
std::set<map_location> hexes;
|
|
std::vector<map_location> loc_vec(1, loc);
|
|
|
|
//handle radius
|
|
size_t radius = lexical_cast_default<size_t>(cfg_["radius"], 0);
|
|
if(radius > max_loop_) {
|
|
ERR_NG << "terrain_filter: radius greater than " << max_loop_
|
|
<< ", restricting\n";
|
|
radius = max_loop_;
|
|
}
|
|
if(cfg_.has_child("filter_radius")) {
|
|
terrain_filter r_filter(cfg_.child("filter_radius"), *this);
|
|
get_tiles_radius(*resources::game_map, loc_vec, radius, hexes, false, &r_filter);
|
|
} else {
|
|
get_tiles_radius(*resources::game_map, loc_vec, radius, hexes);
|
|
}
|
|
|
|
size_t loop_count = 0;
|
|
std::set<map_location>::const_iterator i;
|
|
for(i = hexes.begin(); i != hexes.end(); ++i) {
|
|
bool matches = match_internal(*i, false);
|
|
|
|
//handle [and], [or], and [not] with in-order precedence
|
|
vconfig::all_children_iterator cond = cfg_.ordered_begin();
|
|
vconfig::all_children_iterator cond_end = cfg_.ordered_end();
|
|
while(cond != cond_end)
|
|
{
|
|
const std::string& cond_name = cond.get_key();
|
|
const vconfig& cond_cfg = cond.get_child();
|
|
|
|
//handle [and]
|
|
if(cond_name == "and")
|
|
{
|
|
matches = matches && terrain_filter(cond_cfg, *this)(*i);
|
|
}
|
|
//handle [or]
|
|
else if(cond_name == "or")
|
|
{
|
|
matches = matches || terrain_filter(cond_cfg, *this)(*i);
|
|
}
|
|
//handle [not]
|
|
else if(cond_name == "not")
|
|
{
|
|
matches = matches && !terrain_filter(cond_cfg, *this)(*i);
|
|
}
|
|
++cond;
|
|
}
|
|
if(matches) {
|
|
return true;
|
|
}
|
|
if(++loop_count > max_loop_) {
|
|
std::set<map_location>::const_iterator temp = i;
|
|
if(++temp != hexes.end()) {
|
|
ERR_NG << "terrain_filter: loop count greater than " << max_loop_
|
|
<< ", aborting\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void terrain_filter::get_locations(std::set<map_location>& locs, bool with_border) const
|
|
{
|
|
std::vector<map_location> xy_vector =
|
|
parse_location_range(cfg_["x"], cfg_["y"], with_border);
|
|
std::set<map_location> xy_set(xy_vector.begin(), xy_vector.end());
|
|
if (!cfg_.has_attribute("x") && !cfg_.has_attribute("y")) {
|
|
//consider all locations on the map
|
|
int bs = resources::game_map->border_size();
|
|
int w = with_border ? resources::game_map->w() + bs : resources::game_map->w();
|
|
int h = with_border ? resources::game_map->h() + bs : resources::game_map->h();
|
|
for (int x = with_border ? 0 - bs : 0; x < w; ++x) {
|
|
for (int y = with_border ? 0 - bs : 0; y < h; ++y) {
|
|
xy_set.insert(map_location(x,y));
|
|
}
|
|
}
|
|
}
|
|
if(cfg_.has_attribute("find_in")) {
|
|
//remove any locations not found in the specified variable
|
|
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
|
|
if(!vi.is_valid) {
|
|
xy_set.clear();
|
|
} else if(vi.explicit_index) {
|
|
map_location test_loc(vi.as_container(),NULL);
|
|
if(xy_set.count(test_loc)) {
|
|
xy_set.clear();
|
|
xy_set.insert(test_loc);
|
|
} else {
|
|
xy_set.clear();
|
|
}
|
|
} else {
|
|
std::set<map_location> findin_locs;
|
|
foreach (const config &cfg, vi.as_array()) {
|
|
map_location test_loc(cfg, NULL);
|
|
if (xy_set.count(test_loc)) {
|
|
findin_locs.insert(test_loc);
|
|
}
|
|
}
|
|
xy_set.swap(findin_locs);
|
|
}
|
|
}
|
|
|
|
//handle location filter
|
|
if(cfg_.has_child("filter_adjacent_location")) {
|
|
if(cache_.adjacent_matches == NULL) {
|
|
cache_.adjacent_matches = new std::vector<std::set<map_location> >();
|
|
}
|
|
const vconfig::child_list& adj_cfgs = cfg_.get_children("filter_adjacent_location");
|
|
for (unsigned i = 0; i < adj_cfgs.size(); ++i) {
|
|
std::set<map_location> adj_set;
|
|
/* GCC-3.3 doesn't like operator[] so use at(), which has the same result */
|
|
terrain_filter(adj_cfgs.at(i), *this).get_locations(adj_set, with_border);
|
|
cache_.adjacent_matches->push_back(adj_set);
|
|
if(i >= max_loop_ && i+1 < adj_cfgs.size()) {
|
|
ERR_NG << "terrain_filter: loop count greater than " << max_loop_
|
|
<< ", aborting\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
std::set<map_location>::iterator loc_itor = xy_set.begin();
|
|
while(loc_itor != xy_set.end()) {
|
|
if(match_internal(*loc_itor, true)) {
|
|
++loc_itor;
|
|
} else {
|
|
xy_set.erase(loc_itor++);
|
|
}
|
|
}
|
|
|
|
//handle [and], [or], and [not] with in-order precedence
|
|
vconfig::all_children_iterator cond = cfg_.ordered_begin();
|
|
vconfig::all_children_iterator cond_end = cfg_.ordered_end();
|
|
int ors_left = std::count_if(cond, cond_end, cfg_isor());
|
|
while(cond != cond_end)
|
|
{
|
|
//if there are no locations or [or] conditions left, go ahead and return empty
|
|
if(xy_set.empty() && ors_left <= 0) {
|
|
return;
|
|
}
|
|
|
|
const std::string& cond_name = cond.get_key();
|
|
const vconfig& cond_cfg = cond.get_child();
|
|
|
|
//handle [and]
|
|
if(cond_name == "and") {
|
|
std::set<map_location> intersect_hexes;
|
|
terrain_filter(cond_cfg, *this).get_locations(intersect_hexes, with_border);
|
|
std::set<map_location>::iterator intersect_itor = xy_set.begin();
|
|
while(intersect_itor != xy_set.end()) {
|
|
if(intersect_hexes.find(*intersect_itor) == intersect_hexes.end()) {
|
|
xy_set.erase(*intersect_itor++);
|
|
} else {
|
|
++intersect_itor;
|
|
}
|
|
}
|
|
}
|
|
//handle [or]
|
|
else if(cond_name == "or") {
|
|
std::set<map_location> union_hexes;
|
|
terrain_filter(cond_cfg, *this).get_locations(union_hexes, with_border);
|
|
//xy_set.insert(union_hexes.begin(), union_hexes.end()); //doesn't compile on MSVC
|
|
std::set<map_location>::iterator insert_itor = union_hexes.begin();
|
|
while(insert_itor != union_hexes.end()) {
|
|
xy_set.insert(*insert_itor++);
|
|
}
|
|
--ors_left;
|
|
}
|
|
//handle [not]
|
|
else if(cond_name == "not") {
|
|
std::set<map_location> removal_hexes;
|
|
terrain_filter(cond_cfg, *this).get_locations(removal_hexes, with_border);
|
|
std::set<map_location>::iterator erase_itor = removal_hexes.begin();
|
|
while(erase_itor != removal_hexes.end()) {
|
|
xy_set.erase(*erase_itor++);
|
|
}
|
|
}
|
|
++cond;
|
|
}
|
|
if(xy_set.empty()) {
|
|
return;
|
|
}
|
|
|
|
//handle radius
|
|
size_t radius = lexical_cast_default<size_t>(cfg_["radius"], 0);
|
|
if(radius > max_loop_) {
|
|
ERR_NG << "terrain_filter: radius greater than " << max_loop_
|
|
<< ", restricting\n";
|
|
radius = max_loop_;
|
|
}
|
|
if(radius > 0) {
|
|
xy_vector.clear();
|
|
std::copy(xy_set.begin(),xy_set.end(),std::inserter(xy_vector,xy_vector.end()));
|
|
if(cfg_.has_child("filter_radius")) {
|
|
terrain_filter r_filter(cfg_.child("filter_radius"), *this);
|
|
get_tiles_radius(*resources::game_map, xy_vector, radius, locs, with_border, &r_filter);
|
|
} else {
|
|
get_tiles_radius(*resources::game_map, xy_vector, radius, locs, with_border);
|
|
}
|
|
} else {
|
|
std::copy(xy_set.begin(),xy_set.end(),std::inserter(locs,locs.end()));
|
|
}
|
|
}
|
|
|
|
config terrain_filter::to_config() const
|
|
{
|
|
return cfg_.get_config();
|
|
}
|
|
|
|
terrain_filter::terrain_filter_cache::~terrain_filter_cache() {
|
|
delete parsed_terrain;
|
|
delete adjacent_matches;
|
|
}
|