Browse Source

Extended building dependencies:
- buiding/structure lists must use object format. This may break some
outdated mods.
- generic support for logical expressions that consist from and/or/not
operators.
- string ID's for buidings are now actually used.

Ivan Savenko 12 years ago
parent
commit
ee1b0459e6

+ 16 - 10
AI/VCAI/VCAI.cpp

@@ -934,18 +934,13 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
 	if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
 		return true;
 
-	std::set<BuildingID> toBuild = cb->getBuildingRequiments(t, building);
+	const CBuilding * buildPtr = t->town->buildings.at(building);
 
-	//erase all already built buildings
-	for (auto buildIter = toBuild.begin(); buildIter != toBuild.end();)
+	auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
 	{
-		if (t->hasBuilt(*buildIter))
-			toBuild.erase(buildIter++);
-		else
-			buildIter++;
-	}
-
-	toBuild.insert(building);
+		return t->hasBuilt(buildID);
+	});
+	toBuild.push_back(building);
 
 	for(BuildingID buildID : toBuild)
 	{
@@ -989,6 +984,17 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
 			}
 			continue;
 		}
+		else if (canBuild == EBuildingState::PREREQUIRES)
+		{
+			// can happen when dependencies have their own missing dependencies
+			if (tryBuildStructure(t, buildID, maxDays - 1))
+				return true;
+		}
+		else if (canBuild == EBuildingState::MISSING_BASE)
+		{
+			if (tryBuildStructure(t, b->upgrade, maxDays - 1))
+				 return true;
+		}
 	}
 	return false;
 }

+ 121 - 111
client/CCastleInterface.cpp

@@ -40,13 +40,13 @@ using namespace boost::assign;
 const CBuilding * CBuildingRect::getBuilding()
 {
 	if (!str->building)
-		return nullptr;
-
-	if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes)
-		return town->town->buildings.at(str->building->getBase());
-
-	return str->building;
-}
+		return nullptr;
+
+	if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes)
+		return town->town->buildings.at(str->building->getBase());
+
+	return str->building;
+}
 
 CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance *Town, const CStructure *Str)
 	:CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE | CShowableAnim::USE_RLE),
