1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

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.
This commit is contained in:
Ivan Savenko 2013-12-02 11:58:02 +00:00
parent 1e619c95bc
commit ee1b0459e6
16 changed files with 717 additions and 289 deletions

View File

@ -934,18 +934,13 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
if (t->hasBuilt(building)) //Already built? Shouldn't happen in general if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
return true; return true;
std::set<BuildingID> toBuild = cb->getBuildingRequiments(t, building); const CBuilding * buildPtr = t->town->buildings.at(building);
//erase all already built buildings auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
for (auto buildIter = toBuild.begin(); buildIter != toBuild.end();)
{ {
if (t->hasBuilt(*buildIter)) return t->hasBuilt(buildID);
toBuild.erase(buildIter++); });
else toBuild.push_back(building);
buildIter++;
}
toBuild.insert(building);
for(BuildingID buildID : toBuild) for(BuildingID buildID : toBuild)
{ {
@ -989,6 +984,17 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
} }
continue; 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; return false;
} }

View File

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

View File

@ -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); size_t end = what.find_first_of(delimeters[currDelimeter % 2], begin);
if (begin != end) 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 if (currDelimeter % 2) // Enclosed in {} text - set to yellow
f->renderTextLeft(to, toPrint, Colors::YELLOW, where); 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); label = new CMultiLineLabel(rect, Font, Align, Color);
type |= REDRAW_PARENT; type |= REDRAW_PARENT;
pos.x += rect.x;
pos.y += rect.y;
pos.h = rect.h; pos.h = rect.h;
pos.w = rect.w; pos.w = rect.w;

View File

@ -184,7 +184,7 @@
"dwellingLvl3": { "id" : 32, "requires" : [ 30 ] }, "dwellingLvl3": { "id" : 32, "requires" : [ 30 ] },
"dwellingLvl4": { "id" : 33, "requires" : [ 32 ] }, "dwellingLvl4": { "id" : 33, "requires" : [ 32 ] },
"dwellingLvl5": { "id" : 34, "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 ] }, "dwellingLvl7": { "id" : 36, "requires" : [ 35, 1 ] },
"dwellingUpLvl1": { "id" : 37, "upgrades" : 30 }, "dwellingUpLvl1": { "id" : 37, "upgrades" : 30 },
"dwellingUpLvl2": { "id" : 38, "upgrades" : 31 }, "dwellingUpLvl2": { "id" : 38, "upgrades" : 31 },

View File

@ -44,5 +44,15 @@
"label" : "Select resolution", "label" : "Select resolution",
"help" : "Change in-game screen 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:"
} }
} }

View File

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

View File

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

View File

@ -32,8 +32,9 @@ class CIdentifierStorage
std::string type; /// type, e.g. creature, faction, hero, etc std::string type; /// type, e.g. creature, faction, hero, etc
std::string name; /// string ID std::string name; /// string ID
std::function<void(si32)> callback; 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 struct ObjectData // entry created on ID registration
@ -50,14 +51,22 @@ class CIdentifierStorage
void requestIdentifier(ObjectCallback callback); void requestIdentifier(ObjectCallback callback);
bool resolveIdentifier(const ObjectCallback & callback); bool resolveIdentifier(const ObjectCallback & callback);
std::vector<ObjectData> getIdentifier(const ObjectCallback & callback);
public: public:
/// request identifier for specific object name. If ID is not yet resolved callback will be queued /// request identifier for specific object name.
/// and will be called later /// 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 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(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
void requestIdentifier(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); 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 /// called at the very end of loading to check for any missing ID's

View File

