Move implementation of $teleport_unit into SLF so it can be passed to location formulas

This commit is contained in:
Celtic Minstrel 2017-06-20 00:20:25 -04:00
parent d072661c94
commit 1bb750f649
4 changed files with 85 additions and 25 deletions

View file

@ -34,6 +34,7 @@ Version 1.13.8+dev:
Game Version dialog's clipboard function to stdout.
* WFL Engine
* Add owner key to terrain space callable, for villages
* Location formulas in [tunnel] now have a teleport_unit variable
* WML Engine
* If ai_algorithm is used in [modify_side][ai], it now replaces the whole AI
with the contents of [modify_side][ai], instead of appending these parameters.

View file

@ -109,8 +109,6 @@ void teleport_group::get_teleport_pair(
, const unit& u
, const bool ignore_units) const
{
const map_location &loc = u.get_location();
const filter_context * fc = resources::filter_con;
assert(fc);
@ -122,15 +120,12 @@ void teleport_group::get_teleport_pair(
vconfig source(cfg_.child_or_empty("source"), true);
vconfig target(cfg_.child_or_empty("target"), true);
const unit_filter ufilt(filter, resources::filter_con); //Note: Don't use the ignore units filter context here, only for the terrain filters. (That's how it worked before the filter contexts were introduced)
if (ufilt.matches(u, loc)) {
scoped_xy_unit teleport_unit("teleport_unit", loc, resources::gameboard->units());
if (ufilt.matches(u)) {
terrain_filter source_filter(source, fc);
source_filter.get_locations(reversed_ ? loc_pair.second : loc_pair.first);
source_filter.get_locations(reversed_ ? loc_pair.second : loc_pair.first, u);
terrain_filter target_filter(target, fc);
target_filter.get_locations(reversed_ ? loc_pair.first : loc_pair.second);
target_filter.get_locations(reversed_ ? loc_pair.first : loc_pair.second, u);
}
if (ignore_units) {

View file

@ -97,7 +97,7 @@ namespace {
};
} //end anonymous namespace
bool terrain_filter::match_internal(const map_location& loc, const bool ignore_xy) const
bool terrain_filter::match_internal(const map_location& loc, const unit* ref_unit, const bool ignore_xy) const
{
if (!this->fc_->get_disp_context().map().on_board_with_border(loc)) {
return false;
@ -326,7 +326,13 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x
if(cfg_.has_attribute("formula")) {
try {
const wfl::terrain_callable callable(fc_->get_disp_context(), loc);
const wfl::terrain_callable main(fc_->get_disp_context(), loc);
wfl::map_formula_callable callable(main.fake_ptr());
if(ref_unit) {
std::shared_ptr<wfl::unit_callable> ref(new wfl::unit_callable(*ref_unit));
callable.add("teleport_unit", wfl::variant(ref));
// It's not destroyed upon scope exit because the variant holds a reference
}
const wfl::formula form(cfg_["formula"]);
if(!form.evaluate(callable).as_bool()) {
return false;
@ -342,7 +348,17 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x
return true;
}
bool terrain_filter::match(const map_location& loc) const
class filter_with_unit : public xy_pred {
const terrain_filter& filt_;
const unit& ref_;
public:
filter_with_unit(const terrain_filter& filt, const unit& ref) : filt_(filt), ref_(ref) {}
bool operator()(const map_location& loc) const override {
return filt_.match(loc, ref_);
}
};
bool terrain_filter::match_impl(const map_location& loc, const unit* ref_unit) const
{
if(cfg_["x"] == "recall" && cfg_["y"] == "recall") {
return !fc_->get_disp_context().map().on_board(loc);
@ -350,6 +366,15 @@ bool terrain_filter::match(const map_location& loc) const
std::set<map_location> hexes;
std::vector<map_location> loc_vec(1, loc);
std::unique_ptr<scoped_wml_variable> ref_unit_var;
if(ref_unit) {
if(fc_->get_disp_context().map().on_board(ref_unit->get_location())) {
ref_unit_var.reset(new scoped_xy_unit("teleport_unit", ref_unit->get_location(), fc_->get_disp_context().units()));
} else {
// Possible TODO: Support recall list units?
}
}
//handle radius
size_t radius = cfg_["radius"].to_size_t(0);
if(radius > max_loop_) {
@ -361,7 +386,11 @@ bool terrain_filter::match(const map_location& loc) const
hexes.insert(loc_vec.begin(), loc_vec.end());
else if ( cfg_.has_child("filter_radius") ) {
terrain_filter r_filter(cfg_.child("filter_radius"), *this);
get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes, false, r_filter);
if(ref_unit) {
get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes, false, filter_with_unit(r_filter, *ref_unit));
} else {
get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes, false, r_filter);
}
} else {
get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes);
}
@ -369,7 +398,7 @@ bool terrain_filter::match(const map_location& loc) const
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);
bool matches = match_internal(*i, ref_unit, false);
//handle [and], [or], and [not] with in-order precedence
vconfig::all_children_iterator cond = cfg_.ordered_begin();
@ -382,17 +411,17 @@ bool terrain_filter::match(const map_location& loc) const
//handle [and]
if(cond_name == "and")
{
matches = matches && terrain_filter(cond_cfg, *this)(*i);
matches = matches && terrain_filter(cond_cfg, *this).match_impl(*i, ref_unit);
}
//handle [or]
else if(cond_name == "or")
{
matches = matches || terrain_filter(cond_cfg, *this)(*i);
matches = matches || terrain_filter(cond_cfg, *this).match_impl(*i, ref_unit);
}
//handle [not]
else if(cond_name == "not")
{
matches = matches && !terrain_filter(cond_cfg, *this)(*i);
matches = matches && !terrain_filter(cond_cfg, *this).match_impl(*i, ref_unit);
}
++cond;
}
@ -473,8 +502,17 @@ struct cfg_to_loc
map_location operator()(const config& cfg) const { return map_location(cfg, nullptr); }
typedef map_location result_type;
};
void terrain_filter::get_locations(std::set<map_location>& locs, bool with_border) const
void terrain_filter::get_locs_impl(std::set<map_location>& locs, const unit* ref_unit, bool with_border) const
{
std::unique_ptr<scoped_wml_variable> ref_unit_var;
if(ref_unit) {
if(fc_->get_disp_context().map().on_board(ref_unit->get_location())) {
ref_unit_var.reset(new scoped_xy_unit("teleport_unit", ref_unit->get_location(), fc_->get_disp_context().units()));
} else {
// Possible TODO: Support recall list units?
}
}
std::set<map_location> match_set;
// See if the caller provided an override to with_border
@ -540,10 +578,10 @@ void terrain_filter::get_locations(std::set<map_location>& locs, bool with_borde
}
std::set<map_location>::iterator loc_itor = match_set.begin();
while(loc_itor != match_set.end()) {
if(match_internal(*loc_itor, true)) {
if(match_internal(*loc_itor, ref_unit, true)) {
++loc_itor;
} else {
match_set.erase(loc_itor++);
loc_itor = match_set.erase(loc_itor);
}
}

View file

@ -39,13 +39,37 @@ public:
terrain_filter(const terrain_filter &other);
terrain_filter& operator=(const terrain_filter &other);
//match: returns true if and only if the given location matches this filter
bool match(const map_location& loc) const;
/// @param loc The location to test
/// @returns true if and only if the given location matches this filter
bool match(const map_location& loc) const {
return match_impl(loc, nullptr);
}
/// @param loc The location to test
/// @param ref_unit A reference unit for the $teleport_unit auto-stored variable
/// @param unit_loc The reference unit's apparent location for this filter
/// @returns true if and only if the given location matches this filter
bool match(const map_location& loc, const unit& ref_unit) const {
return match_impl(loc, &ref_unit);
}
virtual bool operator()(const map_location& loc) const { return this->match(loc); }
//get_locations: gets all locations on the map that match this filter
// @param locs - out parameter containing the results
void get_locations(std::set<map_location>& locs, bool with_border=false) const;
/// gets all locations on the map that match this filter
/// @param[out] locs set to store the results in
/// @param[in] with_border whether to include the borders
void get_locations(std::set<map_location>& locs, bool with_border=false) const {
return get_locs_impl(locs, nullptr, with_border);
}
/// gets all locations on the map that match this filter
/// @param[out] locs set to store the results in
/// @param[in] with_border whether to include the borders
/// @param[in] ref_unit A reference unit for the $teleport_unit auto-stored variable
/// @param[in] unit_loc The reference unit's apparent location for this filter
void get_locations(std::set<map_location>& locs, const unit& ref_unit, bool with_border=false) const {
return get_locs_impl(locs, &ref_unit, with_border);
}
//restrict: limits the allowed radius size and also limits nesting
// The purpose to limit the time spent for WML handling
@ -58,7 +82,9 @@ public:
config to_config() const;
friend class terrain_filterimpl;
private:
bool match_internal(const map_location& loc, const bool ignore_xy) const;
bool match_impl(const map_location& loc, const unit* ref_unit) const;
void get_locs_impl(std::set<map_location>& locs, const unit* ref_unit, bool with_border) const;
bool match_internal(const map_location& loc, const unit* ref_unit, const bool ignore_xy) const;
const vconfig cfg_; //config contains WML for a Standard Location Filter
const filter_context * fc_;