@@ -114,13 +114,13 @@ void CBuildingRect::clickRight(tribool down, bool previousState)
 {
 	if((!area) || (!((bool)down)) || (this!=parent->selectedBuilding) || getBuilding() == nullptr)
 		return;
-	if( !CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) //inside building image
-	{
-		BuildingID bid = getBuilding()->bid;
-		const CBuilding *bld = town->town->buildings.at(bid);
-		if (bid < BuildingID::DWELL_FIRST)
-		{
-			CRClickPopup::createAndPush(CInfoWindow::genText(bld->Name(), bld->Description()),
+	if( !CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) //inside building image
+	{
+		BuildingID bid = getBuilding()->bid;
+		const CBuilding *bld = town->town->buildings.at(bid);
+		if (bid < BuildingID::DWELL_FIRST)
+		{
+			CRClickPopup::createAndPush(CInfoWindow::genText(bld->Name(), bld->Description()),
 			                            new CComponent(CComponent::building, bld->town->faction->index, bld->bid));
 		}
 		else
@@ -209,20 +209,20 @@ std::string CBuildingRect::getSubtitle()//hover text for building
 	if (!getBuilding())
 		return "";
 
-	int bid = getBuilding()->bid;
-
-	if (bid<30)//non-dwellings - only buiding name
-		return town->town->buildings.at(getBuilding()->bid)->Name();
-	else//dwellings - recruit %creature%
-	{
-		auto & availableCreatures = town->creatures[(bid-30)%GameConstants::CREATURES_PER_TOWN].second;
-		if(availableCreatures.size())
-		{
-			int creaID = availableCreatures.back();//taking last of available creatures
-			return CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures.at(creaID)->namePl;
-		}
-		else
-		{
+	int bid = getBuilding()->bid;
+
+	if (bid<30)//non-dwellings - only buiding name
+		return town->town->buildings.at(getBuilding()->bid)->Name();
+	else//dwellings - recruit %creature%
+	{
+		auto & availableCreatures = town->creatures[(bid-30)%GameConstants::CREATURES_PER_TOWN].second;
+		if(availableCreatures.size())
+		{
+			int creaID = availableCreatures.back();//taking last of available creatures
+			return CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures.at(creaID)->namePl;
+		}
+		else
+		{
             logGlobal->warnStream() << "Problem: dwelling with id " << bid << " offers no creatures!";
 			return "#ERROR#";
 		}
@@ -255,18 +255,18 @@ void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
 
 CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstance *Town, int level):
     CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "CRTOINFO", Point(centerX, centerY))
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	const CCreature * creature = CGI->creh->creatures.at(Town->creatures.at(level).second.back());
-
-	title = new CLabel(80, 30, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
-	animation =  new CCreaturePic(30, 44, creature, true, true);
-	
-	std::string text = boost::lexical_cast<std::string>(Town->creatures.at(level).first);
-	available = new CLabel(80,190, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[217] + text);
-	costPerTroop = new CLabel(80, 227, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[346]);
-	
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+
+	const CCreature * creature = CGI->creh->creatures.at(Town->creatures.at(level).second.back());
+
+	title = new CLabel(80, 30, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
+	animation =  new CCreaturePic(30, 44, creature, true, true);
+	
+	std::string text = boost::lexical_cast<std::string>(Town->creatures.at(level).first);
+	available = new CLabel(80,190, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[217] + text);
+	costPerTroop = new CLabel(80, 227, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[346]);
+	
 	for(int i = 0; i<GameConstants::RESOURCE_QUANTITY; i++)
 	{
 		if(creature->cost[i])
@@ -503,13 +503,13 @@ void CCastleBuildings::recreate()
 			groups[structure->building->getBase()].push_back(structure);
 		}
 	}
-
-	for(auto & entry : groups)
-	{
-		const CBuilding * build = town->town->buildings.at(entry.first);
-
-		const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
-		{
+
+	for(auto & entry : groups)
+	{
+		const CBuilding * build = town->town->buildings.at(entry.first);
+
+		const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
+		{
 			return build->getDistance(a->building->bid)
 			     < build->getDistance(b->building->bid);
 		});
@@ -526,17 +526,17 @@ CCastleBuildings::~CCastleBuildings()
 {
 }
 
-void CCastleBuildings::addBuilding(BuildingID building)
-{
-	//FIXME: implement faster method without complete recreation of town
-	BuildingID base = town->town->buildings.at(building)->getBase();
-
-	recreate();
-
-	auto & structures = groups.at(base);
-
-	for(CBuildingRect * rect : buildings)
-	{
+void CCastleBuildings::addBuilding(BuildingID building)
+{
+	//FIXME: implement faster method without complete recreation of town
+	BuildingID base = town->town->buildings.at(building)->getBase();
+
+	recreate();
+
+	auto & structures = groups.at(base);
+
+	for(CBuildingRect * rect : buildings)
+	{
 		if (vstd::contains(structures, rect->str))
 		{
 			//reset animation
@@ -1111,13 +1111,13 @@ CTownInfo::CTownInfo(int posX, int posY, const CGTownInstance* Town, bool townHa
 	{
 		buildID = 6 + town->fortLevel();
 		if (buildID == 6)
-			return;
-		picture = new CAnimImage("ITMCL.DEF", town->fortLevel()-1);
-	}
-	building = town->town->buildings.at(BuildingID(buildID));
-	pos = picture->pos;
-}
-
+			return;
+		picture = new CAnimImage("ITMCL.DEF", town->fortLevel()-1);
+	}
+	building = town->town->buildings.at(BuildingID(buildID));
+	pos = picture->pos;
+}
+
 void CTownInfo::hover(bool on)
 {
 	if(on)
@@ -1238,7 +1238,7 @@ void CHallInterface::CBuildingBox::hover(bool on)
 	if(on)
 	{
 		std::string toPrint;
-		if(state==EBuildingState::PREREQUIRES)
+		if(state==EBuildingState::PREREQUIRES || state == EBuildingState::MISSING_BASE)
 			toPrint = CGI->generaltexth->hcommands[5];
 		else if(state==EBuildingState::CANT_BUILD_TODAY)
 			toPrint = CGI->generaltexth->allTexts[223];
@@ -1276,8 +1276,8 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance *
 	
 	state = LOCPLINT->cb->canBuildStructure(town,building->bid);
 	assert(state < EBuildingState::BUILDING_ERROR);
-	static int panelIndex[9] = { 3,  3,  3, 0, 0, 2, 2,  1, 2};
-	static int  iconIndex[9] = {-1, -1, -1, 0, 0, 1, 2, -1, 1};
+	static int panelIndex[10] = { 3,  3,  3, 0, 0, 2, 2,  1, 2, 2};
+	static int  iconIndex[10] = {-1, -1, -1, 0, 0, 1, 2, -1, 1, 1};
 
 	picture = new CAnimImage(town->town->clientInfo.buildingsIcons, building->bid, 0, 2, 2);
 	panel = new CAnimImage("TPTHBAR", panelIndex[state], 0,   1, 73);
@@ -1295,13 +1295,13 @@ CHallInterface::CHallInterface(const CGTownInstance *Town):
 	resdatabar = new CMinorResDataBar;
 	resdatabar->pos.x += pos.x;
 	resdatabar->pos.y += pos.y;
-	Rect barRect(5, 556, 740, 18);
-	statusBar = new CGStatusBar(new CPicture(*background, barRect, 5, 556, false));
-
-	title = new CLabel(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
-	exit = new CAdventureMapButton(CGI->generaltexth->hcommands[8], "", 
-	           boost::bind(&CHallInterface::close,this), 748, 556, "TPMAGE1.DEF", SDLK_RETURN);
-	exit->assignedKeys.insert(SDLK_ESCAPE);
+	Rect barRect(5, 556, 740, 18);
+	statusBar = new CGStatusBar(new CPicture(*background, barRect, 5, 556, false));
+
+	title = new CLabel(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
+	exit = new CAdventureMapButton(CGI->generaltexth->hcommands[8], "", 
+	           boost::bind(&CHallInterface::close,this), 748, 556, "TPMAGE1.DEF", SDLK_RETURN);
+	exit->assignedKeys.insert(SDLK_ESCAPE);
 
 	auto & boxList = town->town->clientInfo.hallSlots;
 	boxes.resize(boxList.size());
@@ -1310,13 +1310,13 @@ CHallInterface::CHallInterface(const CGTownInstance *Town):
 		for(size_t col=0; col<boxList[row].size(); col++) //for each box
 		{
 			const CBuilding *building = nullptr;
-			for(auto & elem : boxList[row][col])//we are looking for the first not build structure
-			{
-				auto buildingID = elem;
-				building = town->town->buildings.at(buildingID);
-
-				if(!vstd::contains(town->builtBuildings,buildingID))
-					break;
+			for(auto & elem : boxList[row][col])//we are looking for the first not build structure
+			{
+				auto buildingID = elem;
+				building = town->town->buildings.at(buildingID);
+
+				if(!vstd::contains(town->builtBuildings,buildingID))
+					break;
 			}
 			int posX = pos.w/2 - boxList[row].size()*154/2 - (boxList[row].size()-1)*20 + 194*col,
 			    posY = 35 + 104*row;
@@ -1336,28 +1336,38 @@ void CBuildWindow::buyFunc()
 std::string CBuildWindow::getTextForState(int state)
 {
 	std::string ret;
-	if(state<7)
+	if(state < EBuildingState::ALLOWED)
 		ret =  CGI->generaltexth->hcommands[state];
 	switch (state)
 	{
-	case 4:	case 5: case 6:
-		ret.replace(ret.find_first_of("%s"),2,building->Name());
+	case EBuildingState::ALREADY_PRESENT:
+	case EBuildingState::CANT_BUILD_TODAY:
+	case EBuildingState::NO_RESOURCES:
+		ret.replace(ret.find_first_of("%s"), 2, building->Name());
 		break;
-	case 7:
+	case EBuildingState::ALLOWED:
 		return CGI->generaltexth->allTexts[219]; //all prereq. are met
-	case 8:
+	case EBuildingState::PREREQUIRES:
 		{
+			auto toStr = [&](const BuildingID build) -> std::string
+			{
+				return town->town->buildings.at(build)->Name();
+			};
+			/*auto toBool = [&](const BuildingID build)
+			{
+				return town->hasBuilt(build);
+			};*/
+
 			ret = CGI->generaltexth->allTexts[52];
-			std::set<BuildingID> reqs= LOCPLINT->cb->getBuildingRequiments(town, building->bid);
-
-			for(const auto & i : reqs)
-			{
-				if (vstd::contains(town->builtBuildings, i))
-					continue;//skipping constructed buildings
-				ret+= town->town->buildings.at(i)->Name() + ", ";
-			}
-			ret.erase(ret.size()-2);
-		}
+			ret += "\n" + building->requirements.toString(toStr);
+			break;
+		}
+	case EBuildingState::MISSING_BASE:
+		{
+			std::string msg = CGI->generaltexth->localizedTexts["townHall"]["missingBase"].String();
+			ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->Name());
+			break;
+		}
 	}
 	return ret;
 }
@@ -1423,13 +1433,13 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	ui32 fortSize = town->creatures.size();
-	if (fortSize > GameConstants::CREATURES_PER_TOWN && town->creatures.back().second.empty())
-		fortSize--;
-	
-	const CBuilding *fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6));
-	title = new CLabel(400, 12, FONT_BIG, CENTER, Colors::WHITE, fortBuilding->Name());
-	
-	std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->Name());
+	if (fortSize > GameConstants::CREATURES_PER_TOWN && town->creatures.back().second.empty())
+		fortSize--;
+	
+	const CBuilding *fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6));
+	title = new CLabel(400, 12, FONT_BIG, CENTER, Colors::WHITE, fortBuilding->Name());
+	
+	std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->Name());
 	exit = new CAdventureMapButton(text, "", boost::bind(&CFortScreen::close,this) ,748, 556, "TPMAGE1", SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);
 
@@ -1555,13 +1565,13 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 	sizes.y+=21;
 	values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[193], CGI->generaltexth->fcommands[4], creature->valOfBonuses(Bonus::STACKS_SPEED)));
 	sizes.y+=20;
-	values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[194], CGI->generaltexth->fcommands[5], town->creatureGrowth(level)));
-
-	creatureName = new CLabel(78,  11, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
-	dwellingName = new CLabel(78, 101, FONT_SMALL, CENTER, Colors::WHITE, town->town->buildings.at(buildingID)->Name());
-
-	if (vstd::contains(town->builtBuildings, buildingID))
-	{
+	values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[194], CGI->generaltexth->fcommands[5], town->creatureGrowth(level)));
+
+	creatureName = new CLabel(78,  11, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
+	dwellingName = new CLabel(78, 101, FONT_SMALL, CENTER, Colors::WHITE, town->town->buildings.at(buildingID)->Name());
+
+	if (vstd::contains(town->builtBuildings, buildingID))
+	{
 		ui32 available = town->creatures[level].first;
 		std::string availableText = CGI->generaltexth->allTexts[217]+ boost::lexical_cast<std::string>(available);
 		availableCount = new CLabel(78, 119, FONT_SMALL, CENTER, Colors::WHITE, availableText);

+ 3 - 1
client/gui/CIntObjectClasses.cpp

@@ -1275,7 +1275,7 @@ void CTextContainer::blitLine(SDL_Surface *to, Rect destRect, std::string what)
 		size_t end = what.find_first_of(delimeters[currDelimeter % 2], begin);
 		if (begin != end)
 		{
-			std::string toPrint = what.substr(begin, end-1);
+			std::string toPrint = what.substr(begin, end - begin);
 
 			if (currDelimeter % 2) // Enclosed in {} text - set to yellow
 				f->renderTextLeft(to, toPrint, Colors::YELLOW, where);
@@ -1388,6 +1388,8 @@ CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts F
 	label = new CMultiLineLabel(rect, Font, Align, Color);
 
 	type |= REDRAW_PARENT;
+	pos.x += rect.x;
+	pos.y += rect.y;
 	pos.h = rect.h;
 	pos.w = rect.w;
 

+ 1 - 1
config/factions/rampart.json

@@ -184,7 +184,7 @@
 				"dwellingLvl3":   { "id" : 32, "requires" : [ 30 ] },
 				"dwellingLvl4":   { "id" : 33, "requires" : [ 32 ] },
 				"dwellingLvl5":   { "id" : 34, "requires" : [ 32 ] },
-				"dwellingLvl6":   { "id" : 35, "requires" : [ 33, 34 ] },
+				"dwellingLvl6":   { "id" : 35, "requires" : [ "allOf", [ "dwellingLvl3" ], [ "dwellingLvl4" ] ] },
 				"dwellingLvl7":   { "id" : 36, "requires" : [ 35, 1 ] },
 				"dwellingUpLvl1": { "id" : 37, "upgrades" : 30 },
 				"dwellingUpLvl2": { "id" : 38, "upgrades" : 31 },

+ 10 - 0
config/translate.json

@@ -44,5 +44,15 @@
 			"label" : "Select resolution",
 			"help" : "Change in-game screen resolution."
 		}
+	},
+	"townHall" :
+	{
+		"missingBase" : "Base building %s must be built first"
+	},
+	"logicalExpressions" :
+	{
+		"anyOf"  :  "Any of the following:",
+		"allOf"  :  "All of the following:",
+		"noneOf" : "None of the following:"
 	}
 }

+ 1 - 0
lib/CMakeLists.txt

@@ -66,6 +66,7 @@ set(lib_SRCS
 		HeroBonus.cpp
 		JsonDetail.cpp
 		JsonNode.cpp
+		LogicalExpression.cpp
 		ResourceSet.cpp
 		VCMI_Lib.cpp
 		VCMIDirs.cpp

+ 60 - 23
lib/CModHandler.cpp

@@ -44,12 +44,13 @@ void CIdentifierStorage::checkIdentifier(std::string & ID)
 }
 
 CIdentifierStorage::ObjectCallback::ObjectCallback(std::string localScope, std::string remoteScope, std::string type,
-                                                   std::string name, const std::function<void(si32)> & callback):
+												   std::string name, const std::function<void(si32)> & callback, bool optional):
     localScope(localScope),
     remoteScope(remoteScope),
     type(type),
     name(name),
-    callback(callback)
+	callback(callback),
+	optional(optional)
 {}
 
 static std::pair<std::string, std::string> splitString(std::string input, char separator)
@@ -84,14 +85,14 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type,
 {
 	auto pair = splitString(name, ':'); // remoteScope:name
 
-	requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback));
+	requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, false));
 }
 
 void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
 {
 	auto pair = splitString(name.String(), ':'); // remoteScope:name
 
-	requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback));
+	requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, false));
 }
 
 void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback)
