Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
10270 lines (8678 sloc) 380 KB
#include "Condition.h"
#include "../util/Logger.h"
#include "../util/Random.h"
#include "../util/i18n.h"
#include "UniverseObject.h"
#include "Pathfinder.h"
#include "Universe.h"
#include "Building.h"
#include "Fighter.h"
#include "Fleet.h"
#include "Ship.h"
#include "ObjectMap.h"
#include "Planet.h"
#include "System.h"
#include "Species.h"
#include "Special.h"
#include "Meter.h"
#include "ValueRef.h"
#include "Enums.h"
#include "../Empire/Empire.h"
#include "../Empire/EmpireManager.h"
#include "../Empire/Supply.h"
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/bind.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/st_connected.hpp>
//TODO: replace with std::make_unique when transitioning to C++14
#include <boost/smart_ptr/make_unique.hpp>
using boost::io::str;
FO_COMMON_API extern const int INVALID_DESIGN_ID;
bool UserStringExists(const std::string& str);
namespace {
const std::string EMPTY_STRING;
}
namespace {
void AddAllObjectsSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingObjects().size());
std::transform(Objects().ExistingObjects().begin(), Objects().ExistingObjects().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second, _1));
}
void AddBuildingSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingBuildings().size());
std::transform(Objects().ExistingBuildings().begin(), Objects().ExistingBuildings().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second, _1));
}
void AddFieldSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingFields().size());
std::transform(Objects().ExistingFields().begin(), Objects().ExistingFields().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second, _1));
}
void AddFleetSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingFleets().size());
std::transform(Objects().ExistingFleets().begin(), Objects().ExistingFleets().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second,_1));
}
void AddPlanetSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingPlanets().size());
std::transform(Objects().ExistingPlanets().begin(), Objects().ExistingPlanets().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second,_1));
}
void AddPopCenterSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingPopCenters().size());
std::transform(Objects().ExistingPopCenters().begin(), Objects().ExistingPopCenters().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second,_1));
}
void AddResCenterSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingResourceCenters().size());
std::transform(Objects().ExistingResourceCenters().begin(), Objects().ExistingResourceCenters().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second,_1));
}
void AddShipSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingShips().size());
std::transform(Objects().ExistingShips().begin(), Objects().ExistingShips().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second,_1));
}
void AddSystemSet(Condition::ObjectSet& condition_non_targets) {
condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingSystems().size());
std::transform(Objects().ExistingSystems().begin(), Objects().ExistingSystems().end(),
std::back_inserter(condition_non_targets),
boost::bind(&std::map<int, std::shared_ptr<UniverseObject>>::value_type::second,_1));
}
/** Used by 4-parameter ConditionBase::Eval function, and some of its
* overrides, to scan through \a matches or \a non_matches set and apply
* \a pred to each object, to test if it should remain in its current set
* or be transferred from the \a search_domain specified set into the
* other. */
template <class Pred>
void EvalImpl(Condition::ObjectSet& matches, Condition::ObjectSet& non_matches,
Condition::SearchDomain search_domain, const Pred& pred)
{
auto& from_set = search_domain == Condition::MATCHES ? matches : non_matches;
auto& to_set = search_domain == Condition::MATCHES ? non_matches : matches;
for (auto it = from_set.begin(); it != from_set.end(); ) {
bool match = pred(*it);
if ((search_domain == Condition::MATCHES && !match) ||
(search_domain == Condition::NON_MATCHES && match))
{
to_set.push_back(*it);
*it = from_set.back();
from_set.pop_back();
} else {
++it;
}
}
}
std::vector<Condition::ConditionBase*> FlattenAndNestedConditions(
const std::vector<Condition::ConditionBase*>& input_conditions)
{
std::vector<Condition::ConditionBase*> retval;
for (Condition::ConditionBase* condition : input_conditions) {
if (Condition::And* and_condition = dynamic_cast<Condition::And*>(condition)) {
std::vector<Condition::ConditionBase*> flattened_operands =
FlattenAndNestedConditions(and_condition->Operands());
std::copy(flattened_operands.begin(), flattened_operands.end(), std::back_inserter(retval));
} else {
if (condition)
retval.push_back(condition);
}
}
return retval;
}
std::map<std::string, bool> ConditionDescriptionAndTest(
const std::vector<Condition::ConditionBase*>& conditions,
const ScriptingContext& parent_context,
std::shared_ptr<const UniverseObject> candidate_object/* = nullptr*/)
{
std::map<std::string, bool> retval;
std::vector<Condition::ConditionBase*> flattened_conditions;
if (conditions.empty())
return retval;
else if (conditions.size() > 1 || dynamic_cast<Condition::And*>(*conditions.begin()))
flattened_conditions = FlattenAndNestedConditions(conditions);
//else if (dynamic_cast<const Condition::Or*>(*conditions.begin()))
// flattened_conditions = FlattenOrNestedConditions(conditions);
else
flattened_conditions = conditions;
for (Condition::ConditionBase* condition : flattened_conditions) {
retval[condition->Description()] = condition->Eval(parent_context, candidate_object);
}
return retval;
}
}
namespace Condition {
std::string ConditionFailedDescription(const std::vector<ConditionBase*>& conditions,
std::shared_ptr<const UniverseObject> candidate_object/* = nullptr*/,
std::shared_ptr<const UniverseObject> source_object/* = nullptr*/)
{
if (conditions.empty())
return UserString("NONE");
ScriptingContext parent_context(source_object);
std::string retval;
// test candidate against all input conditions, and store descriptions of each
for (const auto& result : ConditionDescriptionAndTest(conditions, parent_context, candidate_object)) {
if (!result.second)
retval += UserString("FAILED") + " <rgba 255 0 0 255>" + result.first +"</rgba>\n";
}
// remove empty line from the end of the string
retval = retval.substr(0, retval.length() - 1);
return retval;
}
std::string ConditionDescription(const std::vector<ConditionBase*>& conditions,
std::shared_ptr<const UniverseObject> candidate_object/* = nullptr*/,
std::shared_ptr<const UniverseObject> source_object/* = nullptr*/)
{
if (conditions.empty())
return UserString("NONE");
ScriptingContext parent_context(source_object);
// test candidate against all input conditions, and store descriptions of each
auto condition_description_and_test_results =
ConditionDescriptionAndTest(conditions, parent_context, candidate_object);
bool all_conditions_match_candidate = true, at_least_one_condition_matches_candidate = false;
for (const auto& result : condition_description_and_test_results) {
all_conditions_match_candidate = all_conditions_match_candidate && result.second;
at_least_one_condition_matches_candidate = at_least_one_condition_matches_candidate || result.second;
}
// concatenate (non-duplicated) single-description results
std::string retval;
if (conditions.size() > 1 || dynamic_cast<And*>(*conditions.begin())) {
retval += UserString("ALL_OF") + " ";
retval += (all_conditions_match_candidate ? UserString("PASSED") : UserString("FAILED")) + "\n";
} else if (dynamic_cast<Or*>(*conditions.begin())) {
retval += UserString("ANY_OF") + " ";
retval += (at_least_one_condition_matches_candidate ? UserString("PASSED") : UserString("FAILED")) + "\n";
}
// else just output single condition description and PASS/FAIL text
for (const auto& result : condition_description_and_test_results) {
retval += (result.second ? UserString("PASSED") : UserString("FAILED"));
retval += " " + result.first + "\n";
}
return retval;
}
#define CHECK_COND_VREF_MEMBER(m_ptr) { if (m_ptr == rhs_.m_ptr) { \
/* check next member */ \
} else if (!m_ptr || !rhs_.m_ptr) { \
return false; \
} else { \
if (*m_ptr != *(rhs_.m_ptr)) \
return false; \
} }
///////////////////////////////////////////////////////////
// ConditionBase //
///////////////////////////////////////////////////////////
struct ConditionBase::MatchHelper {
MatchHelper(const ConditionBase* this_, const ScriptingContext& parent_context) :
m_this(this_),
m_parent_context(parent_context)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const
{ return m_this->Match(ScriptingContext(m_parent_context, candidate)); }
const ConditionBase* m_this;
const ScriptingContext& m_parent_context;
};
ConditionBase::~ConditionBase() = default;
bool ConditionBase::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
return true;
}
void ConditionBase::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{ EvalImpl(matches, non_matches, search_domain, MatchHelper(this, parent_context)); }
void ConditionBase::Eval(const ScriptingContext& parent_context,
ObjectSet& matches) const
{
matches.clear();
ObjectSet condition_initial_candidates;
// evaluate condition only on objects that could potentially be matched by the condition
GetDefaultInitialCandidateObjects(parent_context, condition_initial_candidates);
matches.reserve(condition_initial_candidates.size());
Eval(parent_context, matches, condition_initial_candidates);
}
bool ConditionBase::Eval(const ScriptingContext& parent_context,
std::shared_ptr<const UniverseObject> candidate) const
{
if (!candidate)
return false;
ObjectSet non_matches, matches;
non_matches.push_back(candidate);
Eval(parent_context, matches, non_matches);
return non_matches.empty(); // if candidate has been matched, non_matches will now be empty
}
bool ConditionBase::Eval(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
ObjectSet non_matches, matches;
non_matches.push_back(candidate);
Eval(ScriptingContext(), matches, non_matches);
return non_matches.empty(); // if candidate has been matched, non_matches will now be empty
}
void ConditionBase::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{ AddAllObjectsSet(condition_non_targets); }
std::string ConditionBase::Description(bool negated/* = false*/) const
{ return ""; }
std::string ConditionBase::Dump(unsigned short ntabs) const
{ return ""; }
bool ConditionBase::Match(const ScriptingContext& local_context) const
{ return false; }
///////////////////////////////////////////////////////////
// Number //
///////////////////////////////////////////////////////////
Number::Number(std::unique_ptr<ValueRef::ValueRefBase<int>>&& low,
std::unique_ptr<ValueRef::ValueRefBase<int>>&& high,
std::unique_ptr<ConditionBase>&& condition) :
m_low(std::move(low)),
m_high(std::move(high)),
m_condition(std::move(condition))
{}
bool Number::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const Number& rhs_ = static_cast<const Number&>(rhs);
CHECK_COND_VREF_MEMBER(m_low)
CHECK_COND_VREF_MEMBER(m_high)
CHECK_COND_VREF_MEMBER(m_condition)
return true;
}
std::string Number::Description(bool negated/* = false*/) const {
std::string low_str = (m_low ? (m_low->ConstantExpr() ?
std::to_string(m_low->Eval()) :
m_low->Description())
: "0");
std::string high_str = (m_high ? (m_high->ConstantExpr() ?
std::to_string(m_high->Eval()) :
m_high->Description())
: std::to_string(INT_MAX));
const std::string& description_str = (!negated)
? UserString("DESC_NUMBER")
: UserString("DESC_NUMBER_NOT");
return str(FlexibleFormat(description_str)
% low_str
% high_str
% m_condition->Description());
}
std::string Number::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "Number";
if (m_low)
retval += " low = " + m_low->Dump(ntabs);
if (m_high)
retval += " high = " + m_high->Dump(ntabs);
retval += " condition =\n";
retval += m_condition->Dump(ntabs+1);
return retval;
}
void Number::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
// Number does not have a single valid local candidate to be matched, as it
// will match anything if the proper number of objects match the
// subcondition. So, the local context that is passed to the subcondition
// needs to have a null local candidate.
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
if (!(
(!m_low || m_low->LocalCandidateInvariant())
&& (!m_high || m_high->LocalCandidateInvariant())
)
)
{
ErrorLogger() << "Condition::Number::Eval has local candidate-dependent ValueRefs, but no valid local candidate!";
} else if (
!local_context.condition_root_candidate
&& !(
(!m_low || m_low->RootCandidateInvariant())
&& (!m_high || m_high->RootCandidateInvariant())
)
)
{
ErrorLogger() << "Condition::Number::Eval has root candidate-dependent ValueRefs, but expects local candidate to be the root candidate, and has no valid local candidate!";
}
if (!local_context.condition_root_candidate && !this->RootCandidateInvariant()) {
// no externally-defined root candidate, so each object matched must
// separately act as a root candidate, and sub-condition must be re-
// evaluated for each tested object and the number of objects matched
// checked for each object being tested
ConditionBase::Eval(local_context, matches, non_matches, search_domain);
} else {
// parameters for number of subcondition objects that needs to be matched
int low = (m_low ? m_low->Eval(local_context) : 0);
int high = (m_high ? m_high->Eval(local_context) : INT_MAX);
// get set of all UniverseObjects that satisfy m_condition
ObjectSet condition_matches;
// can evaluate subcondition once for all objects being tested by this condition
m_condition->Eval(local_context, condition_matches);
// compare number of objects that satisfy m_condition to the acceptable range of such objects
int matched = condition_matches.size();
bool in_range = (low <= matched && matched <= high);
// transfer objects to or from candidate set, according to whether number of matches was within
// the requested range.
if (search_domain == MATCHES && !in_range) {
non_matches.insert(non_matches.end(), matches.begin(), matches.end());
matches.clear();
}
if (search_domain == NON_MATCHES && in_range) {
matches.insert(matches.end(), non_matches.begin(), non_matches.end());
non_matches.clear();
}
}
}
bool Number::RootCandidateInvariant() const {
return (!m_low || m_low->RootCandidateInvariant()) &&
(!m_high || m_high->RootCandidateInvariant()) &&
m_condition->RootCandidateInvariant();
}
bool Number::TargetInvariant() const {
return (!m_low || m_low->TargetInvariant()) &&
(!m_high || m_high->TargetInvariant()) &&
m_condition->TargetInvariant();
}
bool Number::SourceInvariant() const {
return (!m_low || m_low->SourceInvariant()) &&
(!m_high || m_high->SourceInvariant()) &&
m_condition->SourceInvariant();
}
bool Number::Match(const ScriptingContext& local_context) const {
// get acceptable range of subcondition matches for candidate
int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 0);
int high = (m_high ? std::min(m_high->Eval(local_context), INT_MAX) : INT_MAX);
// get set of all UniverseObjects that satisfy m_condition
ObjectSet condition_matches;
m_condition->Eval(local_context, condition_matches);
// compare number of objects that satisfy m_condition to the acceptable range of such objects
int matched = condition_matches.size();
bool in_range = (low <= matched && matched <= high);
return in_range;
}
void Number::SetTopLevelContent(const std::string& content_name) {
if (m_low)
m_low->SetTopLevelContent(content_name);
if (m_high)
m_high->SetTopLevelContent(content_name);
if (m_condition)
m_condition->SetTopLevelContent(content_name);
}
unsigned int Number::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Number");
CheckSums::CheckSumCombine(retval, m_low);
CheckSums::CheckSumCombine(retval, m_high);
CheckSums::CheckSumCombine(retval, m_condition);
TraceLogger() << "GetCheckSum(Number): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Turn //
///////////////////////////////////////////////////////////
Turn::Turn(std::unique_ptr<ValueRef::ValueRefBase<int>>&& low,
std::unique_ptr<ValueRef::ValueRefBase<int>>&& high) :
m_low(std::move(low)),
m_high(std::move(high))
{}
bool Turn::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const Turn& rhs_ = static_cast<const Turn&>(rhs);
CHECK_COND_VREF_MEMBER(m_low)
CHECK_COND_VREF_MEMBER(m_high)
return true;
}
void Turn::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
// if ValueRef for low or high range limits depend on local candidate, then
// they must be evaluated per-candidate.
// if there already is a root candidate, then this condition's parameters
// can be evaluated assuming it will not change.
// if there is no root candidate in the parent context, then this
// condition's candidates will be the root candidates, and this condition's
// parameters must be root candidate invariant or else must be evaluated
// per-candidate
bool simple_eval_safe = ((!m_low || m_low->LocalCandidateInvariant()) &&
(!m_high || m_high->LocalCandidateInvariant()) &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
// evaluate turn limits once, check range, and use result to match or
// reject all the search domain, since the current turn doesn't change
// from object to object, and neither do the range limits.
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
int low = (m_low ? std::max(BEFORE_FIRST_TURN, m_low->Eval(local_context)) : BEFORE_FIRST_TURN);
int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN);
int turn = CurrentTurn();
bool match = (low <= turn && turn <= high);
if (match && search_domain == NON_MATCHES) {
// move all objects from non_matches to matches
matches.insert(matches.end(), non_matches.begin(), non_matches.end());
non_matches.clear();
} else if (!match && search_domain == MATCHES) {
// move all objects from matches to non_matches
non_matches.insert(non_matches.end(), matches.begin(), matches.end());
matches.clear();
}
} else {
// re-evaluate allowed turn range for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool Turn::RootCandidateInvariant() const
{ return (!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant()); }
bool Turn::TargetInvariant() const
{ return (!m_low || m_low->TargetInvariant()) && (!m_high || m_high->TargetInvariant()); }
bool Turn::SourceInvariant() const
{ return (!m_low || m_low->SourceInvariant()) && (!m_high || m_high->SourceInvariant()); }
std::string Turn::Description(bool negated/* = false*/) const {
std::string low_str;
if (m_low)
low_str = (m_low->ConstantExpr() ?
std::to_string(m_low->Eval()) :
m_low->Description());
std::string high_str;
if (m_high)
high_str = (m_high->ConstantExpr() ?
std::to_string(m_high->Eval()) :
m_high->Description());
std::string description_str;
if (m_low && m_high) {
description_str = (!negated)
? UserString("DESC_TURN")
: UserString("DESC_TURN_NOT");
return str(FlexibleFormat(description_str)
% low_str
% high_str);
} else if (m_low) {
description_str = (!negated)
? UserString("DESC_TURN_MIN_ONLY")
: UserString("DESC_TURN_MIN_ONLY_NOT");
return str(FlexibleFormat(description_str)
% low_str);
} else if (m_high) {
description_str = (!negated)
? UserString("DESC_TURN_MAX_ONLY")
: UserString("DESC_TURN_MAX_ONLY_NOT");
return str(FlexibleFormat(description_str)
% high_str);
} else {
return (!negated)
? UserString("DESC_TURN_ANY")
: UserString("DESC_TURN_ANY_NOT");
}
}
std::string Turn::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "Turn";
if (m_low)
retval += " low = " + m_low->Dump(ntabs);
if (m_high)
retval += " high = " + m_high->Dump(ntabs);
retval += "\n";
return retval;
}
bool Turn::Match(const ScriptingContext& local_context) const {
int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 0);
int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN);
int turn = CurrentTurn();
return (low <= turn && turn <= high);
}
void Turn::SetTopLevelContent(const std::string& content_name) {
if (m_low)
m_low->SetTopLevelContent(content_name);
if (m_high)
m_high->SetTopLevelContent(content_name);
}
unsigned int Turn::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Turn");
CheckSums::CheckSumCombine(retval, m_low);
CheckSums::CheckSumCombine(retval, m_high);
TraceLogger() << "GetCheckSum(Turn): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// SortedNumberOf //
///////////////////////////////////////////////////////////
SortedNumberOf::SortedNumberOf(std::unique_ptr<ValueRef::ValueRefBase<int>>&& number,
std::unique_ptr<ConditionBase>&& condition) :
m_number(std::move(number)),
m_condition(std::move(condition))
{}
SortedNumberOf::SortedNumberOf(std::unique_ptr<ValueRef::ValueRefBase<int>>&& number,
std::unique_ptr<ValueRef::ValueRefBase<double>>&& sort_key_ref,
SortingMethod sorting_method,
std::unique_ptr<ConditionBase>&& condition) :
m_number(std::move(number)),
m_sort_key(std::move(sort_key_ref)),
m_sorting_method(sorting_method),
m_condition(std::move(condition))
{}
bool SortedNumberOf::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const SortedNumberOf& rhs_ = static_cast<const SortedNumberOf&>(rhs);
if (m_sorting_method != rhs_.m_sorting_method)
return false;
CHECK_COND_VREF_MEMBER(m_number)
CHECK_COND_VREF_MEMBER(m_sort_key)
CHECK_COND_VREF_MEMBER(m_condition)
return true;
}
namespace {
/** Random number genrator function to use with random_shuffle */
int CustomRandInt(int max_plus_one)
{ return RandSmallInt(0, max_plus_one - 1); }
int (*CRI)(int) = CustomRandInt;
/** Transfers the indicated \a number of objects, randomly selected from from_set to to_set */
void TransferRandomObjects(unsigned int number, ObjectSet& from_set, ObjectSet& to_set) {
// ensure number of objects to be moved is within reasonable range
number = std::min<unsigned int>(number, from_set.size());
if (number == 0)
return;
// create list of bool flags to indicate whether each item in from_set
// with corresponding place in iteration order should be transfered
std::vector<bool> transfer_flags(from_set.size(), false); // initialized to all false
// set first number flags to true
std::fill_n(transfer_flags.begin(), number, true);
// shuffle flags to randomize which flags are set
std::random_shuffle(transfer_flags.begin(), transfer_flags.end(), CRI);
// transfer objects that have been flagged
int i = 0;
for (auto it = from_set.begin(); it != from_set.end(); ++i) {
if (transfer_flags[i]) {
to_set.push_back(*it);
*it = from_set.back();
from_set.pop_back();
} else {
++it;
}
}
}
/** Transfers the indicated \a number of objects, selected from \a from_set
* into \a to_set. The objects transferred are selected based on the value
* of \a sort_key evaluated on them, with the largest / smallest / most
* common sort keys chosen, or a random selection chosen, depending on the
* specified \a sorting_method */
void TransferSortedObjects(unsigned int number, ValueRef::ValueRefBase<double>* sort_key,
const ScriptingContext& context, SortingMethod sorting_method,
ObjectSet& from_set, ObjectSet& to_set)
{
// handle random case, which doesn't need sorting key
if (sorting_method == SORT_RANDOM) {
TransferRandomObjects(number, from_set, to_set);
return;
}
// for other SoringMethods, need sort key values
if (!sort_key) {
ErrorLogger() << "TransferSortedObjects given null sort_key";
return;
}
// get sort key values for all objects in from_set, and sort by inserting into map
std::multimap<float, std::shared_ptr<const UniverseObject>> sort_key_objects;
for (auto& from : from_set) {
float sort_value = sort_key->Eval(ScriptingContext(context, from));
sort_key_objects.insert({sort_value, from});
}
// how many objects to select?
number = std::min<unsigned int>(number, sort_key_objects.size());
if (number == 0)
return;
unsigned int number_transferred(0);
// pick max / min / most common values
if (sorting_method == SORT_MIN) {
// move (number) objects with smallest sort key (at start of map)
// from the from_set into the to_set.
for (const auto& entry : sort_key_objects) {
auto object_to_transfer = entry.second;
auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer);
if (from_it != from_set.end()) {
*from_it = from_set.back();
from_set.pop_back();
to_set.push_back(object_to_transfer);
number_transferred++;
if (number_transferred >= number)
return;
}
}
} else if (sorting_method == SORT_MAX) {
// move (number) objects with largest sort key (at end of map)
// from the from_set into the to_set.
for (auto sorted_it = sort_key_objects.rbegin(); // would use const_reverse_iterator but this causes a compile error in some compilers
sorted_it != sort_key_objects.rend(); ++sorted_it)
{
auto object_to_transfer = sorted_it->second;
auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer);
if (from_it != from_set.end()) {
*from_it = from_set.back();
from_set.pop_back();
to_set.push_back(object_to_transfer);
number_transferred++;
if (number_transferred >= number)
return;
}
}
} else if (sorting_method == SORT_MODE) {
// compile histogram of of number of times each sort key occurs
std::map<float, unsigned int> histogram;
for (const auto& entry : sort_key_objects) {
histogram[entry.first]++;
}
// invert histogram to index by number of occurances
std::multimap<unsigned int, float> inv_histogram;
for (const auto& entry : histogram)
inv_histogram.insert({entry.second, entry.first});
// reverse-loop through inverted histogram to find which sort keys
// occurred most frequently, and transfer objects with those sort
// keys from from_set to to_set.
for (auto inv_hist_it = inv_histogram.rbegin(); // would use const_reverse_iterator but this causes a compile error in some compilers
inv_hist_it != inv_histogram.rend(); ++inv_hist_it)
{
float cur_sort_key = inv_hist_it->second;
// get range of objects with the current sort key
auto key_range = sort_key_objects.equal_range(cur_sort_key);
// loop over range, selecting objects to transfer from from_set to to_set
for (auto sorted_it = key_range.first;
sorted_it != key_range.second; ++sorted_it)
{
auto object_to_transfer = sorted_it->second;
auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer);
if (from_it != from_set.end()) {
*from_it = from_set.back();
from_set.pop_back();
to_set.push_back(object_to_transfer);
number_transferred++;
if (number_transferred >= number)
return;
}
}
}
} else {
ErrorLogger() << "TransferSortedObjects given unknown sort method";
}
}
}
void SortedNumberOf::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
// Most conditions match objects independently of the other objects being
// tested, but the number parameter for NumberOf conditions makes things
// more complicated. In order to match some number of the potential
// matches property, both the matches and non_matches need to be checked
// against the subcondition, and the total number of subcondition matches
// counted.
// Then, when searching NON_MATCHES, non_matches may be moved into matches
// so that the number of subcondition matches in matches is equal to the
// requested number. There may also be subcondition non-matches in
// matches, but these are not counted or affected by this condition.
// Or, when searching MATCHES, matches may be moved into non_matches so
// that the number of subcondition matches in matches is equal to the
// requested number. There again may be subcondition non-matches in
// matches, but these are also not counted or affected by this condition.
// SortedNumberOf does not have a valid local candidate to be matched
// before the subcondition is evaluated, so the local context that is
// passed to the subcondition needs to have a null local candidate.
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
// which input matches match the subcondition?
ObjectSet subcondition_matching_matches;
subcondition_matching_matches.reserve(matches.size());
m_condition->Eval(local_context, subcondition_matching_matches, matches, NON_MATCHES);
// remaining input matches don't match the subcondition...
ObjectSet subcondition_non_matching_matches = matches;
matches.clear(); // to be refilled later
// which input non_matches match the subcondition?
ObjectSet subcondition_matching_non_matches;
subcondition_matching_non_matches.reserve(non_matches.size());
m_condition->Eval(local_context, subcondition_matching_non_matches, non_matches, NON_MATCHES);
// remaining input non_matches don't match the subcondition...
ObjectSet subcondition_non_matching_non_matches = non_matches;
non_matches.clear(); // to be refilled later
// assemble single set of subcondition matching objects
ObjectSet all_subcondition_matches;
all_subcondition_matches.reserve(subcondition_matching_matches.size() + subcondition_matching_non_matches.size());
all_subcondition_matches.insert(all_subcondition_matches.end(), subcondition_matching_matches.begin(), subcondition_matching_matches.end());
all_subcondition_matches.insert(all_subcondition_matches.end(), subcondition_matching_non_matches.begin(), subcondition_matching_non_matches.end());
// how many subcondition matches to select as matches to this condition
int number = m_number->Eval(local_context);
// compile single set of all objects that are matched by this condition.
// these are the objects that should be transferred from non_matches into
// matches, or those left in matches while the rest are moved into non_matches
ObjectSet matched_objects;
matched_objects.reserve(number);
TransferSortedObjects(number, m_sort_key.get(), local_context, m_sorting_method, all_subcondition_matches, matched_objects);
// put objects back into matches and non_target sets as output...
if (search_domain == NON_MATCHES) {
// put matched objects that are in subcondition_matching_non_matches into matches
for (auto& matched_object : matched_objects) {
// is this matched object in subcondition_matching_non_matches?
auto smnt_it = std::find(subcondition_matching_non_matches.begin(),
subcondition_matching_non_matches.end(), matched_object);
if (smnt_it != subcondition_matching_non_matches.end()) {
// yes; move object to matches
*smnt_it = subcondition_matching_non_matches.back();
subcondition_matching_non_matches.pop_back();
matches.push_back(matched_object);
}
}
// put remaining (non-matched) objects in subcondition_matching_non_matches back into non_matches
non_matches.insert( non_matches.end(), subcondition_matching_non_matches.begin(), subcondition_matching_non_matches.end());
// put objects in subcondition_non_matching_non_matches back into non_matches
non_matches.insert( non_matches.end(), subcondition_non_matching_non_matches.begin(), subcondition_non_matching_non_matches.end());
// put objects in subcondition_matching_matches and subcondition_non_matching_matches back into matches
matches.insert( matches.end(), subcondition_matching_matches.begin(), subcondition_matching_matches.end());
matches.insert( matches.end(), subcondition_non_matching_matches.begin(), subcondition_non_matching_matches.end());
// this leaves the original contents of matches unchanged, other than
// possibly having transferred some objects into matches from non_matches
} else { /*(search_domain == MATCHES)*/
// put matched objecs that are in subcondition_matching_matches back into matches
for (auto& matched_object : matched_objects) {
// is this matched object in subcondition_matching_matches?
auto smt_it = std::find(subcondition_matching_matches.begin(),
subcondition_matching_matches.end(), matched_object);
if (smt_it != subcondition_matching_matches.end()) {
// yes; move back into matches
*smt_it = subcondition_matching_matches.back();
subcondition_matching_matches.pop_back();
matches.push_back(matched_object);
}
}
// put remaining (non-matched) objects in subcondition_matching_matches) into non_matches
non_matches.insert( non_matches.end(), subcondition_matching_matches.begin(), subcondition_matching_matches.end());
// put objects in subcondition_non_matching_matches into non_matches
non_matches.insert( non_matches.end(), subcondition_non_matching_matches.begin(), subcondition_non_matching_matches.end());
// put objects in subcondition_matching_non_matches and subcondition_non_matching_non_matches back into non_matches
non_matches.insert( non_matches.end(), subcondition_matching_non_matches.begin(), subcondition_matching_non_matches.end());
non_matches.insert( non_matches.end(), subcondition_non_matching_non_matches.begin(), subcondition_non_matching_non_matches.end());
// this leaves the original contents of non_matches unchanged, other than
// possibly having transferred some objects into non_matches from matches
}
}
bool SortedNumberOf::RootCandidateInvariant() const
{ return ((!m_number || m_number->SourceInvariant()) &&
(!m_sort_key || m_sort_key->SourceInvariant()) &&
(!m_condition || m_condition->SourceInvariant())); }
bool SortedNumberOf::TargetInvariant() const
{ return ((!m_number || m_number->SourceInvariant()) &&
(!m_sort_key || m_sort_key->SourceInvariant()) &&
(!m_condition || m_condition->SourceInvariant())); }
bool SortedNumberOf::SourceInvariant() const
{ return ((!m_number || m_number->SourceInvariant()) &&
(!m_sort_key || m_sort_key->SourceInvariant()) &&
(!m_condition || m_condition->SourceInvariant())); }
std::string SortedNumberOf::Description(bool negated/* = false*/) const {
std::string number_str = m_number->ConstantExpr() ? m_number->Dump() : m_number->Description();
if (m_sorting_method == SORT_RANDOM) {
return str(FlexibleFormat((!negated)
? UserString("DESC_NUMBER_OF")
: UserString("DESC_NUMBER_OF_NOT")
)
% number_str
% m_condition->Description());
} else {
std::string sort_key_str = m_sort_key->ConstantExpr() ? m_sort_key->Dump() : m_sort_key->Description();
std::string description_str;
switch (m_sorting_method) {
case SORT_MAX:
description_str = (!negated)
? UserString("DESC_MAX_NUMBER_OF")
: UserString("DESC_MAX_NUMBER_OF_NOT");
break;
case SORT_MIN:
description_str = (!negated)
? UserString("DESC_MIN_NUMBER_OF")
: UserString("DESC_MIN_NUMBER_OF_NOT");
break;
case SORT_MODE:
description_str = (!negated)
? UserString("DESC_MODE_NUMBER_OF")
: UserString("DESC_MODE_NUMBER_OF_NOT");
break;
default:
break;
}
return str(FlexibleFormat(description_str)
% number_str
% sort_key_str
% m_condition->Description());
}
}
std::string SortedNumberOf::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs);
switch (m_sorting_method) {
case SORT_RANDOM:
retval += "NumberOf"; break;
case SORT_MAX:
retval += "MaximumNumberOf"; break;
case SORT_MIN:
retval += "MinimumNumberOf"; break;
case SORT_MODE:
retval += "ModeNumberOf"; break;
default:
retval += "??NumberOf??"; break;
}
retval += " number = " + m_number->Dump(ntabs);
if (m_sort_key)
retval += " sortby = " + m_sort_key->Dump(ntabs);
retval += " condition =\n";
retval += m_condition->Dump(ntabs+1);
return retval;
}
void SortedNumberOf::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
if (m_condition) {
m_condition->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
} else {
ConditionBase::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
}
}
void SortedNumberOf::SetTopLevelContent(const std::string& content_name) {
if (m_number)
m_number->SetTopLevelContent(content_name);
if (m_sort_key)
m_sort_key->SetTopLevelContent(content_name);
if (m_condition)
m_condition->SetTopLevelContent(content_name);
}
unsigned int SortedNumberOf::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::SortedNumberOf");
CheckSums::CheckSumCombine(retval, m_number);
CheckSums::CheckSumCombine(retval, m_sort_key);
CheckSums::CheckSumCombine(retval, m_sorting_method);
CheckSums::CheckSumCombine(retval, m_condition);
TraceLogger() << "GetCheckSum(SortedNumberOf): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// All //
///////////////////////////////////////////////////////////
void All::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
if (search_domain == NON_MATCHES) {
// move all objects from non_matches to matches
matches.insert(matches.end(), non_matches.begin(), non_matches.end());
non_matches.clear();
}
// if search_comain is MATCHES, do nothing: all objects in matches set
// match this condition, so should remain in matches set
}
bool All::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string All::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_ALL")
: UserString("DESC_ALL_NOT");
}
std::string All::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "All\n"; }
unsigned int All::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::All");
TraceLogger() << "GetCheckSum(All): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// None //
///////////////////////////////////////////////////////////
void None::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
if (search_domain == MATCHES) {
// move all objects from matches to non_matches
non_matches.insert(non_matches.end(), matches.begin(), matches.end());
matches.clear();
}
// if search domain is non_matches, no need to do anything since none of them match None.
}
bool None::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string None::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_NONE")
: UserString("DESC_NONE_NOT");
}
std::string None::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "None\n"; }
unsigned int None::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::None");
TraceLogger() << "GetCheckSum(None): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// EmpireAffiliation //
///////////////////////////////////////////////////////////
EmpireAffiliation::EmpireAffiliation(std::unique_ptr<ValueRef::ValueRefBase<int>>&& empire_id,
EmpireAffiliationType affiliation) :
m_empire_id(std::move(empire_id)),
m_affiliation(affiliation)
{}
EmpireAffiliation::EmpireAffiliation(std::unique_ptr<ValueRef::ValueRefBase<int>>&& empire_id) :
m_empire_id(std::move(empire_id)),
m_affiliation(AFFIL_SELF)
{}
EmpireAffiliation::EmpireAffiliation(EmpireAffiliationType affiliation) :
m_empire_id(nullptr),
m_affiliation(affiliation)
{}
bool EmpireAffiliation::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const EmpireAffiliation& rhs_ = static_cast<const EmpireAffiliation&>(rhs);
if (m_affiliation != rhs_.m_affiliation)
return false;
CHECK_COND_VREF_MEMBER(m_empire_id)
return true;
}
namespace {
struct EmpireAffiliationSimpleMatch {
EmpireAffiliationSimpleMatch(int empire_id, EmpireAffiliationType affiliation) :
m_empire_id(empire_id),
m_affiliation(affiliation)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
switch (m_affiliation) {
case AFFIL_SELF:
return m_empire_id != ALL_EMPIRES && candidate->OwnedBy(m_empire_id);
break;
case AFFIL_ENEMY: {
if (m_empire_id == ALL_EMPIRES)
return true;
if (m_empire_id == candidate->Owner())
return false;
DiplomaticStatus status = Empires().GetDiplomaticStatus(m_empire_id, candidate->Owner());
return (status == DIPLO_WAR);
break;
}
case AFFIL_ALLY: {
if (m_empire_id == ALL_EMPIRES)
return false;
if (m_empire_id == candidate->Owner())
return false;
DiplomaticStatus status = Empires().GetDiplomaticStatus(m_empire_id, candidate->Owner());
return (status == DIPLO_PEACE);
break;
}
case AFFIL_ANY:
return !candidate->Unowned();
break;
case AFFIL_NONE:
return candidate->Unowned();
break;
case AFFIL_CAN_SEE: {
// todo...
return false;
break;
}
case AFFIL_HUMAN: {
if (candidate->Unowned())
return false;
if (GetEmpireClientType(candidate->Owner()) == Networking::CLIENT_TYPE_HUMAN_PLAYER)
return true;
return false;
break;
}
default:
return false;
break;
}
}
int m_empire_id;
EmpireAffiliationType m_affiliation;
};
}
void EmpireAffiliation::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = (!m_empire_id || m_empire_id->ConstantExpr()) ||
((!m_empire_id || m_empire_id->LocalCandidateInvariant()) &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
// evaluate empire id once, and use to check all candidate objects
std::shared_ptr<const UniverseObject> no_object;
int empire_id = m_empire_id ? m_empire_id->Eval(ScriptingContext(parent_context, no_object)) : ALL_EMPIRES;
EvalImpl(matches, non_matches, search_domain, EmpireAffiliationSimpleMatch(empire_id, m_affiliation));
} else {
// re-evaluate empire id for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool EmpireAffiliation::RootCandidateInvariant() const
{ return m_empire_id ? m_empire_id->RootCandidateInvariant() : true; }
bool EmpireAffiliation::TargetInvariant() const
{ return m_empire_id ? m_empire_id->TargetInvariant() : true; }
bool EmpireAffiliation::SourceInvariant() const
{ return m_empire_id ? m_empire_id->SourceInvariant() : true; }
std::string EmpireAffiliation::Description(bool negated/* = false*/) const {
std::string empire_str;
if (m_empire_id) {
int empire_id = ALL_EMPIRES;
if (m_empire_id->ConstantExpr())
empire_id = m_empire_id->Eval();
if (const Empire* empire = GetEmpire(empire_id))
empire_str = empire->Name();
else
empire_str = m_empire_id->Description();
}
if (m_affiliation == AFFIL_SELF) {
return str(FlexibleFormat((!negated)
? UserString("DESC_EMPIRE_AFFILIATION_SELF")
: UserString("DESC_EMPIRE_AFFILIATION_SELF_NOT")) % empire_str);
} else if (m_affiliation == AFFIL_ANY) {
return (!negated)
? UserString("DESC_EMPIRE_AFFILIATION_ANY")
: UserString("DESC_EMPIRE_AFFILIATION_ANY_NOT");
} else if (m_affiliation == AFFIL_NONE) {
return (!negated)
? UserString("DESC_EMPIRE_AFFILIATION_ANY_NOT")
: UserString("DESC_EMPIRE_AFFILIATION_ANY");
} else {
return str(FlexibleFormat((!negated)
? UserString("DESC_EMPIRE_AFFILIATION")
: UserString("DESC_EMPIRE_AFFILIATION_NOT"))
% UserString(boost::lexical_cast<std::string>(m_affiliation))
% empire_str);
}
}
std::string EmpireAffiliation::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs);
if (m_affiliation == AFFIL_SELF) {
retval += "OwnedBy";
if (m_empire_id)
retval += " empire = " + m_empire_id->Dump(ntabs);
} else if (m_affiliation == AFFIL_ANY) {
retval += "OwnedBy affiliation = AnyEmpire";
} else if (m_affiliation == AFFIL_NONE) {
retval += "Unowned";
} else if (m_affiliation == AFFIL_ENEMY) {
retval += "OwnedBy affiliation = EnemyOf";
if (m_empire_id)
retval += " empire = " + m_empire_id->Dump(ntabs);
} else if (m_affiliation == AFFIL_ALLY) {
retval += "OwnedBy affiliation = AllyOf";
if (m_empire_id)
retval += " empire = " + m_empire_id->Dump(ntabs);
} else if (m_affiliation == AFFIL_CAN_SEE) {
retval += "OwnedBy affiliation = CanSee";
} else if (m_affiliation == AFFIL_HUMAN) {
retval += "OwnedBy affiliation = Human";
} else {
retval += "OwnedBy ??";
}
retval += "\n";
return retval;
}
bool EmpireAffiliation::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "EmpireAffiliation::Match passed no candidate object";
return false;
}
int empire_id = m_empire_id ? m_empire_id->Eval(local_context) : ALL_EMPIRES;
return EmpireAffiliationSimpleMatch(empire_id, m_affiliation)(candidate);
}
void EmpireAffiliation::SetTopLevelContent(const std::string& content_name) {
if (m_empire_id)
m_empire_id->SetTopLevelContent(content_name);
}
unsigned int EmpireAffiliation::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::EmpireAffiliation");
CheckSums::CheckSumCombine(retval, m_empire_id);
CheckSums::CheckSumCombine(retval, m_affiliation);
TraceLogger() << "GetCheckSum(EmpireAffiliation): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Source //
///////////////////////////////////////////////////////////
bool Source::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string Source::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_SOURCE")
: UserString("DESC_SOURCE_NOT");
}
std::string Source::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "Source\n"; }
bool Source::Match(const ScriptingContext& local_context) const {
if (!local_context.source)
return false;
return local_context.source == local_context.condition_local_candidate;
}
void Source::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
if (parent_context.source)
condition_non_targets.push_back(parent_context.source);
}
unsigned int Source::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Source");
TraceLogger() << "GetCheckSum(Source): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// RootCandidate //
///////////////////////////////////////////////////////////
bool RootCandidate::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string RootCandidate::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_ROOT_CANDIDATE")
: UserString("DESC_ROOT_CANDIDATE_NOT");
}
std::string RootCandidate::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "RootCandidate\n"; }
bool RootCandidate::Match(const ScriptingContext& local_context) const {
if (!local_context.condition_root_candidate)
return false;
return local_context.condition_root_candidate == local_context.condition_local_candidate;
}
void RootCandidate::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
if (parent_context.condition_root_candidate)
condition_non_targets.push_back(parent_context.condition_root_candidate);
}
unsigned int RootCandidate::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::RootCandidate");
TraceLogger() << "GetCheckSum(RootCandidate): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Target //
///////////////////////////////////////////////////////////
bool Target::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string Target::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_TARGET")
: UserString("DESC_TARGET_NOT");
}
std::string Target::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "Target\n"; }
bool Target::Match(const ScriptingContext& local_context) const {
if (!local_context.effect_target)
return false;
return local_context.effect_target == local_context.condition_local_candidate;
}
void Target::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
if (parent_context.effect_target)
condition_non_targets.push_back(parent_context.effect_target);
}
unsigned int Target::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Target");
TraceLogger() << "GetCheckSum(Target): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Homeworld //
///////////////////////////////////////////////////////////
Homeworld::Homeworld() :
ConditionBase(),
m_names()
{}
Homeworld::Homeworld(std::vector<std::unique_ptr<ValueRef::ValueRefBase<std::string>>>&& names) :
ConditionBase(),
m_names(std::move(names))
{}
bool Homeworld::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const Homeworld& rhs_ = static_cast<const Homeworld&>(rhs);
if (m_names.size() != rhs_.m_names.size())
return false;
for (unsigned int i = 0; i < m_names.size(); ++i) {
CHECK_COND_VREF_MEMBER(m_names.at(i))
}
return true;
}
namespace {
struct HomeworldSimpleMatch {
HomeworldSimpleMatch(const std::vector<std::string>& names) :
m_names(names)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
// is it a planet or a building on a planet?
auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
std::shared_ptr<const ::Building> building;
if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
planet = GetPlanet(building->PlanetID());
}
if (!planet)
return false;
int planet_id = planet->ID();
const SpeciesManager& manager = GetSpeciesManager();
if (m_names.empty()) {
// match homeworlds for any species
for (auto species_it = manager.begin(); species_it != manager.end(); ++species_it) {
if (const auto& species = species_it->second) {
const auto& homeworld_ids = species->Homeworlds();
if (homeworld_ids.count(planet_id))
return true;
}
}
} else {
// match any of the species specified
for (const std::string& name : m_names) {
if (auto species = GetSpecies(name)) {
const auto& homeworld_ids = species->Homeworlds();
if (homeworld_ids.count(planet_id))
return true;
}
}
}
return false;
}
const std::vector<std::string>& m_names;
};
}
void Homeworld::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
if (simple_eval_safe) {
// check each valueref for invariance to local candidate
for (auto& name : m_names) {
if (!name->LocalCandidateInvariant()) {
simple_eval_safe = false;
break;
}
}
}
if (simple_eval_safe) {
// evaluate names once, and use to check all candidate objects
std::vector<std::string> names;
// get all names from valuerefs
for (auto& name : m_names) {
names.push_back(name->Eval(parent_context));
}
EvalImpl(matches, non_matches, search_domain, HomeworldSimpleMatch(names));
} else {
// re-evaluate allowed names for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool Homeworld::RootCandidateInvariant() const {
for (auto& name : m_names) {
if (!name->RootCandidateInvariant())
return false;
}
return true;
}
bool Homeworld::TargetInvariant() const {
for (auto& name : m_names) {
if (!name->TargetInvariant())
return false;
}
return true;
}
bool Homeworld::SourceInvariant() const {
for (auto& name : m_names) {
if (!name->SourceInvariant())
return false;
}
return true;
}
std::string Homeworld::Description(bool negated/* = false*/) const {
std::string values_str;
for (unsigned int i = 0; i < m_names.size(); ++i) {
values_str += m_names[i]->ConstantExpr() ?
UserString(m_names[i]->Eval()) :
m_names[i]->Description();
if (2 <= m_names.size() && i < m_names.size() - 2) {
values_str += ", ";
} else if (i == m_names.size() - 2) {
values_str += m_names.size() < 3 ? " " : ", ";
values_str += UserString("OR");
values_str += " ";
}
}
return str(FlexibleFormat((!negated)
? UserString("DESC_HOMEWORLD")
: UserString("DESC_HOMEWORLD_NOT"))
% values_str);
}
std::string Homeworld::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "HomeWorld";
if (m_names.size() == 1) {
retval += " name = " + m_names[0]->Dump(ntabs);
} else if (!m_names.empty()) {
retval += " name = [ ";
for (auto& name : m_names) {
retval += name->Dump(ntabs) + " ";
}
retval += "]";
}
return retval;
}
bool Homeworld::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Homeworld::Match passed no candidate object";
return false;
}
// is it a planet or a building on a planet?
auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
std::shared_ptr<const ::Building> building;
if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
planet = GetPlanet(building->PlanetID());
}
if (!planet)
return false;
int planet_id = planet->ID();
const SpeciesManager& manager = GetSpeciesManager();
if (m_names.empty()) {
// match homeworlds for any species
for (const auto& entry : manager) {
if (const auto& species = entry.second) {
const auto& homeworld_ids = species->Homeworlds();
if (homeworld_ids.count(planet_id))
return true;
}
}
} else {
// match any of the species specified
for (auto& name : m_names) {
const auto& species_name = name->Eval(local_context);
if (const auto species = manager.GetSpecies(species_name)) {
const auto& homeworld_ids = species->Homeworlds();
if (homeworld_ids.count(planet_id))
return true;
}
}
}
return false;
}
void Homeworld::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{ AddPlanetSet(condition_non_targets); }
void Homeworld::SetTopLevelContent(const std::string& content_name) {
for (auto& name : m_names) {
if (name)
name->SetTopLevelContent(content_name);
}
}
unsigned int Homeworld::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Homeworld");
CheckSums::CheckSumCombine(retval, m_names);
TraceLogger() << "GetCheckSum(Homeworld): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Capital //
///////////////////////////////////////////////////////////
bool Capital::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string Capital::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_CAPITAL")
: UserString("DESC_CAPITAL_NOT");
}
std::string Capital::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "Capital\n"; }
bool Capital::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Capital::Match passed no candidate object";
return false;
}
int candidate_id = candidate->ID();
// check if any empire's capital's ID is that candidate object's id.
// if it is, the candidate object is a capital.
for (const auto& entry : Empires())
if (entry.second->CapitalID() == candidate_id)
return true;
return false;
}
void Capital::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{ AddPlanetSet(condition_non_targets); }
unsigned int Capital::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Capital");
TraceLogger() << "GetCheckSum(Capital): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Monster //
///////////////////////////////////////////////////////////
bool Monster::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string Monster::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_MONSTER")
: UserString("DESC_MONSTER_NOT");
}
std::string Monster::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "Monster\n"; }
bool Monster::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Monster::Match passed no candidate object";
return false;
}
if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
if (ship->IsMonster())
return true;
return false;
}
void Monster::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{ AddShipSet(condition_non_targets); }
unsigned int Monster::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Monster");
TraceLogger() << "GetCheckSum(Monster): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Armed //
///////////////////////////////////////////////////////////
bool Armed::operator==(const ConditionBase& rhs) const
{ return ConditionBase::operator==(rhs); }
std::string Armed::Description(bool negated/* = false*/) const {
return (!negated)
? UserString("DESC_ARMED")
: UserString("DESC_ARMED_NOT");
}
std::string Armed::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "Armed\n"; }
bool Armed::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Armed::Match passed no candidate object";
return false;
}
if (auto ship = std::dynamic_pointer_cast<const Ship>(candidate))
if (ship->IsArmed() || ship->HasFighters())
return true;
return false;
}
unsigned int Armed::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Armed");
TraceLogger() << "GetCheckSum(Armed): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Type //
///////////////////////////////////////////////////////////
Type::Type(std::unique_ptr<ValueRef::ValueRefBase<UniverseObjectType>>&& type) :
ConditionBase(),
m_type(std::move(type))
{}
Type::Type(UniverseObjectType type) :
ConditionBase(),
m_type(boost::make_unique<ValueRef::Constant<UniverseObjectType>>(type))
{}
bool Type::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const Type& rhs_ = static_cast<const Type&>(rhs);
CHECK_COND_VREF_MEMBER(m_type)
return true;
}
namespace {
struct TypeSimpleMatch {
TypeSimpleMatch(UniverseObjectType type) :
m_type(type)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
switch (m_type) {
case OBJ_BUILDING:
case OBJ_SHIP:
case OBJ_FLEET:
case OBJ_PLANET:
case OBJ_SYSTEM:
case OBJ_FIELD:
case OBJ_FIGHTER:
return candidate->ObjectType() == m_type;
break;
case OBJ_POP_CENTER:
return (bool)std::dynamic_pointer_cast<const PopCenter>(candidate);
break;
case OBJ_PROD_CENTER:
return (bool)std::dynamic_pointer_cast<const ResourceCenter>(candidate);
break;
default:
break;
}
return false;
}
UniverseObjectType m_type;
};
}
void Type::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = m_type->ConstantExpr() ||
(m_type->LocalCandidateInvariant() &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
UniverseObjectType type = m_type->Eval(parent_context);
EvalImpl(matches, non_matches, search_domain, TypeSimpleMatch(type));
} else {
// re-evaluate allowed turn range for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool Type::RootCandidateInvariant() const
{ return m_type->RootCandidateInvariant(); }
bool Type::TargetInvariant() const
{ return m_type->TargetInvariant(); }
bool Type::SourceInvariant() const
{ return m_type->SourceInvariant(); }
std::string Type::Description(bool negated/* = false*/) const {
std::string value_str = m_type->ConstantExpr() ?
UserString(boost::lexical_cast<std::string>(m_type->Eval())) :
m_type->Description();
return str(FlexibleFormat((!negated)
? UserString("DESC_TYPE")
: UserString("DESC_TYPE_NOT"))
% value_str);
}
std::string Type::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs);
if (dynamic_cast<ValueRef::Constant<UniverseObjectType>*>(m_type.get())) {
switch (m_type->Eval()) {
case OBJ_BUILDING: retval += "Building\n"; break;
case OBJ_SHIP: retval += "Ship\n"; break;
case OBJ_FLEET: retval += "Fleet\n"; break;
case OBJ_PLANET: retval += "Planet\n"; break;
case OBJ_POP_CENTER: retval += "PopulationCenter\n"; break;
case OBJ_PROD_CENTER: retval += "ProductionCenter\n"; break;
case OBJ_SYSTEM: retval += "System\n"; break;
case OBJ_FIELD: retval += "Field\n"; break;
case OBJ_FIGHTER: retval += "Fighter\n"; break;
default: retval += "?\n"; break;
}
} else {
retval += "ObjectType type = " + m_type->Dump(ntabs) + "\n";
}
return retval;
}
bool Type::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Type::Match passed no candidate object";
return false;
}
return TypeSimpleMatch(m_type->Eval(local_context))(candidate);
}
void Type::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
// Ships, Fleets and default checks for current objects only
bool found_type = false;
if (m_type) {
found_type = true;
//std::vector<std::shared_ptr<const T>> this_base;
switch (m_type->Eval()) {
case OBJ_BUILDING:
AddBuildingSet(condition_non_targets);
break;
case OBJ_FIELD:
AddFieldSet(condition_non_targets);
break;
case OBJ_FLEET:
AddFleetSet(condition_non_targets);
break;
case OBJ_PLANET:
AddPlanetSet(condition_non_targets);
break;
case OBJ_POP_CENTER:
AddPopCenterSet(condition_non_targets);
break;
case OBJ_PROD_CENTER:
AddResCenterSet(condition_non_targets);
break;
case OBJ_SHIP:
AddShipSet(condition_non_targets);
break;
case OBJ_SYSTEM:
AddSystemSet(condition_non_targets);
break;
case OBJ_FIGHTER: // shouldn't exist outside of combat as a separate object
default:
found_type = false;
break;
}
}
if (found_type) {
//if (int(condition_non_targets.size()) < Objects().NumObjects()) {
// DebugLogger() << "Type::GetBaseNonMatches will provide " << condition_non_targets.size()
// << " objects of type " << GetType()->Eval() << " rather than "
// << Objects().NumObjects() << " total objects";
//}
} else {
ConditionBase::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets);
}
}
void Type::SetTopLevelContent(const std::string& content_name) {
if (m_type)
m_type->SetTopLevelContent(content_name);
}
unsigned int Type::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Type");
CheckSums::CheckSumCombine(retval, m_type);
TraceLogger() << "GetCheckSum(Type): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Building //
///////////////////////////////////////////////////////////
Building::Building(std::vector<std::unique_ptr<ValueRef::ValueRefBase<std::string>>>&& names) :
ConditionBase(),
m_names(std::move(names))
{}
bool Building::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const Building& rhs_ = static_cast<const Building&>(rhs);
if (m_names.size() != rhs_.m_names.size())
return false;
for (unsigned int i = 0; i < m_names.size(); ++i) {
CHECK_COND_VREF_MEMBER(m_names.at(i))
}
return true;
}
namespace {
struct BuildingSimpleMatch {
BuildingSimpleMatch(const std::vector<std::string>& names) :
m_names(names)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
// is it a building?
auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
if (!building)
return false;
// if no name supplied, match any building
if (m_names.empty())
return true;
// is it one of the specified building types?
return std::count(m_names.begin(), m_names.end(), building->BuildingTypeName());
}
const std::vector<std::string>& m_names;
};
}
void Building::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
if (simple_eval_safe) {
// check each valueref for invariance to local candidate
for (auto& name : m_names) {
if (!name->LocalCandidateInvariant()) {
simple_eval_safe = false;
break;
}
}
}
if (simple_eval_safe) {
// evaluate names once, and use to check all candidate objects
std::vector<std::string> names;
// get all names from valuerefs
for (auto& name : m_names) {
names.push_back(name->Eval(parent_context));
}
EvalImpl(matches, non_matches, search_domain, BuildingSimpleMatch(names));
} else {
// re-evaluate allowed building types range for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool Building::RootCandidateInvariant() const {
for (auto& name : m_names) {
if (!name->RootCandidateInvariant())
return false;
}
return true;
}
bool Building::TargetInvariant() const {
for (auto& name : m_names) {
if (!name->TargetInvariant())
return false;
}
return true;
}
bool Building::SourceInvariant() const {
for (auto& name : m_names) {
if (!name->SourceInvariant())
return false;
}
return true;
}
std::string Building::Description(bool negated/* = false*/) const {
std::string values_str;
for (unsigned int i = 0; i < m_names.size(); ++i) {
values_str += m_names[i]->ConstantExpr() ?
UserString(m_names[i]->Eval()) :
m_names[i]->Description();
if (2 <= m_names.size() && i < m_names.size() - 2) {
values_str += ", ";
} else if (i == m_names.size() - 2) {
values_str += m_names.size() < 3 ? " " : ", ";
values_str += UserString("OR");
values_str += " ";
}
}
return str(FlexibleFormat((!negated)
? UserString("DESC_BUILDING")
: UserString("DESC_BUILDING_NOT"))
% values_str);
}
std::string Building::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "Building name = ";
if (m_names.size() == 1) {
retval += m_names[0]->Dump(ntabs) + "\n";
} else {
retval += "[ ";
for (auto& name : m_names) {
retval += name->Dump(ntabs) + " ";
}
retval += "]\n";
}
return retval;
}
void Building::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{ AddBuildingSet(condition_non_targets); }
bool Building::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Building::Match passed no candidate object";
return false;
}
// is it a building?
auto building = std::dynamic_pointer_cast<const ::Building>(candidate);
if (building) {
// match any building type?
if (m_names.empty())
return true;
// match one of the specified building types
for (auto& name : m_names) {
if (name->Eval(local_context) == building->BuildingTypeName())
return true;
}
}
return false;
}
void Building::SetTopLevelContent(const std::string& content_name) {
for (auto& name : m_names) {
if (name)
name->SetTopLevelContent(content_name);
}
}
unsigned int Building::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Building");
CheckSums::CheckSumCombine(retval, m_names);
TraceLogger() << "GetCheckSum(Building): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// HasSpecial //
///////////////////////////////////////////////////////////
HasSpecial::HasSpecial() :
ConditionBase(),
m_name(nullptr),
m_capacity_low(nullptr),
m_capacity_high(nullptr),
m_since_turn_low(),
m_since_turn_high()
{}
HasSpecial::HasSpecial(std::unique_ptr<ValueRef::ValueRefBase<std::string>>&& name) :
ConditionBase(),
m_name(std::move(name)),
m_capacity_low(nullptr),
m_capacity_high(nullptr),
m_since_turn_low(),
m_since_turn_high()
{}
HasSpecial::HasSpecial(std::unique_ptr<ValueRef::ValueRefBase<std::string>>&& name,
std::unique_ptr<ValueRef::ValueRefBase<int>>&& since_turn_low,
std::unique_ptr<ValueRef::ValueRefBase<int>>&& since_turn_high) :
ConditionBase(),
m_name(std::move(name)),
m_capacity_low(nullptr),
m_capacity_high(nullptr),
m_since_turn_low(std::move(since_turn_low)),
m_since_turn_high(std::move(since_turn_high))
{}
HasSpecial::HasSpecial(std::unique_ptr<ValueRef::ValueRefBase<std::string>>&& name,
std::unique_ptr<ValueRef::ValueRefBase<double>>&& capacity_low,
std::unique_ptr<ValueRef::ValueRefBase<double>>&& capacity_high) :
ConditionBase(),
m_name(std::move(name)),
m_capacity_low(std::move(capacity_low)),
m_capacity_high(std::move(capacity_high)),
m_since_turn_low(nullptr),
m_since_turn_high(nullptr)
{}
HasSpecial::HasSpecial(const std::string& name) :
ConditionBase(),
// TODO: Use std::make_unique when adopting C++14
m_name(new ValueRef::Constant<std::string>(name)),
m_capacity_low(nullptr),
m_capacity_high(nullptr),
m_since_turn_low(nullptr),
m_since_turn_high(nullptr)
{}
bool HasSpecial::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const HasSpecial& rhs_ = static_cast<const HasSpecial&>(rhs);
CHECK_COND_VREF_MEMBER(m_name)
CHECK_COND_VREF_MEMBER(m_capacity_low)
CHECK_COND_VREF_MEMBER(m_capacity_high)
CHECK_COND_VREF_MEMBER(m_since_turn_low)
CHECK_COND_VREF_MEMBER(m_since_turn_high)
return true;
}
namespace {
struct HasSpecialSimpleMatch {
HasSpecialSimpleMatch(const std::string& name, float low_cap, float high_cap, int low_turn, int high_turn) :
m_name(name),
m_low_cap(low_cap),
m_high_cap(high_cap),
m_low_turn(low_turn),
m_high_turn(high_turn)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
if (m_name.empty())
return !candidate->Specials().empty();
auto it = candidate->Specials().find(m_name);
if (it == candidate->Specials().end())
return false;
int special_since_turn = it->second.first;
float special_capacity = it->second.second;
return m_low_turn <= special_since_turn
&& special_since_turn <= m_high_turn
&& m_low_cap <= special_capacity
&& special_capacity <= m_high_cap;
}
const std::string& m_name;
float m_low_cap;
float m_high_cap;
int m_low_turn;
int m_high_turn;
};
}
void HasSpecial::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = ((!m_name || m_name->LocalCandidateInvariant()) &&
(!m_capacity_low || m_capacity_low->LocalCandidateInvariant()) &&
(!m_capacity_high || m_capacity_high->LocalCandidateInvariant()) &&
(!m_since_turn_low || m_since_turn_low->LocalCandidateInvariant()) &&
(!m_since_turn_high || m_since_turn_high->LocalCandidateInvariant()) &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
// evaluate turn limits once, pass to simple match for all candidates
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
std::string name = (m_name ? m_name->Eval(local_context) : "");
float low_cap = (m_capacity_low ? m_capacity_low->Eval(local_context) : -FLT_MAX);
float high_cap = (m_capacity_high ? m_capacity_high->Eval(local_context) : FLT_MAX);
int low_turn = (m_since_turn_low ? m_since_turn_low->Eval(local_context) : BEFORE_FIRST_TURN);
int high_turn = (m_since_turn_high ? m_since_turn_high->Eval(local_context) : IMPOSSIBLY_LARGE_TURN);
EvalImpl(matches, non_matches, search_domain, HasSpecialSimpleMatch(name, low_cap, high_cap, low_turn, high_turn));
} else {
// re-evaluate allowed turn range for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool HasSpecial::RootCandidateInvariant() const
{ return ((!m_name || m_name->RootCandidateInvariant()) &&
(!m_capacity_low || m_capacity_low->RootCandidateInvariant()) &&
(!m_capacity_high || m_capacity_high->RootCandidateInvariant()) &&
(!m_since_turn_low || m_since_turn_low->RootCandidateInvariant()) &&
(!m_since_turn_high || m_since_turn_high->RootCandidateInvariant())); }
bool HasSpecial::TargetInvariant() const
{ return ((!m_name || m_name->TargetInvariant()) &&
(!m_capacity_low || m_capacity_low->TargetInvariant()) &&
(!m_capacity_high || m_capacity_high->TargetInvariant()) &&
(!m_since_turn_low || m_since_turn_low->TargetInvariant()) &&
(!m_since_turn_high || m_since_turn_high->TargetInvariant())); }
bool HasSpecial::SourceInvariant() const
{ return ((!m_name || m_name->SourceInvariant()) &&
(!m_capacity_low || m_capacity_low->SourceInvariant()) &&
(!m_capacity_high || m_capacity_high->SourceInvariant()) &&
(!m_since_turn_low || m_since_turn_low->SourceInvariant()) &&
(!m_since_turn_high || m_since_turn_high->SourceInvariant())); }
std::string HasSpecial::Description(bool negated/* = false*/) const {
std::string name_str;
if (m_name) {
name_str = m_name->Description();
if (m_name->ConstantExpr() && UserStringExists(name_str))
name_str = UserString(name_str);
}
if (m_since_turn_low || m_since_turn_high) {
// turn range has been specified; must indicate in description
std::string low_str = std::to_string(BEFORE_FIRST_TURN);
if (m_since_turn_low)
low_str = m_since_turn_low->Description();
std::string high_str = std::to_string(IMPOSSIBLY_LARGE_TURN);
if (m_since_turn_high)
high_str = m_since_turn_high->Description();
return str(FlexibleFormat((!negated)
? UserString("DESC_SPECIAL_TURN_RANGE")
: UserString("DESC_SPECIAL_TURN_RANGE_NOT"))
% name_str
% low_str
% high_str);
}
if (m_capacity_low || m_capacity_high) {
// capacity range has been specified; must indicate in description
std::string low_str = std::to_string(-FLT_MAX);
if (m_capacity_low)
low_str = m_capacity_low->Description();
std::string high_str = std::to_string(FLT_MAX);
if (m_capacity_high)
high_str = m_capacity_high->Description();
return str(FlexibleFormat((!negated)
? UserString("DESC_SPECIAL_CAPACITY_RANGE")
: UserString("DESC_SPECIAL_CAPACITY_RANGE_NOT"))
% name_str
% low_str
% high_str);
}
return str(FlexibleFormat((!negated)
? UserString("DESC_SPECIAL")
: UserString("DESC_SPECIAL_NOT"))
% name_str);
}
std::string HasSpecial::Dump(unsigned short ntabs) const {
std::string name_str = (m_name ? m_name->Dump(ntabs) : "");
if (m_since_turn_low || m_since_turn_high) {
std::string low_dump = (m_since_turn_low ? m_since_turn_low->Dump(ntabs) : std::to_string(BEFORE_FIRST_TURN));
std::string high_dump = (m_since_turn_high ? m_since_turn_high->Dump(ntabs) : std::to_string(IMPOSSIBLY_LARGE_TURN));
return DumpIndent(ntabs) + "HasSpecialSinceTurn name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump;
}
if (m_capacity_low || m_capacity_high) {
std::string low_dump = (m_capacity_low ? m_capacity_low->Dump(ntabs) : std::to_string(-FLT_MAX));
std::string high_dump = (m_capacity_high ? m_capacity_high->Dump(ntabs) : std::to_string(FLT_MAX));
return DumpIndent(ntabs) + "HasSpecialCapacity name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump;
}
return DumpIndent(ntabs) + "HasSpecial name = \"" + name_str + "\"\n";
}
bool HasSpecial::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "HasSpecial::Match passed no candidate object";
return false;
}
std::string name = (m_name ? m_name->Eval(local_context) : "");
float low_cap = (m_capacity_low ? m_capacity_low->Eval(local_context) : -FLT_MAX);
float high_cap = (m_capacity_high ? m_capacity_high->Eval(local_context) : FLT_MAX);
int low_turn = (m_since_turn_low ? m_since_turn_low->Eval(local_context) : BEFORE_FIRST_TURN);
int high_turn = (m_since_turn_high ? m_since_turn_high->Eval(local_context) : IMPOSSIBLY_LARGE_TURN);
return HasSpecialSimpleMatch(name, low_cap, high_cap, low_turn, high_turn)(candidate);
}
void HasSpecial::SetTopLevelContent(const std::string& content_name) {
if (m_name)
m_name->SetTopLevelContent(content_name);
if (m_capacity_low)
m_capacity_low->SetTopLevelContent(content_name);
if (m_capacity_high)
m_capacity_high->SetTopLevelContent(content_name);
if (m_since_turn_low)
m_since_turn_low->SetTopLevelContent(content_name);
if (m_since_turn_high)
m_since_turn_high->SetTopLevelContent(content_name);
}
unsigned int HasSpecial::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::HasSpecial");
CheckSums::CheckSumCombine(retval, m_name);
CheckSums::CheckSumCombine(retval, m_capacity_low);
CheckSums::CheckSumCombine(retval, m_capacity_high);
CheckSums::CheckSumCombine(retval, m_since_turn_low);
CheckSums::CheckSumCombine(retval, m_since_turn_high);
TraceLogger() << "GetCheckSum(HasSpecial): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// HasTag //
///////////////////////////////////////////////////////////
HasTag::HasTag() :
ConditionBase(),
m_name()
{}
HasTag::HasTag(const std::string& name) :
ConditionBase(),
// TODO: Use std::make_unique when adopting C++14
m_name(new ValueRef::Constant<std::string>(name))
{}
HasTag::HasTag(std::unique_ptr<ValueRef::ValueRefBase<std::string>>&& name) :
ConditionBase(),
m_name(std::move(name))
{}
bool HasTag::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const HasTag& rhs_ = static_cast<const HasTag&>(rhs);
CHECK_COND_VREF_MEMBER(m_name)
return true;
}
namespace {
struct HasTagSimpleMatch {
HasTagSimpleMatch() :
m_any_tag_ok(true),
m_name()
{}
HasTagSimpleMatch(const std::string& name) :
m_any_tag_ok(false),
m_name(name)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
if (m_any_tag_ok && !candidate->Tags().empty())
return true;
return candidate->HasTag(m_name);
}
bool m_any_tag_ok;
std::string m_name;
};
}
void HasTag::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) &&
(parent_context.condition_root_candidate || RootCandidateInvariant());
if (simple_eval_safe) {
// evaluate number limits once, use to match all candidates
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
if (!m_name) {
EvalImpl(matches, non_matches, search_domain, HasTagSimpleMatch());
} else {
std::string name = boost::to_upper_copy<std::string>(m_name->Eval(local_context));
EvalImpl(matches, non_matches, search_domain, HasTagSimpleMatch(name));
}
} else {
// re-evaluate allowed turn range for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool HasTag::RootCandidateInvariant() const
{ return !m_name || m_name->RootCandidateInvariant(); }
bool HasTag::TargetInvariant() const
{ return !m_name || m_name->TargetInvariant(); }
bool HasTag::SourceInvariant() const
{ return !m_name || m_name->SourceInvariant(); }
std::string HasTag::Description(bool negated/* = false*/) const {
std::string name_str;
if (m_name) {
name_str = m_name->Description();
if (m_name->ConstantExpr() && UserStringExists(name_str))
name_str = UserString(name_str);
}
return str(FlexibleFormat((!negated)
? UserString("DESC_HAS_TAG")
: UserString("DESC_HAS_TAG_NOT"))
% name_str);
}
std::string HasTag::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "HasTag";
if (m_name)
retval += " name = " + m_name->Dump(ntabs);
retval += "\n";
return retval;
}
bool HasTag::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "HasTag::Match passed no candidate object";
return false;
}
if (!m_name)
return HasTagSimpleMatch()(candidate);
std::string name = boost::to_upper_copy<std::string>(m_name->Eval(local_context));
return HasTagSimpleMatch(name)(candidate);
}
void HasTag::SetTopLevelContent(const std::string& content_name) {
if (m_name)
m_name->SetTopLevelContent(content_name);
}
unsigned int HasTag::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::HasTag");
CheckSums::CheckSumCombine(retval, m_name);
TraceLogger() << "GetCheckSum(HasTag): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// CreatedOnTurn //
///////////////////////////////////////////////////////////
CreatedOnTurn::CreatedOnTurn(std::unique_ptr<ValueRef::ValueRefBase<int>>&& low,
std::unique_ptr<ValueRef::ValueRefBase<int>>&& high) :
ConditionBase(),
m_low(std::move(low)),
m_high(std::move(high))
{}
bool CreatedOnTurn::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const CreatedOnTurn& rhs_ = static_cast<const CreatedOnTurn&>(rhs);
CHECK_COND_VREF_MEMBER(m_low)
CHECK_COND_VREF_MEMBER(m_high)
return true;
}
namespace {
struct CreatedOnTurnSimpleMatch {
CreatedOnTurnSimpleMatch(int low, int high) :
m_low(low),
m_high(high)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
int turn = candidate->CreationTurn();
return m_low <= turn && turn <= m_high;
}
int m_low;
int m_high;
};
}
void CreatedOnTurn::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = ((!m_low || m_low->LocalCandidateInvariant()) &&
(!m_high || m_high->LocalCandidateInvariant()) &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
int low = (m_low ? m_low->Eval(local_context) : BEFORE_FIRST_TURN);
int high = (m_high ? m_high->Eval(local_context) : IMPOSSIBLY_LARGE_TURN);
EvalImpl(matches, non_matches, search_domain, CreatedOnTurnSimpleMatch(low, high));
} else {
// re-evaluate allowed turn range for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool CreatedOnTurn::RootCandidateInvariant() const
{ return ((!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant())); }
bool CreatedOnTurn::TargetInvariant() const
{ return ((!m_low || m_low->TargetInvariant()) && (!m_high || m_high->TargetInvariant())); }
bool CreatedOnTurn::SourceInvariant() const
{ return ((!m_low || m_low->SourceInvariant()) && (!m_high || m_high->SourceInvariant())); }
std::string CreatedOnTurn::Description(bool negated/* = false*/) const {
std::string low_str = (m_low ? (m_low->ConstantExpr() ?
std::to_string(m_low->Eval()) :
m_low->Description())
: std::to_string(BEFORE_FIRST_TURN));
std::string high_str = (m_high ? (m_high->ConstantExpr() ?
std::to_string(m_high->Eval()) :
m_high->Description())
: std::to_string(IMPOSSIBLY_LARGE_TURN));
return str(FlexibleFormat((!negated)
? UserString("DESC_CREATED_ON_TURN")
: UserString("DESC_CREATED_ON_TURN_NOT"))
% low_str
% high_str);
}
std::string CreatedOnTurn::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "CreatedOnTurn";
if (m_low)
retval += " low = " + m_low->Dump(ntabs);
if (m_high)
retval += " high = " + m_high->Dump(ntabs);
retval += "\n";
return retval;
}
bool CreatedOnTurn::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "CreatedOnTurn::Match passed no candidate object";
return false;
}
int low = (m_low ? std::max(0, m_low->Eval(local_context)) : BEFORE_FIRST_TURN);
int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN);
return CreatedOnTurnSimpleMatch(low, high)(candidate);
}
void CreatedOnTurn::SetTopLevelContent(const std::string& content_name) {
if (m_low)
m_low->SetTopLevelContent(content_name);
if (m_high)
m_high->SetTopLevelContent(content_name);
}
unsigned int CreatedOnTurn::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::CreatedOnTurn");
CheckSums::CheckSumCombine(retval, m_low);
CheckSums::CheckSumCombine(retval, m_high);
TraceLogger() << "GetCheckSum(CreatedOnTurn): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// Contains //
///////////////////////////////////////////////////////////
bool Contains::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const Contains& rhs_ = static_cast<const Contains&>(rhs);
CHECK_COND_VREF_MEMBER(m_condition)
return true;
}
namespace {
struct ContainsSimpleMatch {
ContainsSimpleMatch(const ObjectSet& subcondition_matches) :
m_subcondition_matches_ids()
{
// We need a sorted container for efficiently intersecting
// subcondition_matches with the set of objects contained in some
// candidate object.
// We only need ids, not objects, so we can do that conversion
// here as well, simplifying later code.
// Note that this constructor is called only once per
// Contains::Eval(), its work cannot help performance when executed
// for each candidate.
m_subcondition_matches_ids.reserve(subcondition_matches.size());
// gather the ids
for (auto& obj : subcondition_matches) {
if (obj)
m_subcondition_matches_ids.push_back(obj->ID());
}
// sort them
std::sort(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end());
}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
bool match = false;
const auto& candidate_elements = candidate->ContainedObjectIDs(); // guaranteed O(1)
// We need to test whether candidate_elements and m_subcondition_matches_ids have a common element.
// We choose the strategy that is more efficient by comparing the sizes of both sets.
if (candidate_elements.size() < m_subcondition_matches_ids.size()) {
// candidate_elements is smaller, so we iterate it and look up each candidate element in m_subcondition_matches_ids
for (int id : candidate_elements) {
// std::lower_bound requires m_subcondition_matches_ids to be sorted
auto matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id);
if (matching_it != m_subcondition_matches_ids.end() && *matching_it == id) {
match = true;
break;
}
}
} else {
// m_subcondition_matches_ids is smaller, so we iterate it and look up each subcondition match in the set of candidate's elements
for (int id : m_subcondition_matches_ids) {
// candidate->Contains() may have a faster implementation than candidate_elements->find()
if (candidate->Contains(id)) {
match = true;
break;
}
}
}
return match;
}
std::vector<int> m_subcondition_matches_ids;
};
}
void Contains::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
unsigned int search_domain_size = (search_domain == MATCHES ? matches.size() : non_matches.size());
bool simple_eval_safe = parent_context.condition_root_candidate ||
RootCandidateInvariant() ||
search_domain_size < 2;
if (!simple_eval_safe) {
// re-evaluate contained objects for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
return;
}
// how complicated is this containment test?
if (((search_domain == MATCHES) && matches.empty()) ||
((search_domain == NON_MATCHES) && non_matches.empty()))
{
// don't need to evaluate anything...
} else if (search_domain_size == 1) {
// evaluate subcondition on objects contained by the candidate
ScriptingContext local_context(parent_context, search_domain == MATCHES ? *matches.begin() : *non_matches.begin());
// initialize subcondition candidates from local candidate's contents
const ObjectMap& objects = Objects();
ObjectSet subcondition_matches = objects.FindObjects(local_context.condition_local_candidate->ContainedObjectIDs());
// apply subcondition to candidates
if (!subcondition_matches.empty()) {
ObjectSet dummy;
m_condition->Eval(local_context, subcondition_matches, dummy, Condition::MATCHES);
}
// move single local candidate as appropriate...
if (search_domain == MATCHES && subcondition_matches.empty()) {
// move to non_matches
matches.clear();
non_matches.push_back(local_context.condition_local_candidate);
} else if (search_domain == NON_MATCHES && !subcondition_matches.empty()) {
// move to matches
non_matches.clear();
matches.push_back(local_context.condition_local_candidate);
}
} else {
// evaluate contained objects once using default initial candidates
// of subcondition to find all subcondition matches in the Universe
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
ObjectSet subcondition_matches;
m_condition->Eval(local_context, subcondition_matches);
// check all candidates to see if they contain any subcondition matches
EvalImpl(matches, non_matches, search_domain, ContainsSimpleMatch(subcondition_matches));
}
}
bool Contains::RootCandidateInvariant() const
{ return m_condition->RootCandidateInvariant(); }
bool Contains::TargetInvariant() const
{ return m_condition->TargetInvariant(); }
bool Contains::SourceInvariant() const
{ return m_condition->SourceInvariant(); }
std::string Contains::Description(bool negated/* = false*/) const {
return str(FlexibleFormat((!negated)
? UserString("DESC_CONTAINS")
: UserString("DESC_CONTAINS_NOT"))
% m_condition->Description());
}
std::string Contains::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "Contains condition =\n";
retval += m_condition->Dump(ntabs+1);
return retval;
}
void Contains::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
// objects that can contain other objects: systems, fleets, planets
AddSystemSet(condition_non_targets);
AddFleetSet(condition_non_targets);
AddPlanetSet(condition_non_targets);
}
bool Contains::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "Contains::Match passed no candidate object";
return false;
}
// get subcondition matches
ObjectSet subcondition_matches;
m_condition->Eval(local_context, subcondition_matches);
// does candidate object contain any subcondition matches?
for (auto& obj : subcondition_matches)
if (candidate->Contains(obj->ID()))
return true;
return false;
}
void Contains::SetTopLevelContent(const std::string& content_name) {
if (m_condition)
m_condition->SetTopLevelContent(content_name);
}
unsigned int Contains::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::Contains");
CheckSums::CheckSumCombine(retval, m_condition);
TraceLogger() << "GetCheckSum(Contains): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// ContainedBy //
///////////////////////////////////////////////////////////
bool ContainedBy::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const ContainedBy& rhs_ = static_cast<const ContainedBy&>(rhs);
CHECK_COND_VREF_MEMBER(m_condition)
return true;
}
namespace {
struct ContainedBySimpleMatch {
ContainedBySimpleMatch(const ObjectSet& subcondition_matches) :
m_subcondition_matches_ids()
{
// We need a sorted container for efficiently intersecting
// subcondition_matches with the set of objects containing some
// candidate object.
// We only need ids, not objects, so we can do that conversion
// here as well, simplifying later code.
// Note that this constructor is called only once per
// ContainedBy::Eval(), its work cannot help performance when
// executed for each candidate.
m_subcondition_matches_ids.reserve(subcondition_matches.size());
// gather the ids
for (auto& obj : subcondition_matches) {
if (obj)
{ m_subcondition_matches_ids.push_back(obj->ID()); }
}
// sort them
std::sort(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end());
}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
bool match = false;
// gather the objects containing candidate
std::vector<int> candidate_containers;
const int candidate_id = candidate->ID();
const int system_id = candidate->SystemID();
const int container_id = candidate->ContainerObjectID();
if ( system_id != INVALID_OBJECT_ID && system_id != candidate_id) candidate_containers.push_back( system_id);
if (container_id != INVALID_OBJECT_ID && container_id != system_id) candidate_containers.push_back(container_id);
// FIXME: currently, direct container and system will do. In the future, we might need a way to retrieve containers of containers
// We need to test whether candidate_containers and m_subcondition_matches_ids have a common element.
// We choose the strategy that is more efficient by comparing the sizes of both sets.
if (candidate_containers.size() < m_subcondition_matches_ids.size()) {
// candidate_containers is smaller, so we iterate it and look up each candidate container in m_subcondition_matches_ids
for (int id : candidate_containers) {
// std::lower_bound requires m_subcondition_matches_ids to be sorted
auto matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id);
if (matching_it != m_subcondition_matches_ids.end() && *matching_it == id) {
match = true;
break;
}
}
} else {
// m_subcondition_matches_ids is smaller, so we iterate it and look up each subcondition match in the set of candidate's containers
for (int id : m_subcondition_matches_ids) {
// candidate->ContainedBy() may have a faster implementation than candidate_containers->find()
if (candidate->ContainedBy(id)) {
match = true;
break;
}
}
}
return match;
}
std::vector<int> m_subcondition_matches_ids;
};
}
void ContainedBy::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
unsigned int search_domain_size = (search_domain == MATCHES ? matches.size() : non_matches.size());
bool simple_eval_safe = parent_context.condition_root_candidate ||
RootCandidateInvariant() ||
search_domain_size < 2;
if (!simple_eval_safe) {
// re-evaluate container objects for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
return;
}
// how complicated is this containment test?
if (((search_domain == MATCHES) && matches.empty()) ||
((search_domain == NON_MATCHES) && non_matches.empty()))
{
// don't need to evaluate anything...
} else if (search_domain_size == 1) {
// evaluate subcondition on objects that contain the candidate
ScriptingContext local_context(parent_context, search_domain == MATCHES ? *matches.begin() : *non_matches.begin());
// initialize subcondition candidates from local candidate's containers
const ObjectMap& objects = Objects();
std::set<int> container_object_ids;
if (local_context.condition_local_candidate->ContainerObjectID() != INVALID_OBJECT_ID)
container_object_ids.insert(local_context.condition_local_candidate->ContainerObjectID());
if (local_context.condition_local_candidate->SystemID() != INVALID_OBJECT_ID)
container_object_ids.insert(local_context.condition_local_candidate->SystemID());
ObjectSet subcondition_matches = objects.FindObjects(container_object_ids);
// apply subcondition to candidates
if (!subcondition_matches.empty()) {
ObjectSet dummy;
m_condition->Eval(local_context, subcondition_matches, dummy, Condition::MATCHES);
}
// move single local candidate as appropriate...
if (search_domain == MATCHES && subcondition_matches.empty()) {
// move to non_matches
matches.clear();
non_matches.push_back(local_context.condition_local_candidate);
} else if (search_domain == NON_MATCHES && !subcondition_matches.empty()) {
// move to matches
non_matches.clear();
matches.push_back(local_context.condition_local_candidate);
}
} else {
// evaluate container objects once using default initial candidates
// of subcondition to find all subcondition matches in the Universe
std::shared_ptr<const UniverseObject> no_object;
ScriptingContext local_context(parent_context, no_object);
ObjectSet subcondition_matches;
m_condition->Eval(local_context, subcondition_matches);
// check all candidates to see if they contain any subcondition matches
EvalImpl(matches, non_matches, search_domain, ContainedBySimpleMatch(subcondition_matches));
}
}
bool ContainedBy::RootCandidateInvariant() const
{ return m_condition->RootCandidateInvariant(); }
bool ContainedBy::TargetInvariant() const
{ return m_condition->TargetInvariant(); }
bool ContainedBy::SourceInvariant() const
{ return m_condition->SourceInvariant(); }
std::string ContainedBy::Description(bool negated/* = false*/) const {
return str(FlexibleFormat((!negated)
? UserString("DESC_CONTAINED_BY")
: UserString("DESC_CONTAINED_BY_NOT"))
% m_condition->Description());
}
std::string ContainedBy::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "ContainedBy condition =\n";
retval += m_condition->Dump(ntabs+1);
return retval;
}
void ContainedBy::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
// objects that can be contained by other objects: fleets, planets, ships, buildings
AddFleetSet(condition_non_targets);
AddPlanetSet(condition_non_targets);
AddShipSet(condition_non_targets);
AddBuildingSet(condition_non_targets);
}
bool ContainedBy::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "ContainedBy::Match passed no candidate object";
return false;
}
// get containing objects
std::set<int> containers;
if (candidate->SystemID() != INVALID_OBJECT_ID)
containers.insert(candidate->SystemID());
if (candidate->ContainerObjectID() != INVALID_OBJECT_ID && candidate->ContainerObjectID() != candidate->SystemID())
containers.insert(candidate->ContainerObjectID());
ObjectSet container_objects = Objects().FindObjects<const UniverseObject>(containers);
if (container_objects.empty())
return false;
m_condition->Eval(local_context, container_objects);
return container_objects.empty();
}
void ContainedBy::SetTopLevelContent(const std::string& content_name) {
if (m_condition)
m_condition->SetTopLevelContent(content_name);
}
unsigned int ContainedBy::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::ContainedBy");
CheckSums::CheckSumCombine(retval, m_condition);
TraceLogger() << "GetCheckSum(ContainedBy): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// InSystem //
///////////////////////////////////////////////////////////
InSystem::InSystem(std::unique_ptr<ValueRef::ValueRefBase<int>>&& system_id) :
ConditionBase(),
m_system_id(std::move(system_id))
{}
bool InSystem::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const InSystem& rhs_ = static_cast<const InSystem&>(rhs);
CHECK_COND_VREF_MEMBER(m_system_id)
return true;
}
namespace {
struct InSystemSimpleMatch {
InSystemSimpleMatch(int system_id) :
m_system_id(system_id)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
if (m_system_id == INVALID_OBJECT_ID)
return candidate->SystemID() != INVALID_OBJECT_ID; // match objects in any system
else
return candidate->SystemID() == m_system_id; // match objects in specified system
}
int m_system_id;
};
}
void InSystem::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = !m_system_id || m_system_id->ConstantExpr() ||
(m_system_id->LocalCandidateInvariant() &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
// evaluate empire id once, and use to check all candidate objects
std::shared_ptr<const UniverseObject> no_object;
int system_id = (m_system_id ? m_system_id->Eval(ScriptingContext(parent_context, no_object)) : INVALID_OBJECT_ID);
EvalImpl(matches, non_matches, search_domain, InSystemSimpleMatch(system_id));
} else {
// re-evaluate empire id for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool InSystem::RootCandidateInvariant() const
{ return !m_system_id || m_system_id->RootCandidateInvariant(); }
bool InSystem::TargetInvariant() const
{ return !m_system_id || m_system_id->TargetInvariant(); }
bool InSystem::SourceInvariant() const
{ return !m_system_id || m_system_id->SourceInvariant(); }
std::string InSystem::Description(bool negated/* = false*/) const {
std::string system_str;
int system_id = INVALID_OBJECT_ID;
if (m_system_id && m_system_id->ConstantExpr())
system_id = m_system_id->Eval();
if (auto system = GetSystem(system_id))
system_str = system->Name();
else if (m_system_id)
system_str = m_system_id->Description();
std::string description_str;
if (!system_str.empty())
description_str = (!negated)
? UserString("DESC_IN_SYSTEM")
: UserString("DESC_IN_SYSTEM_NOT");
else
description_str = (!negated)
? UserString("DESC_IN_SYSTEM_SIMPLE")
: UserString("DESC_IN_SYSTEM_SIMPLE_NOT");
return str(FlexibleFormat(description_str) % system_str);
}
std::string InSystem::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "InSystem";
if (m_system_id)
retval += " id = " + m_system_id->Dump(ntabs);
retval += "\n";
return retval;
}
void InSystem::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
if (!m_system_id) {
// can match objects in any system, or any system
AddAllObjectsSet(condition_non_targets);
return;
}
bool simple_eval_safe = m_system_id->ConstantExpr() ||
(m_system_id->LocalCandidateInvariant() &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (!simple_eval_safe) {
// almost anything can be in a system, and can also match the system itself
AddAllObjectsSet(condition_non_targets);
return;
}
// simple case of a single specified system id; can add just objects in that system
int system_id = m_system_id->Eval(parent_context);
auto system = GetSystem(system_id);
if (!system)
return;
const ObjectMap& obj_map = Objects();
const std::set<int>& system_object_ids = system->ObjectIDs();
auto sys_objs = obj_map.FindObjects(system_object_ids);
// insert all objects that have the specified system id
condition_non_targets.reserve(sys_objs.size() + 1);
std::copy(sys_objs.begin(), sys_objs.end(), std::back_inserter(condition_non_targets));
// also insert system itself
condition_non_targets.push_back(system);
}
bool InSystem::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "InSystem::Match passed no candidate object";
return false;
}
int system_id = (m_system_id ? m_system_id->Eval(local_context) : INVALID_OBJECT_ID);
return InSystemSimpleMatch(system_id)(candidate);
}
void InSystem::SetTopLevelContent(const std::string& content_name) {
if (m_system_id)
m_system_id->SetTopLevelContent(content_name);
}
unsigned int InSystem::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::InSystem");
CheckSums::CheckSumCombine(retval, m_system_id);
TraceLogger() << "GetCheckSum(InSystem): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// ObjectID //
///////////////////////////////////////////////////////////
ObjectID::ObjectID(std::unique_ptr<ValueRef::ValueRefBase<int>>&& object_id) :
ConditionBase(),
m_object_id(std::move(object_id))
{}
bool ObjectID::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const ObjectID& rhs_ = static_cast<const ObjectID&>(rhs);
CHECK_COND_VREF_MEMBER(m_object_id)
return true;
}
namespace {
struct ObjectIDSimpleMatch {
ObjectIDSimpleMatch(int object_id) :
m_object_id(object_id)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
return candidate &&
m_object_id != INVALID_OBJECT_ID &&
candidate->ID() == m_object_id;
}
int m_object_id;
};
}
void ObjectID::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = !m_object_id || m_object_id->ConstantExpr() ||
(m_object_id->LocalCandidateInvariant() &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (simple_eval_safe) {
// evaluate empire id once, and use to check all candidate objects
std::shared_ptr<const UniverseObject> no_object;
int object_id = (m_object_id ? m_object_id->Eval(ScriptingContext(parent_context, no_object)) : INVALID_OBJECT_ID);
EvalImpl(matches, non_matches, search_domain, ObjectIDSimpleMatch(object_id));
} else {
// re-evaluate empire id for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool ObjectID::RootCandidateInvariant() const
{ return !m_object_id || m_object_id->RootCandidateInvariant(); }
bool ObjectID::TargetInvariant() const
{ return !m_object_id || m_object_id->TargetInvariant(); }
bool ObjectID::SourceInvariant() const
{ return !m_object_id || m_object_id->SourceInvariant(); }
std::string ObjectID::Description(bool negated/* = false*/) const {
std::string object_str;
int object_id = INVALID_OBJECT_ID;
if (m_object_id && m_object_id->ConstantExpr())
object_id = m_object_id->Eval();
if (auto system = GetSystem(object_id))
object_str = system->Name();
else if (m_object_id)
object_str = m_object_id->Description();
else
object_str = UserString("ERROR"); // should always have a valid ID for this condition
return str(FlexibleFormat((!negated)
? UserString("DESC_OBJECT_ID")
: UserString("DESC_OBJECT_ID_NOT"))
% object_str);
}
std::string ObjectID::Dump(unsigned short ntabs) const
{ return DumpIndent(ntabs) + "Object id = " + m_object_id->Dump(ntabs) + "\n"; }
void ObjectID::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
if (!m_object_id)
return;
bool simple_eval_safe = m_object_id->ConstantExpr() ||
(m_object_id->LocalCandidateInvariant() &&
(parent_context.condition_root_candidate || RootCandidateInvariant()));
if (!simple_eval_safe) {
AddAllObjectsSet(condition_non_targets);
return;
}
// simple case of a single specified id; can add just that object
std::shared_ptr<const UniverseObject> no_object;
int object_id = m_object_id->Eval(ScriptingContext(parent_context, no_object));
if (object_id == INVALID_OBJECT_ID)
return;
auto obj = Objects().ExistingObject(object_id);
if (obj)
condition_non_targets.push_back(obj);
}
bool ObjectID::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "ObjectID::Match passed no candidate object";
return false;
}
return ObjectIDSimpleMatch(m_object_id->Eval(local_context))(candidate);
}
void ObjectID::SetTopLevelContent(const std::string& content_name) {
if (m_object_id)
m_object_id->SetTopLevelContent(content_name);
}
unsigned int ObjectID::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::ObjectID");
CheckSums::CheckSumCombine(retval, m_object_id);
TraceLogger() << "GetCheckSum(ObjectID): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// PlanetType //
///////////////////////////////////////////////////////////
PlanetType::PlanetType(std::vector<std::unique_ptr<ValueRef::ValueRefBase< ::PlanetType>>>&& types) :
ConditionBase(),
m_types(std::move(types))
{}
bool PlanetType::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const PlanetType& rhs_ = static_cast<const PlanetType&>(rhs);
if (m_types.size() != rhs_.m_types.size())
return false;
for (unsigned int i = 0; i < m_types.size(); ++i) {
CHECK_COND_VREF_MEMBER(m_types.at(i))
}
return true;
}
namespace {
struct PlanetTypeSimpleMatch {
PlanetTypeSimpleMatch(const std::vector< ::PlanetType>& types) :
m_types(types)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
// is it a planet or on a planet?
auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
std::shared_ptr<const ::Building> building;
if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
planet = GetPlanet(building->PlanetID());
}
if (planet) {
// is it one of the specified building types?
return std::count(m_types.begin(), m_types.end(), planet->Type());
}
return false;
}
const std::vector< ::PlanetType>& m_types;
};
}
void PlanetType::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
if (simple_eval_safe) {
// check each valueref for invariance to local candidate
for (auto& type : m_types) {
if (!type->LocalCandidateInvariant()) {
simple_eval_safe = false;
break;
}
}
}
if (simple_eval_safe) {
// evaluate types once, and use to check all candidate objects
std::vector< ::PlanetType> types;
// get all types from valuerefs
for (auto& type : m_types) {
types.push_back(type->Eval(parent_context));
}
EvalImpl(matches, non_matches, search_domain, PlanetTypeSimpleMatch(types));
} else {
// re-evaluate contained objects for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool PlanetType::RootCandidateInvariant() const {
for (auto& type : m_types) {
if (!type->RootCandidateInvariant())
return false;
}
return true;
}
bool PlanetType::TargetInvariant() const {
for (auto& type : m_types) {
if (!type->TargetInvariant())
return false;
}
return true;
}
bool PlanetType::SourceInvariant() const {
for (auto& type : m_types) {
if (!type->SourceInvariant())
return false;
}
return true;
}
std::string PlanetType::Description(bool negated/* = false*/) const {
std::string values_str;
for (unsigned int i = 0; i < m_types.size(); ++i) {
values_str += m_types[i]->ConstantExpr() ?
UserString(boost::lexical_cast<std::string>(m_types[i]->Eval())) :
m_types[i]->Description();
if (2 <= m_types.size() && i < m_types.size() - 2) {
values_str += ", ";
} else if (i == m_types.size() - 2) {
values_str += m_types.size() < 3 ? " " : ", ";
values_str += UserString("OR");
values_str += " ";
}
}
return str(FlexibleFormat((!negated)
? UserString("DESC_PLANET_TYPE")
: UserString("DESC_PLANET_TYPE_NOT"))
% values_str);
}
std::string PlanetType::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "Planet type = ";
if (m_types.size() == 1) {
retval += m_types[0]->Dump(ntabs) + "\n";
} else {
retval += "[ ";
for (auto& type : m_types) {
retval += type->Dump(ntabs) + " ";
}
retval += "]\n";
}
return retval;
}
void PlanetType::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,
ObjectSet& condition_non_targets) const
{
AddPlanetSet(condition_non_targets);
AddBuildingSet(condition_non_targets);
}
bool PlanetType::Match(const ScriptingContext& local_context) const {
auto candidate = local_context.condition_local_candidate;
if (!candidate) {
ErrorLogger() << "PlanetType::Match passed no candidate object";
return false;
}
auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
std::shared_ptr<const ::Building> building;
if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
planet = GetPlanet(building->PlanetID());
}
if (planet) {
for (auto& type : m_types) {
if (type->Eval(ScriptingContext(local_context)) == planet->Type())
return true;
}
}
return false;
}
void PlanetType::SetTopLevelContent(const std::string& content_name) {
for (auto& type : m_types) {
if (type)
type->SetTopLevelContent(content_name);
}
}
unsigned int PlanetType::GetCheckSum() const {
unsigned int retval{0};
CheckSums::CheckSumCombine(retval, "Condition::PlanetType");
CheckSums::CheckSumCombine(retval, m_types);
TraceLogger() << "GetCheckSum(PlanetType): retval: " << retval;
return retval;
}
///////////////////////////////////////////////////////////
// PlanetSize //
///////////////////////////////////////////////////////////
PlanetSize::PlanetSize(std::vector<std::unique_ptr<ValueRef::ValueRefBase< ::PlanetSize>>>&& sizes) :
ConditionBase(),
m_sizes(std::move(sizes))
{}
bool PlanetSize::operator==(const ConditionBase& rhs) const {
if (this == &rhs)
return true;
if (typeid(*this) != typeid(rhs))
return false;
const PlanetSize& rhs_ = static_cast<const PlanetSize&>(rhs);
if (m_sizes.size() != rhs_.m_sizes.size())
return false;
for (unsigned int i = 0; i < m_sizes.size(); ++i) {
CHECK_COND_VREF_MEMBER(m_sizes.at(i))
}
return true;
}
namespace {
struct PlanetSizeSimpleMatch {
PlanetSizeSimpleMatch(const std::vector< ::PlanetSize>& sizes) :
m_sizes(sizes)
{}
bool operator()(std::shared_ptr<const UniverseObject> candidate) const {
if (!candidate)
return false;
// is it a planet or on a planet? TODO: This concept should be generalized and factored out.
auto planet = std::dynamic_pointer_cast<const Planet>(candidate);
std::shared_ptr<const ::Building> building;
if (!planet && (building = std::dynamic_pointer_cast<const ::Building>(candidate))) {
planet = GetPlanet(building->PlanetID());
}
if (planet) {
// is it one of the specified building types?
for (auto size : m_sizes) {
if (planet->Size() == size)
return true;
}
}
return false;
}
const std::vector< ::PlanetSize>& m_sizes;
};
}
void PlanetSize::Eval(const ScriptingContext& parent_context,
ObjectSet& matches, ObjectSet& non_matches,
SearchDomain search_domain/* = NON_MATCHES*/) const
{
bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant();
if (simple_eval_safe) {
// check each valueref for invariance to local candidate
for (auto& size : m_sizes) {
if (!size->LocalCandidateInvariant()) {
simple_eval_safe = false;
break;
}
}
}
if (simple_eval_safe) {
// evaluate types once, and use to check all candidate objects
std::vector< ::PlanetSize> sizes;
// get all types from valuerefs
for (auto& size : m_sizes) {
sizes.push_back(size->Eval(parent_context));
}
EvalImpl(matches, non_matches, search_domain, PlanetSizeSimpleMatch(sizes));
} else {
// re-evaluate contained objects for each candidate object
ConditionBase::Eval(parent_context, matches, non_matches, search_domain);
}
}
bool PlanetSize::RootCandidateInvariant() const {
for (auto& size : m_sizes) {
if (!size->RootCandidateInvariant())
return false;
}
return true;
}
bool PlanetSize::TargetInvariant() const {
for (auto& size : m_sizes) {
if (!size->TargetInvariant())
return false;
}
return true;
}
bool PlanetSize::SourceInvariant() const {
for (auto& size : m_sizes) {
if (!size->SourceInvariant())
return false;
}
return true;
}
std::string PlanetSize::Description(bool negated/* = false*/) const {
std::string values_str;
for (unsigned int i = 0; i < m_sizes.size(); ++i) {
values_str += m_sizes[i]->ConstantExpr() ?
UserString(boost::lexical_cast<std::string>(m_sizes[i]->Eval())) :
m_sizes[i]->Description();
if (2 <= m_sizes.size() && i < m_sizes.size() - 2) {
values_str += ", ";
} else if (i == m_sizes.size() - 2) {
values_str += m_sizes.size() < 3 ? " " : ", ";
values_str += UserString("OR");
values_str += " ";
}
}
return str(FlexibleFormat((!negated)
? UserString("DESC_PLANET_SIZE")
: UserString("DESC_PLANET_SIZE_NOT"))
% values_str);
}
std::string PlanetSize::Dump(unsigned short ntabs) const {
std::string retval = DumpIndent(ntabs) + "Planet size = ";
if (m_sizes.size() == 1) {
retval += m_sizes[0]->Dump(ntabs) + "\n";
} else {
retval += "[ ";
for (auto& size : m_sizes) {
retval += size->Dump(ntabs) + " ";
}
retval += "]\n";
}
return retval;
}
void PlanetSize::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context,