From ee1b0459e68d4795a047c7204155233fccbfb0d4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 2 Dec 2013 11:58:02 +0000 Subject: [PATCH] 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. --- AI/VCAI/VCAI.cpp | 26 ++- client/CCastleInterface.cpp | 232 +++++++++++----------- client/gui/CIntObjectClasses.cpp | 4 +- config/factions/rampart.json | 2 +- config/translate.json | 10 + lib/CMakeLists.txt | 1 + lib/CModHandler.cpp | 91 ++++++--- lib/CModHandler.h | 17 +- lib/CTownHandler.cpp | 128 +++++++----- lib/CTownHandler.h | 69 ++++--- lib/GameConstants.h | 2 +- lib/IGameCallback.cpp | 56 +----- lib/IGameCallback.h | 2 - lib/LogicalExpression.cpp | 12 ++ lib/LogicalExpression.h | 326 +++++++++++++++++++++++++++++++ server/CGameHandler.cpp | 28 +-- 16 files changed, 717 insertions(+), 289 deletions(-) create mode 100644 lib/LogicalExpression.cpp create mode 100644 lib/LogicalExpression.h diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 44c8a5372..7700be90d 100644 --- a/AI/VCAI/VCAI.cpp +++ b/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 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; } diff --git a/client/CCastleInterface.cpp b/client/CCastleInterface.cpp index 942cbde4c..63008a7f7 100644 --- a/client/CCastleInterface.cpp +++ b/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(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(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; icost[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; coltown->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: { - ret = CGI->generaltexth->allTexts[52]; - std::set reqs= LOCPLINT->cb->getBuildingRequiments(town, building->bid); + auto toStr = [&](const BuildingID build) -> std::string + { + return town->town->buildings.at(build)->Name(); + }; + /*auto toBool = [&](const BuildingID build) + { + return town->hasBuilt(build); + };*/ - 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 = CGI->generaltexth->allTexts[52]; + 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(available); availableCount = new CLabel(78, 119, FONT_SMALL, CENTER, Colors::WHITE, availableText); diff --git a/client/gui/CIntObjectClasses.cpp b/client/gui/CIntObjectClasses.cpp index e6f26c52c..90fb22f19 100644 --- a/client/gui/CIntObjectClasses.cpp +++ b/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; diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 84fc05859..9cfbad9e7 100644 --- a/config/factions/rampart.json +++ b/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 }, diff --git a/config/translate.json b/config/translate.json index 13d3c73a7..ac8066686 100644 --- a/config/translate.json +++ b/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:" } } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 65afa57d8..7d7111e8e 100644 --- a/lib/CMakeLists.txt +++ b/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 diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index e0ffe6b99..d9d27251c 100644 --- a/lib/CModHandler.cpp +++ b/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 & callback): + std::string name, const std::function & callback, bool optional): localScope(localScope), remoteScope(remoteScope), type(type), name(name), - callback(callback) + callback(callback), + optional(optional) {} static std::pair 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 & 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 & callback) @@ -99,7 +100,34 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun auto pair = splitString(name.String(), ':'); // remoteScope: 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 & 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 & callback) +{ + auto pair = splitString(name.String(), ':'); // remoteScope:name + + requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, true)); +} + +boost::optional 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(), 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(); } 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::getIdentifier(const ObjectCallback & request) { std::set 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 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); } } - - if (matchesFound == 1) - return true; // success, only one matching ID - - // error found. Try to generate some debug info - if (matchesFound == 0) - logGlobal->errorStream() << "Unknown identifier!"; - else - logGlobal->errorStream() << "Ambiguous identifier request!"; - - 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; - } + return locatedIDs; + } + return std::vector(); +} + +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 (request.optional && identifiers.empty()) // failed to resolve optinal ID + return true; + + // 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; + + 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; } diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 93f388f26..6fd2395a7 100644 --- a/lib/CModHandler.h +++ b/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 callback; + bool optional; - ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const std::function & callback); + ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const std::function & 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 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 & callback); void requestIdentifier(std::string type, const JsonNode & name, const std::function & callback); void requestIdentifier(const JsonNode & name, const std::function & 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 & callback); + void tryRequestIdentifier(std::string type, const JsonNode & name, const std::function & callback); + + /// get identifier immediately. If identifier is not know and not silent call will result in error message + boost::optional 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 diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 20f0648b9..a5a055118 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -260,7 +260,31 @@ std::vector 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(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; - if (source["id"].isNull()) + //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 = nullptr; - ret->buildable = nullptr; + ret->building = town.buildings[BuildingID(identifier)]; + }); + + if (source["builds"].isNull()) + { + 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.Struct()) { - for(auto &node : source.Vector()) - { - if (!node.isNull()) - loadStructure(town, node); - } - } - else - { - 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 CTownHandler::getDefaultAllowed() const { std::vector allowedFactions; diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 0f114e44f..3829039d4 100644 --- a/lib/CTownHandler.h +++ b/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: - CTown * town; // town this building belongs to - BuildingID bid; //structure ID - TResources resources; + typedef LogicalExpression TRequired; - std::set requirements; /// set of required buildings, includes upgradeOf; + CTown * town; // town this building belongs to + TResources resources; + TRequired requirements; + std::string identifier; + + 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 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 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 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 names; //names of the town instances /// level -> list of creatures on this tier - // TODO: replace with pointers to CCreature - std::vector > creatures; - - std::map > buildings; - - std::vector dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc. - std::vector dwellingNames; - - // should be removed at least from configs in favor of auto-detection - std::map 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 > creatures; + + std::map > buildings; + + std::vector dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc. + std::vector dwellingNames; + + // should be removed at least from configs in favor of auto-detection + std::map 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 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 > 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 getDefaultAllowed() const override; template void serialize(Handler &h, const int version) diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 36ab451db..3cb479eb9 100644 --- a/lib/GameConstants.h +++ b/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 }; } diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 81d50848a..6ad0dc501 100644 --- a/lib/IGameCallback.cpp +++ b/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 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 CGameInfoCallback::getBuildingRequiments( const CGTownInstance *t, BuildingID ID ) -{ - ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", std::set()); - ERROR_RET_VAL_IF(!t->town->buildings.count(ID), "No such building!", std::set()); - - std::set 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; diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index e4188bcb6..8a16a0bc7 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -130,14 +130,12 @@ public: std::vector 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 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 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 }; diff --git a/lib/LogicalExpression.cpp b/lib/LogicalExpression.cpp new file mode 100644 index 000000000..c6a59b85a --- /dev/null +++ b/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(); +} diff --git a/lib/LogicalExpression.h b/lib/LogicalExpression.h new file mode 100644 index 000000000..0d4024435 --- /dev/null +++ b/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 + 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 class Element; + + typedef Element OperatorAny; + typedef Element OperatorAll; + typedef Element 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 + class DLL_LINKAGE Element + { + public: + Element() {} + Element(std::vector expressions): + expressions(expressions) + {} + + std::vector expressions; + + template + void serialize(Handler & h, const int version) + { + h & expressions; + } + }; + }; + + /// Visitor to test result (true/false) of the expression + template + class DLL_LINKAGE TestVisitor : public boost::static_visitor + { + typedef ExpressionBase Base; + + std::function classTest; + + size_t countPassed(const std::vector & element) const + { + return boost::range::count_if(element, [&](const typename Base::Variant & expr) + { + return boost::apply_visitor(*this, expr); + }); + } + public: + TestVisitor(std::function 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 + class DLL_LINKAGE CandidatesVisitor : public boost::static_visitor > + { + typedef ExpressionBase Base; + typedef std::vector TValueList; + + TestVisitor classTest; + + public: + CandidatesVisitor(std::function 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 + class DLL_LINKAGE Reader + { + typedef ExpressionBase Base; + + std::function 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 readVector(const JsonNode & node) + { + std::vector 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 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 + class DLL_LINKAGE Printer : public boost::static_visitor + { + typedef ExpressionBase Base; + + std::function classPrinter; + std::unique_ptr> statusTest; + mutable std::string prefix; + + template + 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 & 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 classPrinter): + classPrinter(classPrinter) + {} + + Printer(std::function classPrinter, std::function toBool): + classPrinter(classPrinter), + statusTest(new TestVisitor(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 +class DLL_LINKAGE LogicalExpression +{ + typedef LogicalExpressionDetail::ExpressionBase 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 parser) + { + LogicalExpressionDetail::Reader 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 toBool) const + { + LogicalExpressionDetail::TestVisitor testVisitor(toBool); + return boost::apply_visitor(testVisitor, data); + } + + /// generates list of candidates that can be fulfilled by caller (like AI) + std::vector getFulfillmentCandidates(std::function toBool) const + { + LogicalExpressionDetail::CandidatesVisitor 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 toStr) const + { + LogicalExpressionDetail::Printer printVisitor(toStr); + return boost::apply_visitor(printVisitor, data); + } + std::string toString(std::function toStr, std::function toBool) const + { + LogicalExpressionDetail::Printer printVisitor(toStr, toBool); + return boost::apply_visitor(printVisitor, data); + } + + template + void serialize(Handler & h, const int version) + { + h & data; + } +}; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a82efd39d..b45b2aef0 100644 --- a/server/CGameHandler.cpp +++ b/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 buildingsThatWillBe, remainingAutoBuildings; + std::vector remainingAutoBuildings; + std::set 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); } }