@@ -99,7 +100,34 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun
 	auto pair  = splitString(name.String(), ':'); // remoteScope:<type.name>
 	auto pair2 = splitString(pair.second,   '.'); // type.name
 
-	requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback));
+	requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback, false));
+}
+
+void CIdentifierStorage::tryRequestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback)
+{
+	auto pair = splitString(name, ':'); // remoteScope:name
+
+	requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, true));
+}
+
+void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
+{
+	auto pair = splitString(name.String(), ':'); // remoteScope:name
+
+	requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, true));
+}
+
+boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent)
+{
+	auto pair = splitString(name.String(), ':'); // remoteScope:name
+	auto idList = getIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, std::function<void(si32)>(), silent));
+
+	if (idList.size() == 1)
+		return idList.front().id;
+	if (!silent)
+		logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << type;
+
+	return boost::optional<si32>();
 }
 
 void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier)
@@ -114,7 +142,7 @@ void CIdentifierStorage::registerObject(std::string scope, std::string type, std
 	registeredObjects.insert(std::make_pair(fullID, data));
 }
 
-bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
+std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(const ObjectCallback & request)
 {
 	std::set<std::string> allowedScopes;
 
@@ -141,35 +169,44 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
 	auto entries = registeredObjects.equal_range(fullID);
 	if (entries.first != entries.second)
 	{
-		size_t matchesFound = 0;
+		std::vector<ObjectData> locatedIDs;
 
 		for (auto it = entries.first; it != entries.second; it++)
 		{
 			if (vstd::contains(allowedScopes, it->second.scope))
 			{
-				if (matchesFound == 0) // trigger only once
-					request.callback(it->second.id);
-				matchesFound++;
+				locatedIDs.push_back(it->second);
 			}
 		}
+		return locatedIDs;
+	}
+	return std::vector<ObjectData>();
+}
+
+bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
+{
+	auto identifiers = getIdentifier(request);
+	if (identifiers.size() == 1) // normally resolved ID
+	{
+		request.callback(identifiers.front().id);
+		return true;
+	}
 
-		if (matchesFound == 1)
-			return true; // success, only one matching ID
+	if (request.optional && identifiers.empty()) // failed to resolve optinal ID
+		return true;
 
-		// error found. Try to generate some debug info
-		if (matchesFound == 0)
-			logGlobal->errorStream() << "Unknown identifier!";
-		else
-			logGlobal->errorStream() << "Ambiguous identifier request!";
+	// error found. Try to generate some debug info
+	if (identifiers.size() == 0)
+		logGlobal->errorStream() << "Unknown identifier!";
+	else
+		logGlobal->errorStream() << "Ambiguous identifier request!";
 
-		 logGlobal->errorStream() << "Request for " << request.type << "." << request.name << " from mod " << request.localScope;
+	 logGlobal->errorStream() << "Request for " << request.type << "." << request.name << " from mod " << request.localScope;
 
-		for (auto it = entries.first; it != entries.second; it++)
-		{
-			logGlobal->errorStream() << "\tID is available in mod " << it->second.scope;
-		}
+	for (auto id : identifiers)
+	{
+		logGlobal->errorStream() << "\tID is available in mod " << id.scope;
 	}
-	logGlobal->errorStream() << "Unknown identifier " << request.type << "." << request.name << " from mod " << request.localScope;
 	return false;
 }
 