@ -260,7 +260,31 @@ std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
return dest; 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; 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->mode = static_cast<CBuilding::EBuildMode>(boost::find(modes, source["mode"].String()) - modes);
ret->identifier = stringID;
ret->town = &town; ret->town = &town;
ret->bid = BuildingID(source["id"].Float()); ret->bid = BuildingID(source["id"].Float());
ret->name = source["name"].String(); ret->name = source["name"].String();
ret->description = source["description"].String(); ret->description = source["description"].String();
ret->resources = TResources(source["cost"]); ret->resources = TResources(source["cost"]);
for(const JsonNode &building : source["requires"].Vector()) loadBuildingRequirements(town, *ret, source["requires"]);
ret->requirements.insert(BuildingID(building.Float()));
if (!source["upgrades"].isNull()) if (!source["upgrades"].isNull())
{
ret->requirements.insert(BuildingID(source["upgrades"].Float()));
ret->upgrade = BuildingID(source["upgrades"].Float()); ret->upgrade = BuildingID(source["upgrades"].Float());
}
else else
ret->upgrade = BuildingID::NONE; ret->upgrade = BuildingID::NONE;
town.buildings[ret->bid] = ret; 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) 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.first, node.second);
loadBuilding(town, node);
}
}
else
{
for(auto &node : source.Struct())
{
if (!node.second.isNull())
loadBuilding(town, 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->building = town.buildings[BuildingID(identifier)];
ret->buildable = nullptr; });
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 else
{ {
ret->building = town.buildings[BuildingID(source["id"].Float())]; if (source["builds"].getType() == JsonNode::DATA_FLOAT)
{
if (source["builds"].isNull())
ret->buildable = ret->building;
else
ret->buildable = town.buildings[BuildingID(source["builds"].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.x = source["x"].Float();
ret->pos.y = source["y"].Float(); ret->pos.y = source["y"].Float();
ret->pos.z = source["z"].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) 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.second.isNull())
{ loadStructure(town, node.first, node.second);
if (!node.isNull())
loadStructure(town, node);
}
}
else
{
for(auto &node : source.Struct())
{
if (!node.second.isNull())
loadStructure(town, node.second);
}
} }
} }
@ -564,11 +583,12 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES); 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(); auto faction = new CFaction();
faction->name = source["name"].String(); faction->name = source["name"].String();
faction->identifier = identifier;
VLC->modh->identifiers.requestIdentifier ("creature", source["commander"], VLC->modh->identifiers.requestIdentifier ("creature", source["commander"],
[=](si32 commanderID) [=](si32 commanderID)
@ -604,7 +624,7 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source)
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data) 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(); object->index = factions.size();
if (object->town) 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) 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; object->index = index;
if (object->town) 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); 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> CTownHandler::getDefaultAllowed() const
{ {
std::vector<bool> allowedFactions; std::vector<bool> allowedFactions;

View File

@ -5,6 +5,7 @@
#include "int3.h" #include "int3.h"
#include "GameConstants.h" #include "GameConstants.h"
#include "IHandlerBase.h" #include "IHandlerBase.h"
#include "LogicalExpression.h"
/* /*
* CTownHandler.h, part of VCMI engine * CTownHandler.h, part of VCMI engine
@ -31,11 +32,14 @@ class DLL_LINKAGE CBuilding
std::string description; std::string description;
public: public:
CTown * town; // town this building belongs to typedef LogicalExpression<BuildingID> TRequired;
BuildingID bid; //structure ID
TResources resources;
std::set<BuildingID> 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 BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
enum EBuildMode enum EBuildMode
@ -57,7 +61,7 @@ public:
template <typename Handler> void serialize(Handler &h, const int version) 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; 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 * 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" 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; 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) 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(); ~CFaction();
std::string name; //town name, by default - from TownName.txt std::string name; //town name, by default - from TownName.txt
std::string identifier;
TFaction index; TFaction index;
@ -119,7 +123,7 @@ public:
template <typename Handler> void serialize(Handler &h, const int version) 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 std::vector<std::string> names; //names of the town instances
/// level -> list of creatures on this tier /// level -> list of creatures on this tier
// TODO: replace with pointers to CCreature // TODO: replace with pointers to CCreature
std::vector<std::vector<CreatureID> > creatures; std::vector<std::vector<CreatureID> > creatures;
std::map<BuildingID, ConstTransitivePtr<CBuilding> > buildings; 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> dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc.
std::vector<std::string> dwellingNames; std::vector<std::string> dwellingNames;
// should be removed at least from configs in favor of auto-detection // 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) 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 ui32 mageLevel; //max available mage guild level
ui16 primaryRes; ui16 primaryRes;
ArtifactID warMachine; ArtifactID warMachine;
si32 moatDamage; si32 moatDamage;
// default chance for hero of specific class to appear in tavern, if field "tavern" was not set // default chance for hero of specific class to appear in tavern, if field "tavern" was not set
// resulting chance = sqrt(town.chance * heroClass.chance) // resulting chance = sqrt(town.chance * heroClass.chance)
@ -216,12 +220,23 @@ public:
class DLL_LINKAGE CTownHandler : public IHandlerBase 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 /// 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); void loadBuildings(CTown &town, const JsonNode & source);
/// loads CStructure's into town /// 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); void loadStructures(CTown &town, const JsonNode & source);
/// loads town hall vector (hallSlots) /// loads town hall vector (hallSlots)
@ -234,7 +249,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
void loadPuzzle(CFaction & faction, const JsonNode & source); void loadPuzzle(CFaction & faction, const JsonNode & source);
CFaction * loadFromJson(const JsonNode & data); CFaction * loadFromJson(const JsonNode & data, std::string identifier);
public: public:
std::vector<ConstTransitivePtr<CFaction> > factions; 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) override;
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) 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; std::vector<bool> getDefaultAllowed() const override;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)

View File

@ -394,7 +394,7 @@ namespace EBuildingState
enum EBuildingState enum EBuildingState
{ {
HAVE_CAPITAL, NO_WATER, FORBIDDEN, ADD_MAGES_GUILD, ALREADY_PRESENT, CANT_BUILD_TODAY, 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
}; };
} }

View File

@ -570,7 +570,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
if(!t->town->buildings.count(ID)) if(!t->town->buildings.count(ID))
return EBuildingState::BUILDING_ERROR; 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 if(t->hasBuilt(ID)) //already built
@ -580,27 +580,20 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
if(vstd::contains(t->forbiddenBuildings, ID)) if(vstd::contains(t->forbiddenBuildings, ID))
return EBuildingState::FORBIDDEN; //forbidden return EBuildingState::FORBIDDEN; //forbidden
//checking for requirements auto buildTest = [&](const BuildingID & id)
std::set<BuildingID> reqs = getBuildingRequiments(t, ID);//getting all requirements
bool notAllBuilt = false;
for(auto & req : reqs)
{ {
if(!t->hasBuilt(req)) //lack of requirements - cannot build return t->hasBuilt(id);
{ };
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
}
}
if(t->builded >= VLC->modh->settings.MAX_BUILDING_PER_TURN) if(t->builded >= VLC->modh->settings.MAX_BUILDING_PER_TURN)
return EBuildingState::CANT_BUILD_TODAY; //building limit return EBuildingState::CANT_BUILD_TODAY; //building limit
if (notAllBuilt) if (!building->requirements.test(buildTest))
return EBuildingState::PREREQUIRES; return EBuildingState::PREREQUIRES;
if (building->upgrade != BuildingID::NONE && !t->hasBuilt(building->upgrade))
return EBuildingState::MISSING_BASE;
if(ID == BuildingID::CAPITOL) if(ID == BuildingID::CAPITOL)
{ {
const PlayerState *ps = getPlayer(t->tempOwner); const PlayerState *ps = getPlayer(t->tempOwner);
@ -624,43 +617,12 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
} }
//checking resources //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::NO_RESOURCES; //lack of res
return EBuildingState::ALLOWED; 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 const CMapHeader * CGameInfoCallback::getMapHeader() const
{ {
return gs->map; return gs->map;

View File

@ -130,14 +130,12 @@ public:
std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
std::string getTavernGossip(const CGObjectInstance * townOrTavern) const; 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 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; virtual bool getTownInfo(const CGObjectInstance *town, InfoAboutTown &dest) const;
const CTown *getNativeTown(PlayerColor color) const; const CTown *getNativeTown(PlayerColor color) const;
//from gs //from gs
const TeamState *getTeam(TeamID teamID) const; const TeamState *getTeam(TeamID teamID) const;
const TeamState *getPlayerTeam(PlayerColor color) 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 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
lib/LogicalExpression.cpp Normal file
View File

@ -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
lib/LogicalExpression.h Normal file
View File

@ -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;
}
};

View File

@ -2352,7 +2352,8 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
const CBuilding * requestedBuilding = t->town->buildings.at(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. //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 //Check validity of request
if(!force) if(!force)
@ -2360,13 +2361,13 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
switch (requestedBuilding->mode) switch (requestedBuilding->mode)
{ {
case CBuilding::BUILD_NORMAL : case CBuilding::BUILD_NORMAL :
case CBuilding::BUILD_AUTO :
if (gs->canBuildStructure(t, requestedID) != EBuildingState::ALLOWED) if (gs->canBuildStructure(t, requestedID) != EBuildingState::ALLOWED)
COMPLAIN_RET("Cannot build that building!"); COMPLAIN_RET("Cannot build that building!");
break; break;
case CBuilding::BUILD_AUTO :
case CBuilding::BUILD_SPECIAL: 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 : case CBuilding::BUILD_GRAIL :
if(requestedBuilding->mode == CBuilding::BUILD_GRAIL) //needs 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" //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) return buildingsThatWillBe.count(buildID);
if(!vstd::contains(buildingsThatWillBe, t->town->buildings.at(requirementID)))
return false;
return true;
}; };
//Init the vectors //Init the vectors
for(auto & build : t->town->buildings) for(auto & build : t->town->buildings)
{ {
if(t->hasBuilt(build.first)) if(t->hasBuilt(build.first))
buildingsThatWillBe.push_back(build.second); buildingsThatWillBe.insert(build.first);
else if(build.second->mode == CBuilding::BUILD_AUTO) //not built auto building else
remainingAutoBuildings.push_back(build.second); {
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) //Prepare structure (list of building ids will be filled later)
@ -2453,12 +2453,12 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
buildingsToAdd.pop(); buildingsToAdd.pop();
ns.bid.insert(b->bid); ns.bid.insert(b->bid);
buildingsThatWillBe.push_back(b); buildingsThatWillBe.insert(b->bid);
remainingAutoBuildings -= b; remainingAutoBuildings -= b;
for(auto autoBuilding : remainingAutoBuildings) for(auto autoBuilding : remainingAutoBuildings)
{ {
if(allRequirementsFullfilled(autoBuilding)) if (autoBuilding->requirements.test(areRequirementsFullfilled))
buildingsToAdd.push(autoBuilding); buildingsToAdd.push(autoBuilding);
} }
} }