From d1dd7eef48c3c528974a4d85bf5d404f305601be Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Aug 2014 19:40:22 +0300 Subject: [PATCH] Proper fix for #1712 - Building requirement tests are now fixed - added minimize() method in logical expressions that will remove excessive elements - replaced forEach() with morph() that allows replacing types in variant --- client/CCastleInterface.cpp | 6 +-- lib/CGameInfoCallback.cpp | 27 ++-------- lib/LogicalExpression.h | 86 ++++++++++++++++++++++++------- lib/NetPacksLib.cpp | 5 +- lib/mapObjects/CGTownInstance.cpp | 34 ++++++++++++ lib/mapObjects/CGTownInstance.h | 2 + lib/mapping/CMap.cpp | 5 +- lib/mapping/MapFormatH3M.cpp | 5 +- 8 files changed, 115 insertions(+), 55 deletions(-) diff --git a/client/CCastleInterface.cpp b/client/CCastleInterface.cpp index edefe065c..0a386b232 100644 --- a/client/CCastleInterface.cpp +++ b/client/CCastleInterface.cpp @@ -1358,13 +1358,9 @@ std::string CBuildWindow::getTextForState(int state) { return town->town->buildings.at(build)->Name(); }; - /*auto toBool = [&](const BuildingID build) - { - return town->hasBuilt(build); - };*/ ret = CGI->generaltexth->allTexts[52]; - ret += "\n" + building->requirements.toString(toStr); + ret += "\n" + town->genBuildingRequirements(building->bid).toString(toStr); break; } case EBuildingState::MISSING_BASE: diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 92e689be6..8617c42e3 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -403,33 +403,12 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow return EBuildingState::NO_WATER; //lack of water } - /// returns true if building prerequisites are fulfilled - std::function buildTest; - - auto dependTest = [&](BuildingID id) -> bool + auto buildTest = [&](BuildingID id) -> bool { - const CBuilding * build = t->town->buildings.at(id); - - if (build->upgrade != BuildingID::NONE) - { - if (!t->hasBuilt(build->upgrade)) - return false; - - if (!t->town->buildings.at(build->upgrade)->requirements.test(buildTest)) - return false; - } - - if (!build->requirements.test(buildTest)) - return false; - return true; + return t->hasBuilt(id); }; - buildTest = [&](BuildingID bid) - { - return t->hasBuilt(bid) && dependTest(bid); - }; - - if (!dependTest(ID)) + if (!t->genBuildingRequirements(ID).test(buildTest)) return EBuildingState::PREREQUIRES; if(t->builded >= VLC->modh->settings.MAX_BUILDING_PER_TURN) diff --git a/lib/LogicalExpression.h b/lib/LogicalExpression.h index 13dbc6e0b..c31288eb5 100644 --- a/lib/LogicalExpression.h +++ b/lib/LogicalExpression.h @@ -45,6 +45,11 @@ namespace LogicalExpressionDetail std::vector expressions; + bool operator == (const Element & other) const + { + return expressions == other.expressions; + } + template void serialize(Handler & h, const int version) { @@ -147,39 +152,73 @@ namespace LogicalExpressionDetail /// Simple foreach visitor template - class ForEachVisitor : public boost::static_visitor + class ForEachVisitor : public boost::static_visitor::Variant> { typedef ExpressionBase Base; - std::function visitor; + std::function visitor; public: - ForEachVisitor(std::function visitor): + ForEachVisitor(std::function visitor): visitor(visitor) {} - //FIXME: duplicated code - void operator()(typename Base::OperatorAny & element) const + typename Base::Variant operator()(const typename Base::Value & element) const { - for (auto & entry : element.expressions) - boost::apply_visitor(*this, entry); + return visitor(element); } - void operator()(typename Base::OperatorAll & element) const + template + typename Base::Variant operator()(Type element) const { for (auto & entry : element.expressions) - boost::apply_visitor(*this, entry); + entry = boost::apply_visitor(*this, entry); + return element; + } + }; + + /// Minimizing visitor that removes all redundant elements from variant (e.g. AllOf inside another AllOf can be merged safely) + template + class MinimizingVisitor : public boost::static_visitor::Variant> + { + typedef ExpressionBase Base; + + public: + typename Base::Variant operator()(const typename Base::Value & element) const + { + return element; } - void operator()(typename Base::OperatorNone & element) const + template + typename Base::Variant operator()(const Type & element) const { - for (auto & entry : element.expressions) - boost::apply_visitor(*this, entry); - } + Type ret; - void operator()(typename Base::Value & element) const - { - visitor(element); + for (auto & entryRO : element.expressions) + { + auto entry = boost::apply_visitor(*this, entryRO); + + try + { + // copy entries from child of this type + auto sublist = boost::get(entry).expressions; + std::move(sublist.begin(), sublist.end(), std::back_inserter(ret.expressions)); + } + catch (boost::bad_get &) + { + // different type (e.g. allOf vs oneOf) just copy + ret.expressions.push_back(entry); + } + } + + for ( auto it = ret.expressions.begin(); it != ret.expressions.end();) + { + if (std::find(ret.expressions.begin(), it, *it) != it) + it = ret.expressions.erase(it); // erase duplicate + else + it++; // goto next + } + return ret; } }; @@ -370,16 +409,23 @@ public: std::swap(data, expr.data); } - Variant get() + Variant get() const { return data; } /// Simple visitor that visits all entries in expression - void forEach(std::function visitor) + Variant morph(std::function morpher) const { - LogicalExpressionDetail::ForEachVisitor testVisitor(visitor); - boost::apply_visitor(testVisitor, data); + LogicalExpressionDetail::ForEachVisitor visitor(morpher); + return boost::apply_visitor(visitor, data); + } + + /// Minimizes expression, removing any redundant elements + void minimize() + { + LogicalExpressionDetail::MinimizingVisitor visitor; + data = boost::apply_visitor(visitor, data); } /// calculates if expression evaluates to "true". diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 85c383cac..1abf1cc10 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -396,7 +396,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) for (TriggeredEvent & event : gs->map->triggeredEvents) { - auto patcher = [&](EventCondition & cond) + auto patcher = [&](EventCondition cond) -> EventExpression::Variant { if (cond.object == obj) { @@ -411,8 +411,9 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) cond.value = 0; // destroyed object, from now on can not be fulfilled } } + return cond; }; - event.trigger.forEach(patcher); + event.trigger = event.trigger.morph(patcher); } gs->map->objects[id.getNum()].dellNull(); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 3bff6cefa..a86df42d7 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1004,6 +1004,40 @@ bool CGTownInstance::hasBuilt(BuildingID buildingID) const return vstd::contains(builtBuildings, buildingID); } +CBuilding::TRequired CGTownInstance::genBuildingRequirements(BuildingID buildID) const +{ + const CBuilding * building = town->buildings.at(buildID); + + std::function dependTest = + [&](const BuildingID & id) -> CBuilding::TRequired::Variant + { + const CBuilding * build = town->buildings.at(id); + + if (!hasBuilt(id)) + return id; + + if (build->upgrade != BuildingID::NONE && !hasBuilt(build->upgrade)) + return build->upgrade; + + return build->requirements.morph(dependTest); + }; + + CBuilding::TRequired::OperatorAll requirements; + if (building->upgrade != BuildingID::NONE) + { + const CBuilding * upgr = town->buildings.at(building->upgrade); + + requirements.expressions.push_back(upgr->bid); + requirements.expressions.push_back(upgr->requirements.morph(dependTest)); + } + requirements.expressions.push_back(building->requirements.morph(dependTest)); + + CBuilding::TRequired::Variant variant(requirements); + CBuilding::TRequired ret(variant); + ret.minimize(); + return ret; +} + void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const { if(visitingHero == h) diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fd105365f..baf9f63a0 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -232,6 +232,8 @@ public: bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero int getTownLevel() const; + CBuilding::TRequired genBuildingRequirements(BuildingID build) const; + void removeCapitols (PlayerColor owner) const; void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 1d53f8d88..0217d9595 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -441,7 +441,7 @@ void CMap::checkForObjectives() // NOTE: probably should be moved to MapFormatH3M.cpp for (TriggeredEvent & event : triggeredEvents) { - auto patcher = [&](EventCondition & cond) + auto patcher = [&](EventCondition cond) -> EventExpression::Variant { switch (cond.condition) { @@ -491,8 +491,9 @@ void CMap::checkForObjectives() //break; case EventCondition::DAYS_WITHOUT_TOWN: //break; case EventCondition::STANDARD_WIN: } + return cond; }; - event.trigger.forEach(patcher); + event.trigger = event.trigger.morph(patcher); } } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 5e7694528..3325645e2 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -676,16 +676,17 @@ void CMapLoaderH3M::readAllowedArtifacts() // Messy, but needed for (TriggeredEvent & event : map->triggeredEvents) { - auto patcher = [&](EventCondition & cond) + auto patcher = [&](EventCondition cond) -> EventExpression::Variant { if (cond.condition == EventCondition::HAVE_ARTIFACT || cond.condition == EventCondition::TRANSPORT) { map->allowedArtifact[cond.objectType] = false; } + return cond; }; - event.trigger.forEach(patcher); + event.trigger = event.trigger.morph(patcher); } }