+ 13 - 4
lib/CModHandler.h

@@ -32,8 +32,9 @@ class CIdentifierStorage
 		std::string type;        /// type, e.g. creature, faction, hero, etc
 		std::string name;        /// string ID
 		std::function<void(si32)> callback;
+		bool optional;
 
-		ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const std::function<void(si32)> & callback);
+		ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const std::function<void(si32)> & callback, bool optional);
 	};
 
 	struct ObjectData // entry created on ID registration
@@ -50,14 +51,22 @@ class CIdentifierStorage
 
 	void requestIdentifier(ObjectCallback callback);
 	bool resolveIdentifier(const ObjectCallback & callback);
+	std::vector<ObjectData> getIdentifier(const ObjectCallback & callback);
 public:
-	/// request identifier for specific object name. If ID is not yet resolved callback will be queued
-	/// and will be called later
+	/// request identifier for specific object name.
+	/// Function callback will be called during ID resolution phase of loading
 	void requestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
 	void requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
 	void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback);
 
-	/// registers new object, calls all associated callbacks
+	/// try to request ID. If ID with such name won't be loaded, callback function will not be called
+	void tryRequestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
+	void tryRequestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
+
+	/// get identifier immediately. If identifier is not know and not silent call will result in error message
+	boost::optional<si32> getIdentifier(std::string type, const JsonNode & name, bool silent = false);
+
+	/// registers new object
 	void registerObject(std::string scope, std::string type, std::string name, si32 identifier);
 
 	/// called at the very end of loading to check for any missing ID's

+ 83 - 45
lib/CTownHandler.cpp

@@ -260,7 +260,31 @@ std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
 	return dest;
 }
 
-void CTownHandler::loadBuilding(CTown &town, const JsonNode & source)
+void CTownHandler::loadBuildingRequirements(CTown &town, CBuilding & building, const JsonNode & source)
+{
+	if (source.isNull())
+		return;
+	if (source.Vector()[0].getType() == JsonNode::DATA_FLOAT)
+	{
+		// MODS COMPATIBILITY
+		CBuilding::TRequired::OperatorAll required;
+
+		for(const JsonNode &building : source.Vector())
+			required.expressions.push_back(BuildingID(building.Float()));
+
+		building.requirements = CBuilding::TRequired(required);
+	}
+	else
+	{
+		BuildingRequirementsHelper hlp;
+		hlp.building = &building;
+		hlp.faction  = town.faction;
+		hlp.json = source;
+		requirementsToLoad.push_back(hlp);
+	}
+}
+
+void CTownHandler::loadBuilding(CTown &town, const std::string & stringID, const JsonNode & source)
 {
 	auto  ret = new CBuilding;
 
@@ -268,65 +292,71 @@ void CTownHandler::loadBuilding(CTown &town, const JsonNode & source)
 
 	ret->mode = static_cast<CBuilding::EBuildMode>(boost::find(modes, source["mode"].String()) - modes);
 
+	ret->identifier = stringID;
 	ret->town = &town;
 	ret->bid = BuildingID(source["id"].Float());
 	ret->name = source["name"].String();
 	ret->description = source["description"].String();
 	ret->resources = TResources(source["cost"]);
 
-	for(const JsonNode &building : source["requires"].Vector())
-		ret->requirements.insert(BuildingID(building.Float()));
+	loadBuildingRequirements(town, *ret, source["requires"]);
 
 	if (!source["upgrades"].isNull())
-	{
-		ret->requirements.insert(BuildingID(source["upgrades"].Float()));
 		ret->upgrade = BuildingID(source["upgrades"].Float());
-	}
 	else
 		ret->upgrade = BuildingID::NONE;
 
 	town.buildings[ret->bid] = ret;
+	VLC->modh->identifiers.registerObject(source.meta, "building." + town.faction->identifier, ret->identifier, ret->bid);
 }
 
 void CTownHandler::loadBuildings(CTown &town, const JsonNode & source)
 {
-	if (source.getType() == JsonNode::DATA_VECTOR)
+	for(auto &node : source.Struct())
 	{
-		for(auto &node : source.Vector())
+		if (!node.second.isNull())
 		{
-			if (!node.isNull())
-				loadBuilding(town, node);
-		}
-	}
-	else
-	{
-		for(auto &node : source.Struct())
-		{
-			if (!node.second.isNull())
-				loadBuilding(town, node.second);
+			loadBuilding(town, node.first, node.second);
 		}
 	}
 }
 
-void CTownHandler::loadStructure(CTown &town, const JsonNode & source)
+void CTownHandler::loadStructure(CTown &town, const std::string & stringID, const JsonNode & source)
 {
-	auto  ret = new CStructure;
+	auto ret = new CStructure;
+
+	//Note: MODS COMPATIBILITY CODE
+	ret->building = nullptr;
+	ret->buildable = nullptr;
+
+	VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->identifier, stringID, [=, &town](si32 identifier) mutable
+	{
+		ret->building = town.buildings[BuildingID(identifier)];
+	});
 
-	if (source["id"].isNull())
+	if (source["builds"].isNull())
 	{
-		ret->building = nullptr;
-		ret->buildable = nullptr;
+		VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->identifier, stringID, [=, &town](si32 identifier) mutable
+		{
+			ret->building = town.buildings[BuildingID(identifier)];
+		});
 	}
 	else
 	{
-		ret->building = town.buildings[BuildingID(source["id"].Float())];
-
-		if (source["builds"].isNull())
-			ret->buildable = ret->building;
-		else
+		if (source["builds"].getType() == JsonNode::DATA_FLOAT)
+		{
 			ret->buildable = town.buildings[BuildingID(source["builds"].Float())];
+		}
+		else
+		{
+			VLC->modh->identifiers.tryRequestIdentifier("building." + town.faction->identifier, source["builds"], [=, &town](si32 identifier) mutable
+			{
+				ret->buildable = town.buildings[BuildingID(identifier)];
+			});
+		}
 	}
 
+	ret->identifier = stringID;
 	ret->pos.x = source["x"].Float();
 	ret->pos.y = source["y"].Float();
 	ret->pos.z = source["z"].Float();
@@ -341,21 +371,10 @@ void CTownHandler::loadStructure(CTown &town, const JsonNode & source)
 
 void CTownHandler::loadStructures(CTown &town, const JsonNode & source)
 {
-	if (source.getType() == JsonNode::DATA_VECTOR)
-	{
-		for(auto &node : source.Vector())
-		{
-			if (!node.isNull())
-				loadStructure(town, node);
-		}
-	}
-	else
+	for(auto &node : source.Struct())
 	{
-		for(auto &node : source.Struct())
-		{
-			if (!node.second.isNull())
-				loadStructure(town, node.second);
-		}
+		if (!node.second.isNull())
+			loadStructure(town, node.first, node.second);
 	}
 }
 
@@ -564,11 +583,12 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
 	assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES);
 }
 
-CFaction * CTownHandler::loadFromJson(const JsonNode &source)
+CFaction * CTownHandler::loadFromJson(const JsonNode &source, std::string identifier)
 {
 	auto  faction = new CFaction();
 
 	faction->name = source["name"].String();
+	faction->identifier = identifier;
 
 	VLC->modh->identifiers.requestIdentifier ("creature", source["commander"],
 		[=](si32 commanderID)
@@ -604,7 +624,7 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source)
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, name);
 	object->index = factions.size();
 	if (object->town)
 	{
@@ -622,7 +642,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, name);
 	object->index = index;
 	if (object->town)
 	{
@@ -639,6 +659,24 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 	VLC->modh->identifiers.registerObject(scope, "faction", name, object->index);
 }
 
+void CTownHandler::afterLoadFinalization()
+{
+	initializeRequirements();
+}
+
+void CTownHandler::initializeRequirements()
+{
+	// must be done separately after all ID's are known
+	for (auto & requirement : requirementsToLoad)
+	{
+		requirement.building->requirements = CBuilding::TRequired(requirement.json, [&](const JsonNode & node)
+		{
+			return BuildingID(VLC->modh->identifiers.getIdentifier("building." + requirement.faction->identifier, node.Vector()[0]).get());
+		});
+	}
+	requirementsToLoad.clear();
+}
+
 std::vector<bool> CTownHandler::getDefaultAllowed() const
 {
 	std::vector<bool> allowedFactions;

+ 41 - 24
lib/CTownHandler.h

@@ -5,6 +5,7 @@
 #include "int3.h"
 #include "GameConstants.h"
 #include "IHandlerBase.h"
+#include "LogicalExpression.h"
 
 /*
  * CTownHandler.h, part of VCMI engine
@@ -31,11 +32,14 @@ class DLL_LINKAGE CBuilding
 	std::string description;
 
 public:
+	typedef LogicalExpression<BuildingID> TRequired;
+
 	CTown * town; // town this building belongs to
-	BuildingID bid; //structure ID
 	TResources resources;
+	TRequired requirements;
+	std::string identifier;
 
-	std::set<BuildingID> requirements; /// set of required buildings, includes upgradeOf;
+	BuildingID bid; //structure ID
 	BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
 
 	enum EBuildMode
@@ -57,7 +61,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & town & bid & resources & name & description & requirements & upgrade & mode;
+		h & identifier & town & bid & resources & name & description & requirements & upgrade & mode;
 	}
 
 	friend class CTownHandler;
@@ -71,14 +75,13 @@ struct DLL_LINKAGE CStructure
 	CBuilding * building;  // base building. If null - this structure will be always present on screen
 	CBuilding * buildable; // building that will be used to determine built building and visible cost. Usually same as "building"
 
-	bool hiddenUpgrade; // used only if "building" is upgrade, if true - structure on town screen will behave exactly like parent (mouse clicks, hover texts, etc)
-
 	int3 pos;
-	std::string defName, borderName, areaName;
+	std::string defName, borderName, areaName, identifier;
 
+	bool hiddenUpgrade; // used only if "building" is upgrade, if true - structure on town screen will behave exactly like parent (mouse clicks, hover texts, etc)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & pos & defName & borderName & areaName & building & buildable & hiddenUpgrade;
+		h & pos & defName & borderName & areaName & identifier & building & buildable & hiddenUpgrade;
 	}
 };
 
@@ -102,6 +105,7 @@ public:
 	~CFaction();
 
 	std::string name; //town name, by default - from TownName.txt
+	std::string identifier;
 
 	TFaction index;
 
@@ -119,7 +123,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & name & index & nativeTerrain & alignment & commander & town & creatureBg120 & creatureBg130 & puzzleMap;
+		h & name & identifier & index & nativeTerrain & alignment & commander & town & creatureBg120 & creatureBg130 & puzzleMap;
 	}
 };
 
@@ -134,19 +138,19 @@ public:
 	std::vector<std::string> names; //names of the town instances
 
 	/// level -> list of creatures on this tier
-	// TODO: replace with pointers to CCreature
-	std::vector<std::vector<CreatureID> > creatures;
-
-	std::map<BuildingID, ConstTransitivePtr<CBuilding> > buildings;
-
-	std::vector<std::string> dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc.
-	std::vector<std::string> dwellingNames;
-
-	// should be removed at least from configs in favor of auto-detection
-	std::map<int,int> hordeLvl; //[0] - first horde building creature level; [1] - second horde building (-1 if not present)
-	ui32 mageLevel; //max available mage guild level
-	ui16 primaryRes;
-	ArtifactID warMachine;
+	// TODO: replace with pointers to CCreature
+	std::vector<std::vector<CreatureID> > creatures;
+
+	std::map<BuildingID, ConstTransitivePtr<CBuilding> > buildings;
+
+	std::vector<std::string> dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc.
+	std::vector<std::string> dwellingNames;
+
+	// should be removed at least from configs in favor of auto-detection
+	std::map<int,int> hordeLvl; //[0] - first horde building creature level; [1] - second horde building (-1 if not present)
+	ui32 mageLevel; //max available mage guild level
+	ui16 primaryRes;
+	ArtifactID warMachine;
 	si32 moatDamage;
 	// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
 	// resulting chance = sqrt(town.chance * heroClass.chance)
@@ -216,12 +220,23 @@ public:
 
 class DLL_LINKAGE CTownHandler : public IHandlerBase
 {
+	struct BuildingRequirementsHelper
+	{
+		JsonNode json;
+		CBuilding * building;
+		CFaction * faction;
+	};
+
+	std::vector<BuildingRequirementsHelper> requirementsToLoad;
+	void initializeRequirements();
+
 	/// loads CBuilding's into town
-	void loadBuilding(CTown &town, const JsonNode & source);
+	void loadBuildingRequirements(CTown &town, CBuilding & building, const JsonNode & source);
+	void loadBuilding(CTown &town, const std::string & stringID, const JsonNode & source);
 	void loadBuildings(CTown &town, const JsonNode & source);
 
 	/// loads CStructure's into town
-	void loadStructure(CTown &town, const JsonNode & source);
+	void loadStructure(CTown &town, const std::string & stringID, const JsonNode & source);
 	void loadStructures(CTown &town, const JsonNode & source);
 
 	/// loads town hall vector (hallSlots)
@@ -234,7 +249,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
 
 	void loadPuzzle(CFaction & faction, const JsonNode & source);
 
-	CFaction * loadFromJson(const JsonNode & data);
+	CFaction * loadFromJson(const JsonNode & data, std::string identifier);
 
 public:
 	std::vector<ConstTransitivePtr<CFaction> > factions;
@@ -247,6 +262,8 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
 
+	void afterLoadFinalization() override;
+
 	std::vector<bool> getDefaultAllowed() const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 1 - 1
lib/GameConstants.h

@@ -394,7 +394,7 @@ namespace EBuildingState
 	enum EBuildingState
 	{
 		HAVE_CAPITAL, NO_WATER, FORBIDDEN, ADD_MAGES_GUILD, ALREADY_PRESENT, CANT_BUILD_TODAY,
-		NO_RESOURCES, ALLOWED, PREREQUIRES, BUILDING_ERROR, TOWN_NOT_OWNED
+		NO_RESOURCES, ALLOWED, PREREQUIRES, MISSING_BASE, BUILDING_ERROR, TOWN_NOT_OWNED
 	};
 }
 

+ 9 - 47
lib/IGameCallback.cpp

@@ -570,7 +570,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
 	if(!t->town->buildings.count(ID))
 		return EBuildingState::BUILDING_ERROR;
 
-	const CBuilding * pom = t->town->buildings.at(ID);
+	const CBuilding * building = t->town->buildings.at(ID);
 
 
 	if(t->hasBuilt(ID))	//already built
@@ -580,27 +580,20 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
 	if(vstd::contains(t->forbiddenBuildings, ID))
 		return EBuildingState::FORBIDDEN; //forbidden
 
-	//checking for requirements
-	std::set<BuildingID> reqs = getBuildingRequiments(t, ID);//getting all requirements
-
-	bool notAllBuilt = false;
-	for(auto & req : reqs)
+	auto buildTest = [&](const BuildingID & id)
 	{
-		if(!t->hasBuilt(req)) //lack of requirements - cannot build
-		{
-			if(vstd::contains(t->forbiddenBuildings, req)) // not built requirement forbidden - same goes to this build
-				return EBuildingState::FORBIDDEN;
-			else
-				notAllBuilt = true; // no return here - we need to check if any required builds are forbidden
-		}
-	}
+		return t->hasBuilt(id);
+	};
 
 	if(t->builded >= VLC->modh->settings.MAX_BUILDING_PER_TURN)
 		return EBuildingState::CANT_BUILD_TODAY; //building limit
 
-	if (notAllBuilt)
+	if (!building->requirements.test(buildTest))
 		return EBuildingState::PREREQUIRES;
 
+	if (building->upgrade != BuildingID::NONE && !t->hasBuilt(building->upgrade))
+		return EBuildingState::MISSING_BASE;
+
 	if(ID == BuildingID::CAPITOL)
 	{
 		const PlayerState *ps = getPlayer(t->tempOwner);
@@ -624,43 +617,12 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
 	}
 
 	//checking resources
-	if(!pom->resources.canBeAfforded(getPlayer(t->tempOwner)->resources))
+	if(!building->resources.canBeAfforded(getPlayer(t->tempOwner)->resources))
 		return EBuildingState::NO_RESOURCES; //lack of res
 
 	return EBuildingState::ALLOWED;
 }
 
-std::set<BuildingID> CGameInfoCallback::getBuildingRequiments( const CGTownInstance *t, BuildingID ID )
-{
-	ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", std::set<BuildingID>());
-	ERROR_RET_VAL_IF(!t->town->buildings.count(ID), "No such building!", std::set<BuildingID>());
-
-	std::set<int> used;
-	used.insert(ID);
-	auto reqs = t->town->buildings.at(ID)->requirements;
-
-	bool found;
-	do
-	{
-		found = false;
-		for(auto i=reqs.begin();i!=reqs.end();i++)
-		{
-			if(used.find(*i)==used.end()) //we haven't added requirements for this building
-			{
-				found = true;
-				auto & requires = t->town->buildings.at(*i)->requirements;
-
-				used.insert(*i);
-				for(auto & require : requires)
-					reqs.insert(require);//creating full list of requirements
-			}
-		}
-	}
-	while (found);
-
-	return reqs;
-}
-
 const CMapHeader * CGameInfoCallback::getMapHeader() const
 {
 	return gs->map;

+ 0 - 2
lib/IGameCallback.h

@@ -130,14 +130,12 @@ public:
 	std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
 	std::string getTavernGossip(const CGObjectInstance * townOrTavern) const; 
 	EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
-	std::set<BuildingID> getBuildingRequiments(const CGTownInstance *t, BuildingID ID);
 	virtual bool getTownInfo(const CGObjectInstance *town, InfoAboutTown &dest) const;
 	const CTown *getNativeTown(PlayerColor color) const;
 
 	//from gs
 	const TeamState *getTeam(TeamID teamID) const;
 	const TeamState *getPlayerTeam(PlayerColor color) const;
-	std::set<BuildingID> getBuildingRequiments(const CGTownInstance *t, BuildingID ID) const;
 	EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID) const;// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
 };
 

+ 12 - 0
lib/LogicalExpression.cpp

@@ -0,0 +1,12 @@
+#include "StdInc.h"
+
+#include "LogicalExpression.h"
+
+#include "VCMI_Lib.h"
+#include "CGeneralTextHandler.h"
+
+std::string LogicalExpressionDetail::getTextForOperator(std::string operation)
+{
+	//placed in cpp mostly to avoid unnecessary includes in header
+	return VLC->generaltexth->localizedTexts["logicalExpressions"][operation].String();
+}

+ 326 - 0
lib/LogicalExpression.h

@@ -0,0 +1,326 @@
+#pragma once
+
+//FIXME: move some of code into .cpp to avoid this include?
+#include "JsonNode.h"
+
+namespace LogicalExpressionDetail
+{
+	/// class that defines required types for logical expressions
+	template<typename ContainedClass>
+	class DLL_LINKAGE ExpressionBase
+	{
+		/// Possible logical operations, mostly needed to create different types for boost::variant
+		enum EOperations
+		{
+			ANY_OF,
+			ALL_OF,
+			NONE_OF
+		};
+	public:
+		template<EOperations tag> class Element;
+
+		typedef Element<ANY_OF> OperatorAny;
+		typedef Element<ALL_OF> OperatorAll;
+		typedef Element<NONE_OF> OperatorNone;
+
+		typedef ContainedClass Value;
+
+		/// Variant that contains all possible elements from logical expression
+		typedef boost::variant<
+			OperatorAll,
+			OperatorAny,
+			OperatorNone,
+			Value
+			> Variant;
+
+		/// Variant element, contains list of expressions to which operation "tag" should be applied
+		template<EOperations tag>
+		class DLL_LINKAGE Element
+		{
+		public:
+			Element() {}
+			Element(std::vector<Variant> expressions):
+				expressions(expressions)
+			{}
+
+			std::vector<Variant> expressions;
+
+			template <typename Handler>
+			void serialize(Handler & h, const int version)
+			{
+				h & expressions;
+			}
+		};
+	};
+
+	/// Visitor to test result (true/false) of the expression
+	template <typename ContainedClass>
+	class DLL_LINKAGE TestVisitor : public boost::static_visitor<bool>
+	{
+		typedef ExpressionBase<ContainedClass> Base;
+
+		std::function<bool(const typename Base::Value &)> classTest;
+
+		size_t countPassed(const std::vector<typename Base::Variant> & element) const
+		{
+			return boost::range::count_if(element, [&](const typename Base::Variant & expr)
+			{
+				return boost::apply_visitor(*this, expr);
+			});
+		}
+	public:
+		TestVisitor(std::function<bool (const typename Base::Value &)> classTest):
+			classTest(classTest)
+		{}
+
+		bool operator()(const typename Base::OperatorAny & element) const
+		{
+			return countPassed(element.expressions) != 0;
+		}
+
+		bool operator()(const typename Base::OperatorAll & element) const
+		{
+			return countPassed(element.expressions) == element.expressions.size();
+		}
+
+		bool operator()(const typename Base::OperatorNone & element) const
+		{
+			return countPassed(element.expressions) == 0;
+		}
+
+		bool operator()(const typename Base::Value & element) const
+		{
+			return classTest(element);
+		}
+	};
+
+	/// visitor that is trying to generates candidates that must be fulfilled
+	/// to complete this expression
+	template <typename ContainedClass>
+	class DLL_LINKAGE CandidatesVisitor : public boost::static_visitor<std::vector<ContainedClass> >
+	{
+		typedef ExpressionBase<ContainedClass> Base;
+		typedef std::vector<typename Base::Value> TValueList;
+
+		TestVisitor<ContainedClass> classTest;
+
+	public:
+		CandidatesVisitor(std::function<bool(const typename Base::Value &)> classTest):
+			classTest(classTest)
+		{}
+
+		TValueList operator()(const typename Base::OperatorAny & element) const
+		{
+			TValueList ret;
+			if (!classTest(element))
+			{
+				for (auto & elem : element.expressions)
+					boost::range::copy(boost::apply_visitor(*this, elem), std::back_inserter(ret));
+			}
+			return ret;
+		}
+
+		TValueList operator()(const typename Base::OperatorAll & element) const
+		{
+			TValueList ret;
+			if (!classTest(element))
+			{
+				for (auto & elem : element.expressions)
+					boost::range::copy(boost::apply_visitor(*this, elem), std::back_inserter(ret));
+			}
+			return ret;
+		}
+
+		TValueList operator()(const typename Base::OperatorNone & element) const
+		{
+			return TValueList(); //TODO. Implementing this one is not straightforward, if ever possible
+		}
+
+		TValueList operator()(const typename Base::Value & element) const
+		{
+			if (classTest(element))
+				return TValueList();
+			else
+				return TValueList(1, element);
+		}
+	};
+
+	/// Json parser for expressions
+	template <typename ContainedClass>
+	class DLL_LINKAGE Reader
+	{
+		typedef ExpressionBase<ContainedClass> Base;
+
+		std::function<typename Base::Value(const JsonNode &)> classParser;
+
+		typename Base::Variant readExpression(const JsonNode & node)
+		{
+			assert(!node.Vector().empty());
+
+			std::string type = node.Vector()[0].String();
+			if (type == "anyOf")
+				return typename Base::OperatorAny(readVector(node));
+			if (type == "allOf")
+				return typename Base::OperatorAll(readVector(node));
+			if (type == "noneOf")
+				return typename Base::OperatorNone(readVector(node));
+			return classParser(node);
+		}
+
+		std::vector<typename Base::Variant> readVector(const JsonNode & node)
+		{
+			std::vector<typename Base::Variant> ret;
+			ret.reserve(node.Vector().size()-1);
+			for (size_t i=1; i < node.Vector().size(); i++)
+				ret.push_back(readExpression(node.Vector()[i]));
+			return ret;
+		}
+	public:
+		Reader(std::function<typename Base::Value(const JsonNode &)> classParser):
+			classParser(classParser)
+		{}
+		typename Base::Variant operator ()(const JsonNode & node)
+		{
+			return readExpression(node);
+		}
+	};
+
+	std::string getTextForOperator(std::string operation);
+
+	/// Prints expression in human-readable format
+	template <typename ContainedClass>
+	class DLL_LINKAGE Printer : public boost::static_visitor<std::string>
+	{
+		typedef ExpressionBase<ContainedClass> Base;
+
+		std::function<std::string(const typename Base::Value &)> classPrinter;
+		std::unique_ptr<TestVisitor<ContainedClass>> statusTest;
+		mutable std::string prefix;
+
+		template<typename Operator>
+		std::string formatString(std::string toFormat, const Operator & expr) const
+		{
+			// highlight not fulfilled expressions, if pretty formatting is on
+			if (statusTest && !(*statusTest)(expr))
+				return "{" + toFormat + "}";
+			return toFormat;
+		}
+
+		std::string printExpressionList(const std::vector<typename Base::Variant> & element) const
+		{
+			std::string ret;
+			prefix.push_back('\t');
+			for (auto & expr : element)
+				ret += prefix + boost::apply_visitor(*this, expr) + "\n";
+			prefix.pop_back();
+			return ret;
+		}
+	public:
+		Printer(std::function<std::string(const typename Base::Value &)> classPrinter):
+			classPrinter(classPrinter)
+		{}
+
+		Printer(std::function<std::string(const typename Base::Value &)> classPrinter, std::function<bool(const typename Base::Value &)> toBool):
+			classPrinter(classPrinter),
+			statusTest(new TestVisitor<ContainedClass>(toBool))
+		{}
+
+		std::string operator()(const typename Base::OperatorAny & element) const
+		{
+			return formatString(getTextForOperator("anyOf"), element) + "\n"
+					+ printExpressionList(element.expressions);
+		}
+
+		std::string operator()(const typename Base::OperatorAll & element) const
+		{
+			return formatString(getTextForOperator("allOf"), element) + "\n"
+					+ printExpressionList(element.expressions);
+		}
+
+		std::string operator()(const typename Base::OperatorNone & element) const
+		{
+			return formatString(getTextForOperator("noneOf"), element) + "\n"
+					+ printExpressionList(element.expressions);
+		}
+
+		std::string operator()(const typename Base::Value & element) const
+		{
+			return formatString(classPrinter(element), element);
+		}
+	};
+}
+
+///
+/// Class for evaluation of logical expressions generated in runtime
+///
+template<typename ContainedClass>
+class DLL_LINKAGE LogicalExpression
+{
+	typedef LogicalExpressionDetail::ExpressionBase<ContainedClass> Base;
+public:
+	/// Type of values used in expressions, same as ContainedClass
+	typedef typename Base::Value Value;
+	/// Operators for use in expressions, all include vectors with operands
+	typedef typename Base::OperatorAny OperatorAny;
+	typedef typename Base::OperatorAll OperatorAll;
+	typedef typename Base::OperatorNone OperatorNone;
+	/// one expression entry
+	typedef typename Base::Variant Variant;
+
+private:
+	Variant data;
+
+public:
+	/// Base constructor
+	LogicalExpression()
+	{}
+
+	/// Constructor from variant or (implicitly) from Operator* types
+	LogicalExpression(const Variant & data):
+		data(data)
+	{
+	}
+
+	/// Constructor that receives JsonNode as input and function that can parse Value instances
+	LogicalExpression(const JsonNode & input, std::function<Value(const JsonNode &)> parser)
+	{
+		LogicalExpressionDetail::Reader<Value> reader(parser);
+		LogicalExpression expr(reader(input));
+		std::swap(data, expr.data);
+	}
+
+	/// calculates if expression evaluates to "true".
+	/// Note: empty expressions always return true
+	bool test(std::function<bool(const Value &)> toBool) const
+	{
+		LogicalExpressionDetail::TestVisitor<Value> testVisitor(toBool);
+		return boost::apply_visitor(testVisitor, data);
+	}
+
+	/// generates list of candidates that can be fulfilled by caller (like AI)
+	std::vector<Value> getFulfillmentCandidates(std::function<bool(const Value &)> toBool) const
+	{
+		LogicalExpressionDetail::CandidatesVisitor<Value> candidateVisitor(toBool);
+		return boost::apply_visitor(candidateVisitor, data);
+	}
+
+	/// Converts expression in human-readable form
+	/// Second version will try to do some pretty printing using H3 text formatting "{}"
+	/// to indicate fulfilled components of an expression
+	std::string toString(std::function<std::string(const Value &)> toStr) const
+	{
+		LogicalExpressionDetail::Printer<Value> printVisitor(toStr);
+		return boost::apply_visitor(printVisitor, data);
+	}
+	std::string toString(std::function<std::string(const Value &)> toStr, std::function<bool(const Value &)> toBool) const
+	{
+		LogicalExpressionDetail::Printer<Value> printVisitor(toStr, toBool);
+		return boost::apply_visitor(printVisitor, data);
+	}
+
+	template <typename Handler>
+	void serialize(Handler & h, const int version)
+	{
+		h & data;
+	}
+};

+ 14 - 14
server/CGameHandler.cpp

@@ -2352,7 +2352,8 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
 	const CBuilding * requestedBuilding = t->town->buildings.at(requestedID);
 
 	//Vector with future list of built building and buildings in auto-mode that are not yet built.
-	std::vector<const CBuilding*> buildingsThatWillBe, remainingAutoBuildings;
+	std::vector<const CBuilding*> remainingAutoBuildings;
+	std::set<BuildingID> buildingsThatWillBe;
 
 	//Check validity of request
 	if(!force)
@@ -2360,13 +2361,13 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
 		switch (requestedBuilding->mode)
 		{
 		case CBuilding::BUILD_NORMAL :
-		case CBuilding::BUILD_AUTO   :
 			if (gs->canBuildStructure(t, requestedID) != EBuildingState::ALLOWED)
 				COMPLAIN_RET("Cannot build that building!");
 			break;
 
+		case CBuilding::BUILD_AUTO   :
 		case CBuilding::BUILD_SPECIAL:
-			COMPLAIN_RET("This building can not be constructed!");
+			COMPLAIN_RET("This building can not be constructed normally!");
 
 		case CBuilding::BUILD_GRAIL  :
 			if(requestedBuilding->mode == CBuilding::BUILD_GRAIL) //needs grail
@@ -2421,22 +2422,21 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
 	};
 
 	//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
-	auto allRequirementsFullfilled = [&buildingsThatWillBe, t](const CBuilding *b)
+	auto areRequirementsFullfilled = [&](const BuildingID & buildID)
 	{
-		for(auto requirementID : b->requirements)
-			if(!vstd::contains(buildingsThatWillBe, t->town->buildings.at(requirementID)))
-				return false;
-
-		return true;
+		return buildingsThatWillBe.count(buildID);
 	};
 
 	//Init the vectors
 	for(auto & build : t->town->buildings)
 	{
 		if(t->hasBuilt(build.first))
-			buildingsThatWillBe.push_back(build.second);
-		else if(build.second->mode == CBuilding::BUILD_AUTO) //not built auto building
-			remainingAutoBuildings.push_back(build.second);
+			buildingsThatWillBe.insert(build.first);
+		else
+		{
+			if(build.second->mode == CBuilding::BUILD_AUTO) //not built auto building
+				remainingAutoBuildings.push_back(build.second);
+		}
 	}
 
 	//Prepare structure (list of building ids will be filled later)
@@ -2453,12 +2453,12 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
 		buildingsToAdd.pop();
 
 		ns.bid.insert(b->bid);
-		buildingsThatWillBe.push_back(b);
+		buildingsThatWillBe.insert(b->bid);
 		remainingAutoBuildings -= b;
 
 		for(auto autoBuilding : remainingAutoBuildings)
 		{
-			if(allRequirementsFullfilled(autoBuilding))
+			if (autoBuilding->requirements.test(areRequirementsFullfilled))
 				buildingsToAdd.push(autoBuilding);
 		}
 	}