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:
parent
1e619c95bc
commit
ee1b0459e6
@ -934,18 +934,13 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
|
||||
if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
|
||||
return true;
|
||||
|
||||
std::set<BuildingID> toBuild = cb->getBuildingRequiments(t, building);
|
||||
const CBuilding * buildPtr = t->town->buildings.at(building);
|
||||
|
||||
//erase all already built buildings
|
||||
for (auto buildIter = toBuild.begin(); buildIter != toBuild.end();)
|
||||
auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
|
||||
{
|
||||
if (t->hasBuilt(*buildIter))
|
||||
toBuild.erase(buildIter++);
|
||||
else
|
||||
buildIter++;
|
||||
}
|
||||
|
||||
toBuild.insert(building);
|
||||
return t->hasBuilt(buildID);
|
||||
});
|
||||
toBuild.push_back(building);
|
||||
|
||||
for(BuildingID buildID : toBuild)
|
||||
{
|
||||
@ -989,6 +984,17 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (canBuild == EBuildingState::PREREQUIRES)
|
||||
{
|
||||
// can happen when dependencies have their own missing dependencies
|
||||
if (tryBuildStructure(t, buildID, maxDays - 1))
|
||||
return true;
|
||||
}
|
||||
else if (canBuild == EBuildingState::MISSING_BASE)
|
||||
{
|
||||
if (tryBuildStructure(t, b->upgrade, maxDays - 1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -40,13 +40,13 @@ using namespace boost::assign;
|
||||
const CBuilding * CBuildingRect::getBuilding()
|
||||
{
|
||||
if (!str->building)
|
||||
return nullptr;
|
||||
|
||||
if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes)
|
||||
return town->town->buildings.at(str->building->getBase());
|
||||
|
||||
return str->building;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes)
|
||||
return town->town->buildings.at(str->building->getBase());
|
||||
|
||||
return str->building;
|
||||
}
|
||||
|
||||
CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance *Town, const CStructure *Str)
|
||||
:CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE | CShowableAnim::USE_RLE),
|
||||
@ -114,13 +114,13 @@ void CBuildingRect::clickRight(tribool down, bool previousState)
|
||||
{
|
||||
if((!area) || (!((bool)down)) || (this!=parent->selectedBuilding) || getBuilding() == nullptr)
|
||||
return;
|
||||
if( !CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) //inside building image
|
||||
{
|
||||
BuildingID bid = getBuilding()->bid;
|
||||
const CBuilding *bld = town->town->buildings.at(bid);
|
||||
if (bid < BuildingID::DWELL_FIRST)
|
||||
{
|
||||
CRClickPopup::createAndPush(CInfoWindow::genText(bld->Name(), bld->Description()),
|
||||
if( !CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) //inside building image
|
||||
{
|
||||
BuildingID bid = getBuilding()->bid;
|
||||
const CBuilding *bld = town->town->buildings.at(bid);
|
||||
if (bid < BuildingID::DWELL_FIRST)
|
||||
{
|
||||
CRClickPopup::createAndPush(CInfoWindow::genText(bld->Name(), bld->Description()),
|
||||
new CComponent(CComponent::building, bld->town->faction->index, bld->bid));
|
||||
}
|
||||
else
|
||||
@ -209,20 +209,20 @@ std::string CBuildingRect::getSubtitle()//hover text for building
|
||||
if (!getBuilding())
|
||||
return "";
|
||||
|
||||
int bid = getBuilding()->bid;
|
||||
|
||||
if (bid<30)//non-dwellings - only buiding name
|
||||
return town->town->buildings.at(getBuilding()->bid)->Name();
|
||||
else//dwellings - recruit %creature%
|
||||
{
|
||||
auto & availableCreatures = town->creatures[(bid-30)%GameConstants::CREATURES_PER_TOWN].second;
|
||||
if(availableCreatures.size())
|
||||
{
|
||||
int creaID = availableCreatures.back();//taking last of available creatures
|
||||
return CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures.at(creaID)->namePl;
|
||||
}
|
||||
else
|
||||
{
|
||||
int bid = getBuilding()->bid;
|
||||
|
||||
if (bid<30)//non-dwellings - only buiding name
|
||||
return town->town->buildings.at(getBuilding()->bid)->Name();
|
||||
else//dwellings - recruit %creature%
|
||||
{
|
||||
auto & availableCreatures = town->creatures[(bid-30)%GameConstants::CREATURES_PER_TOWN].second;
|
||||
if(availableCreatures.size())
|
||||
{
|
||||
int creaID = availableCreatures.back();//taking last of available creatures
|
||||
return CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures.at(creaID)->namePl;
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->warnStream() << "Problem: dwelling with id " << bid << " offers no creatures!";
|
||||
return "#ERROR#";
|
||||
}
|
||||
@ -255,18 +255,18 @@ void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
|
||||
|
||||
CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstance *Town, int level):
|
||||
CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "CRTOINFO", Point(centerX, centerY))
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
||||
|
||||
const CCreature * creature = CGI->creh->creatures.at(Town->creatures.at(level).second.back());
|
||||
|
||||
title = new CLabel(80, 30, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
|
||||
animation = new CCreaturePic(30, 44, creature, true, true);
|
||||
|
||||
std::string text = boost::lexical_cast<std::string>(Town->creatures.at(level).first);
|
||||
available = new CLabel(80,190, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[217] + text);
|
||||
costPerTroop = new CLabel(80, 227, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[346]);
|
||||
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
||||
|
||||
const CCreature * creature = CGI->creh->creatures.at(Town->creatures.at(level).second.back());
|
||||
|
||||
title = new CLabel(80, 30, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
|
||||
animation = new CCreaturePic(30, 44, creature, true, true);
|
||||
|
||||
std::string text = boost::lexical_cast<std::string>(Town->creatures.at(level).first);
|
||||
available = new CLabel(80,190, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[217] + text);
|
||||
costPerTroop = new CLabel(80, 227, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[346]);
|
||||
|
||||
for(int i = 0; i<GameConstants::RESOURCE_QUANTITY; i++)
|
||||
{
|
||||
if(creature->cost[i])
|
||||
@ -503,13 +503,13 @@ void CCastleBuildings::recreate()
|
||||
groups[structure->building->getBase()].push_back(structure);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & entry : groups)
|
||||
{
|
||||
const CBuilding * build = town->town->buildings.at(entry.first);
|
||||
|
||||
const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
|
||||
{
|
||||
|
||||
for(auto & entry : groups)
|
||||
{
|
||||
const CBuilding * build = town->town->buildings.at(entry.first);
|
||||
|
||||
const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
|
||||
{
|
||||
return build->getDistance(a->building->bid)
|
||||
< build->getDistance(b->building->bid);
|
||||
});
|
||||
@ -526,17 +526,17 @@ CCastleBuildings::~CCastleBuildings()
|
||||
{
|
||||
}
|
||||
|
||||
void CCastleBuildings::addBuilding(BuildingID building)
|
||||
{
|
||||
//FIXME: implement faster method without complete recreation of town
|
||||
BuildingID base = town->town->buildings.at(building)->getBase();
|
||||
|
||||
recreate();
|
||||
|
||||
auto & structures = groups.at(base);
|
||||
|
||||
for(CBuildingRect * rect : buildings)
|
||||
{
|
||||
void CCastleBuildings::addBuilding(BuildingID building)
|
||||
{
|
||||
//FIXME: implement faster method without complete recreation of town
|
||||
BuildingID base = town->town->buildings.at(building)->getBase();
|
||||
|
||||
recreate();
|
||||
|
||||
auto & structures = groups.at(base);
|
||||
|
||||
for(CBuildingRect * rect : buildings)
|
||||
{
|
||||
if (vstd::contains(structures, rect->str))
|
||||
{
|
||||
//reset animation
|
||||
@ -1111,13 +1111,13 @@ CTownInfo::CTownInfo(int posX, int posY, const CGTownInstance* Town, bool townHa
|
||||
{
|
||||
buildID = 6 + town->fortLevel();
|
||||
if (buildID == 6)
|
||||
return;
|
||||
picture = new CAnimImage("ITMCL.DEF", town->fortLevel()-1);
|
||||
}
|
||||
building = town->town->buildings.at(BuildingID(buildID));
|
||||
pos = picture->pos;
|
||||
}
|
||||
|
||||
return;
|
||||
picture = new CAnimImage("ITMCL.DEF", town->fortLevel()-1);
|
||||
}
|
||||
building = town->town->buildings.at(BuildingID(buildID));
|
||||
pos = picture->pos;
|
||||
}
|
||||
|
||||
void CTownInfo::hover(bool on)
|
||||
{
|
||||
if(on)
|
||||
@ -1238,7 +1238,7 @@ void CHallInterface::CBuildingBox::hover(bool on)
|
||||
if(on)
|
||||
{
|
||||
std::string toPrint;
|
||||
if(state==EBuildingState::PREREQUIRES)
|
||||
if(state==EBuildingState::PREREQUIRES || state == EBuildingState::MISSING_BASE)
|
||||
toPrint = CGI->generaltexth->hcommands[5];
|
||||
else if(state==EBuildingState::CANT_BUILD_TODAY)
|
||||
toPrint = CGI->generaltexth->allTexts[223];
|
||||
@ -1276,8 +1276,8 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance *
|
||||
|
||||
state = LOCPLINT->cb->canBuildStructure(town,building->bid);
|
||||
assert(state < EBuildingState::BUILDING_ERROR);
|
||||
static int panelIndex[9] = { 3, 3, 3, 0, 0, 2, 2, 1, 2};
|
||||
static int iconIndex[9] = {-1, -1, -1, 0, 0, 1, 2, -1, 1};
|
||||
static int panelIndex[10] = { 3, 3, 3, 0, 0, 2, 2, 1, 2, 2};
|
||||
static int iconIndex[10] = {-1, -1, -1, 0, 0, 1, 2, -1, 1, 1};
|
||||
|
||||
picture = new CAnimImage(town->town->clientInfo.buildingsIcons, building->bid, 0, 2, 2);
|
||||
panel = new CAnimImage("TPTHBAR", panelIndex[state], 0, 1, 73);
|
||||
@ -1295,13 +1295,13 @@ CHallInterface::CHallInterface(const CGTownInstance *Town):
|
||||
resdatabar = new CMinorResDataBar;
|
||||
resdatabar->pos.x += pos.x;
|
||||
resdatabar->pos.y += pos.y;
|
||||
Rect barRect(5, 556, 740, 18);
|
||||
statusBar = new CGStatusBar(new CPicture(*background, barRect, 5, 556, false));
|
||||
|
||||
title = new CLabel(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
|
||||
exit = new CAdventureMapButton(CGI->generaltexth->hcommands[8], "",
|
||||
boost::bind(&CHallInterface::close,this), 748, 556, "TPMAGE1.DEF", SDLK_RETURN);
|
||||
exit->assignedKeys.insert(SDLK_ESCAPE);
|
||||
Rect barRect(5, 556, 740, 18);
|
||||
statusBar = new CGStatusBar(new CPicture(*background, barRect, 5, 556, false));
|
||||
|
||||
title = new CLabel(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
|
||||
exit = new CAdventureMapButton(CGI->generaltexth->hcommands[8], "",
|
||||
boost::bind(&CHallInterface::close,this), 748, 556, "TPMAGE1.DEF", SDLK_RETURN);
|
||||
exit->assignedKeys.insert(SDLK_ESCAPE);
|
||||
|
||||
auto & boxList = town->town->clientInfo.hallSlots;
|
||||
boxes.resize(boxList.size());
|
||||
@ -1310,13 +1310,13 @@ CHallInterface::CHallInterface(const CGTownInstance *Town):
|
||||
for(size_t col=0; col<boxList[row].size(); col++) //for each box
|
||||
{
|
||||
const CBuilding *building = nullptr;
|
||||
for(auto & elem : boxList[row][col])//we are looking for the first not build structure
|
||||
{
|
||||
auto buildingID = elem;
|
||||
building = town->town->buildings.at(buildingID);
|
||||
|
||||
if(!vstd::contains(town->builtBuildings,buildingID))
|
||||
break;
|
||||
for(auto & elem : boxList[row][col])//we are looking for the first not build structure
|
||||
{
|
||||
auto buildingID = elem;
|
||||
building = town->town->buildings.at(buildingID);
|
||||
|
||||
if(!vstd::contains(town->builtBuildings,buildingID))
|
||||
break;
|
||||
}
|
||||
int posX = pos.w/2 - boxList[row].size()*154/2 - (boxList[row].size()-1)*20 + 194*col,
|
||||
posY = 35 + 104*row;
|
||||
@ -1336,28 +1336,38 @@ void CBuildWindow::buyFunc()
|
||||
std::string CBuildWindow::getTextForState(int state)
|
||||
{
|
||||
std::string ret;
|
||||
if(state<7)
|
||||
if(state < EBuildingState::ALLOWED)
|
||||
ret = CGI->generaltexth->hcommands[state];
|
||||
switch (state)
|
||||
{
|
||||
case 4: case 5: case 6:
|
||||
ret.replace(ret.find_first_of("%s"),2,building->Name());
|
||||
case EBuildingState::ALREADY_PRESENT:
|
||||
case EBuildingState::CANT_BUILD_TODAY:
|
||||
case EBuildingState::NO_RESOURCES:
|
||||
ret.replace(ret.find_first_of("%s"), 2, building->Name());
|
||||
break;
|
||||
case 7:
|
||||
case EBuildingState::ALLOWED:
|
||||
return CGI->generaltexth->allTexts[219]; //all prereq. are met
|
||||
case 8:
|
||||
case EBuildingState::PREREQUIRES:
|
||||
{
|
||||
ret = CGI->generaltexth->allTexts[52];
|
||||
std::set<BuildingID> 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<std::string>(available);
|
||||
availableCount = new CLabel(78, 119, FONT_SMALL, CENTER, Colors::WHITE, availableText);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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:"
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ set(lib_SRCS
|
||||
HeroBonus.cpp
|
||||
JsonDetail.cpp
|
||||
JsonNode.cpp
|
||||
LogicalExpression.cpp
|
||||
ResourceSet.cpp
|
||||
VCMI_Lib.cpp
|
||||
VCMIDirs.cpp
|
||||
|
@ -44,12 +44,13 @@ void CIdentifierStorage::checkIdentifier(std::string & ID)
|
||||
}
|
||||
|
||||
CIdentifierStorage::ObjectCallback::ObjectCallback(std::string localScope, std::string remoteScope, std::string type,
|
||||
std::string name, const std::function<void(si32)> & callback):
|
||||
std::string name, const std::function<void(si32)> & callback, bool optional):
|
||||
localScope(localScope),
|
||||
remoteScope(remoteScope),
|
||||
type(type),
|
||||
name(name),
|
||||
callback(callback)
|
||||
callback(callback),
|
||||
optional(optional)
|
||||
{}
|
||||
|
||||
static std::pair<std::string, std::string> splitString(std::string input, char separator)
|
||||
@ -84,14 +85,14 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type,
|
||||
{
|
||||
auto pair = splitString(name, ':'); // remoteScope:name
|
||||
|
||||
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback));
|
||||
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, false));
|
||||
}
|
||||
|
||||
void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
|
||||
{
|
||||
auto pair = splitString(name.String(), ':'); // remoteScope:name
|
||||
|
||||
requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback));
|
||||
requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, false));
|
||||
}
|
||||
|
||||
void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback)
|
||||
@ -99,7 +100,34 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun
|
||||
auto pair = splitString(name.String(), ':'); // remoteScope:<type.name>
|
||||
auto pair2 = splitString(pair.second, '.'); // type.name
|
||||
|
||||
requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback));
|
||||
requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback, false));
|
||||
}
|
||||
|
||||
void CIdentifierStorage::tryRequestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback)
|
||||
{
|
||||
auto pair = splitString(name, ':'); // remoteScope:name
|
||||
|
||||
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, true));
|
||||
}
|
||||
|
||||
void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
|
||||
{
|
||||
auto pair = splitString(name.String(), ':'); // remoteScope:name
|
||||
|
||||
requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, true));
|
||||
}
|
||||
|
||||
boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent)
|
||||
{
|
||||
auto pair = splitString(name.String(), ':'); // remoteScope:name
|
||||
auto idList = getIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, std::function<void(si32)>(), silent));
|
||||
|
||||
if (idList.size() == 1)
|
||||
return idList.front().id;
|
||||
if (!silent)
|
||||
logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << type;
|
||||
|
||||
return boost::optional<si32>();
|
||||
}
|
||||
|
||||
void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier)
|
||||
@ -114,7 +142,7 @@ void CIdentifierStorage::registerObject(std::string scope, std::string type, std
|
||||
registeredObjects.insert(std::make_pair(fullID, data));
|
||||
}
|
||||
|
||||
bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
|
||||
std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(const ObjectCallback & request)
|
||||
{
|
||||
std::set<std::string> allowedScopes;
|
||||
|
||||
@ -141,35 +169,44 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
|
||||
auto entries = registeredObjects.equal_range(fullID);
|
||||
if (entries.first != entries.second)
|
||||
{
|
||||
size_t matchesFound = 0;
|
||||
std::vector<ObjectData> locatedIDs;
|
||||
|
||||
for (auto it = entries.first; it != entries.second; it++)
|
||||
{
|
||||
if (vstd::contains(allowedScopes, it->second.scope))
|
||||
{
|
||||
if (matchesFound == 0) // trigger only once
|
||||
request.callback(it->second.id);
|
||||
matchesFound++;
|
||||
locatedIDs.push_back(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
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<ObjectData>();
|
||||
}
|
||||
|
||||
bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
|
||||
{
|
||||
auto identifiers = getIdentifier(request);
|
||||
if (identifiers.size() == 1) // normally resolved ID
|
||||
{
|
||||
request.callback(identifiers.front().id);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,9 @@ class CIdentifierStorage
|
||||
std::string type; /// type, e.g. creature, faction, hero, etc
|
||||
std::string name; /// string ID
|
||||
std::function<void(si32)> callback;
|
||||
bool optional;
|
||||
|
||||
ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const std::function<void(si32)> & callback);
|
||||
ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const std::function<void(si32)> & callback, bool optional);
|
||||
};
|
||||
|
||||
struct ObjectData // entry created on ID registration
|
||||
@ -50,14 +51,22 @@ class CIdentifierStorage
|
||||
|
||||
void requestIdentifier(ObjectCallback callback);
|
||||
bool resolveIdentifier(const ObjectCallback & callback);
|
||||
std::vector<ObjectData> getIdentifier(const ObjectCallback & callback);
|
||||
public:
|
||||
/// request identifier for specific object name. If ID is not yet resolved callback will be queued
|
||||
/// and will be called later
|
||||
/// request identifier for specific object name.
|
||||
/// Function callback will be called during ID resolution phase of loading
|
||||
void requestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
|
||||
void requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
|
||||
void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback);
|
||||
|
||||
/// registers new object, calls all associated callbacks
|
||||
/// try to request ID. If ID with such name won't be loaded, callback function will not be called
|
||||
void tryRequestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
|
||||
void tryRequestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
|
||||
|
||||
/// get identifier immediately. If identifier is not know and not silent call will result in error message
|
||||
boost::optional<si32> getIdentifier(std::string type, const JsonNode & name, bool silent = false);
|
||||
|
||||
/// registers new object
|
||||
void registerObject(std::string scope, std::string type, std::string name, si32 identifier);
|
||||
|
||||
/// called at the very end of loading to check for any missing ID's
|
||||
|
@ -260,7 +260,31 @@ std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
|
||||
return dest;
|
||||
}
|
||||
|
||||
void CTownHandler::loadBuilding(CTown &town, const JsonNode & source)
|
||||
void CTownHandler::loadBuildingRequirements(CTown &town, CBuilding & building, const JsonNode & source)
|
||||
{
|
||||
if (source.isNull())
|
||||
return;
|
||||
if (source.Vector()[0].getType() == JsonNode::DATA_FLOAT)
|
||||
{
|
||||
// MODS COMPATIBILITY
|
||||
CBuilding::TRequired::OperatorAll required;
|
||||
|
||||
for(const JsonNode &building : source.Vector())
|
||||
required.expressions.push_back(BuildingID(building.Float()));
|
||||
|
||||
building.requirements = CBuilding::TRequired(required);
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildingRequirementsHelper hlp;
|
||||
hlp.building = &building;
|
||||
hlp.faction = town.faction;
|
||||
hlp.json = source;
|
||||
requirementsToLoad.push_back(hlp);
|
||||
}
|
||||
}
|
||||
|
||||
void CTownHandler::loadBuilding(CTown &town, const std::string & stringID, const JsonNode & source)
|
||||
{
|
||||
auto ret = new CBuilding;
|
||||
|
||||
@ -268,65 +292,71 @@ void CTownHandler::loadBuilding(CTown &town, const JsonNode & source)
|
||||
|
||||
ret->mode = static_cast<CBuilding::EBuildMode>(boost::find(modes, source["mode"].String()) - modes);
|
||||
|
||||
ret->identifier = stringID;
|
||||
ret->town = &town;
|
||||
ret->bid = BuildingID(source["id"].Float());
|
||||
ret->name = source["name"].String();
|
||||
ret->description = source["description"].String();
|
||||
ret->resources = TResources(source["cost"]);
|
||||
|
||||
for(const JsonNode &building : source["requires"].Vector())
|
||||
ret->requirements.insert(BuildingID(building.Float()));
|
||||
loadBuildingRequirements(town, *ret, source["requires"]);
|
||||
|
||||
if (!source["upgrades"].isNull())
|
||||
{
|
||||
ret->requirements.insert(BuildingID(source["upgrades"].Float()));
|
||||
ret->upgrade = BuildingID(source["upgrades"].Float());
|
||||
}
|
||||
else
|
||||
ret->upgrade = BuildingID::NONE;
|
||||
|
||||
town.buildings[ret->bid] = ret;
|
||||
VLC->modh->identifiers.registerObject(source.meta, "building." + town.faction->identifier, ret->identifier, ret->bid);
|
||||
}
|
||||
|
||||
void CTownHandler::loadBuildings(CTown &town, const JsonNode & source)
|
||||
{
|
||||
if (source.getType() == JsonNode::DATA_VECTOR)
|
||||
for(auto &node : source.Struct())
|
||||
{
|
||||
for(auto &node : source.Vector())
|
||||
if (!node.second.isNull())
|
||||
{
|
||||
if (!node.isNull())
|
||||
loadBuilding(town, node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto &node : source.Struct())
|
||||
{
|
||||
if (!node.second.isNull())
|
||||
loadBuilding(town, node.second);
|
||||
loadBuilding(town, node.first, node.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CTownHandler::loadStructure(CTown &town, const JsonNode & source)
|
||||
void CTownHandler::loadStructure(CTown &town, const std::string & stringID, const JsonNode & source)
|
||||
{
|
||||
auto ret = new CStructure;
|
||||
auto ret = new CStructure;
|
||||
|
||||
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<bool> CTownHandler::getDefaultAllowed() const
|
||||
{
|
||||
std::vector<bool> allowedFactions;
|
||||
|
@ -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<BuildingID> TRequired;
|
||||
|
||||
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
|
||||
|
||||
enum EBuildMode
|
||||
@ -57,7 +61,7 @@ public:
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & town & bid & resources & name & description & requirements & upgrade & mode;
|
||||
h & identifier & town & bid & resources & name & description & requirements & upgrade & mode;
|
||||
}
|
||||
|
||||
friend class CTownHandler;
|
||||
@ -71,14 +75,13 @@ struct DLL_LINKAGE CStructure
|
||||
CBuilding * building; // base building. If null - this structure will be always present on screen
|
||||
CBuilding * buildable; // building that will be used to determine built building and visible cost. Usually same as "building"
|
||||
|
||||
bool hiddenUpgrade; // used only if "building" is upgrade, if true - structure on town screen will behave exactly like parent (mouse clicks, hover texts, etc)
|
||||
|
||||
int3 pos;
|
||||
std::string defName, borderName, areaName;
|
||||
std::string defName, borderName, areaName, identifier;
|
||||
|
||||
bool hiddenUpgrade; // used only if "building" is upgrade, if true - structure on town screen will behave exactly like parent (mouse clicks, hover texts, etc)
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & pos & defName & borderName & areaName & building & buildable & hiddenUpgrade;
|
||||
h & pos & defName & borderName & areaName & identifier & building & buildable & hiddenUpgrade;
|
||||
}
|
||||
};
|
||||
|
||||
@ -102,6 +105,7 @@ public:
|
||||
~CFaction();
|
||||
|
||||
std::string name; //town name, by default - from TownName.txt
|
||||
std::string identifier;
|
||||
|
||||
TFaction index;
|
||||
|
||||
@ -119,7 +123,7 @@ public:
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & name & index & nativeTerrain & alignment & commander & town & creatureBg120 & creatureBg130 & puzzleMap;
|
||||
h & name & identifier & index & nativeTerrain & alignment & commander & town & creatureBg120 & creatureBg130 & puzzleMap;
|
||||
}
|
||||
};
|
||||
|
||||
@ -134,19 +138,19 @@ public:
|
||||
std::vector<std::string> names; //names of the town instances
|
||||
|
||||
/// level -> list of creatures on this tier
|
||||
// TODO: replace with pointers to CCreature
|
||||
std::vector<std::vector<CreatureID> > creatures;
|
||||
|
||||
std::map<BuildingID, ConstTransitivePtr<CBuilding> > buildings;
|
||||
|
||||
std::vector<std::string> dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc.
|
||||
std::vector<std::string> dwellingNames;
|
||||
|
||||
// should be removed at least from configs in favor of auto-detection
|
||||
std::map<int,int> hordeLvl; //[0] - first horde building creature level; [1] - second horde building (-1 if not present)
|
||||
ui32 mageLevel; //max available mage guild level
|
||||
ui16 primaryRes;
|
||||
ArtifactID warMachine;
|
||||
// TODO: replace with pointers to CCreature
|
||||
std::vector<std::vector<CreatureID> > creatures;
|
||||
|
||||
std::map<BuildingID, ConstTransitivePtr<CBuilding> > buildings;
|
||||
|
||||
std::vector<std::string> dwellings; //defs for adventure map dwellings for new towns, [0] means tier 1 creatures etc.
|
||||
std::vector<std::string> dwellingNames;
|
||||
|
||||
// should be removed at least from configs in favor of auto-detection
|
||||
std::map<int,int> hordeLvl; //[0] - first horde building creature level; [1] - second horde building (-1 if not present)
|
||||
ui32 mageLevel; //max available mage guild level
|
||||
ui16 primaryRes;
|
||||
ArtifactID warMachine;
|
||||
si32 moatDamage;
|
||||
// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
|
||||
// resulting chance = sqrt(town.chance * heroClass.chance)
|
||||
@ -216,12 +220,23 @@ public:
|
||||
|
||||
class DLL_LINKAGE CTownHandler : public IHandlerBase
|
||||
{
|
||||
struct BuildingRequirementsHelper
|
||||
{
|
||||
JsonNode json;
|
||||
CBuilding * building;
|
||||
CFaction * faction;
|
||||
};
|
||||
|
||||
std::vector<BuildingRequirementsHelper> requirementsToLoad;
|
||||
void initializeRequirements();
|
||||
|
||||
/// loads CBuilding's into town
|
||||
void loadBuilding(CTown &town, const JsonNode & source);
|
||||
void loadBuildingRequirements(CTown &town, CBuilding & building, const JsonNode & source);
|
||||
void loadBuilding(CTown &town, const std::string & stringID, const JsonNode & source);
|
||||
void loadBuildings(CTown &town, const JsonNode & source);
|
||||
|
||||
/// loads CStructure's into town
|
||||
void loadStructure(CTown &town, const JsonNode & source);
|
||||
void loadStructure(CTown &town, const std::string & stringID, const JsonNode & source);
|
||||
void loadStructures(CTown &town, const JsonNode & source);
|
||||
|
||||
/// loads town hall vector (hallSlots)
|
||||
@ -234,7 +249,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
|
||||
|
||||
void loadPuzzle(CFaction & faction, const JsonNode & source);
|
||||
|
||||
CFaction * loadFromJson(const JsonNode & data);
|
||||
CFaction * loadFromJson(const JsonNode & data, std::string identifier);
|
||||
|
||||
public:
|
||||
std::vector<ConstTransitivePtr<CFaction> > factions;
|
||||
@ -247,6 +262,8 @@ public:
|
||||
void loadObject(std::string scope, std::string name, const JsonNode & data) override;
|
||||
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
|
||||
|
||||
void afterLoadFinalization() override;
|
||||
|
||||
std::vector<bool> getDefaultAllowed() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -570,7 +570,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
|
||||
if(!t->town->buildings.count(ID))
|
||||
return EBuildingState::BUILDING_ERROR;
|
||||
|
||||
const CBuilding * pom = t->town->buildings.at(ID);
|
||||
const CBuilding * building = t->town->buildings.at(ID);
|
||||
|
||||
|
||||
if(t->hasBuilt(ID)) //already built
|
||||
@ -580,27 +580,20 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
|
||||
if(vstd::contains(t->forbiddenBuildings, ID))
|
||||
return EBuildingState::FORBIDDEN; //forbidden
|
||||
|
||||
//checking for requirements
|
||||
std::set<BuildingID> reqs = getBuildingRequiments(t, ID);//getting all requirements
|
||||
|
||||
bool notAllBuilt = false;
|
||||
for(auto & req : reqs)
|
||||
auto buildTest = [&](const BuildingID & id)
|
||||
{
|
||||
if(!t->hasBuilt(req)) //lack of requirements - cannot build
|
||||
{
|
||||
if(vstd::contains(t->forbiddenBuildings, req)) // not built requirement forbidden - same goes to this build
|
||||
return EBuildingState::FORBIDDEN;
|
||||
else
|
||||
notAllBuilt = true; // no return here - we need to check if any required builds are forbidden
|
||||
}
|
||||
}
|
||||
return t->hasBuilt(id);
|
||||
};
|
||||
|
||||
if(t->builded >= VLC->modh->settings.MAX_BUILDING_PER_TURN)
|
||||
return EBuildingState::CANT_BUILD_TODAY; //building limit
|
||||
|
||||
if (notAllBuilt)
|
||||
if (!building->requirements.test(buildTest))
|
||||
return EBuildingState::PREREQUIRES;
|
||||
|
||||
if (building->upgrade != BuildingID::NONE && !t->hasBuilt(building->upgrade))
|
||||
return EBuildingState::MISSING_BASE;
|
||||
|
||||
if(ID == BuildingID::CAPITOL)
|
||||
{
|
||||
const PlayerState *ps = getPlayer(t->tempOwner);
|
||||
@ -624,43 +617,12 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
|
||||
}
|
||||
|
||||
//checking resources
|
||||
if(!pom->resources.canBeAfforded(getPlayer(t->tempOwner)->resources))
|
||||
if(!building->resources.canBeAfforded(getPlayer(t->tempOwner)->resources))
|
||||
return EBuildingState::NO_RESOURCES; //lack of res
|
||||
|
||||
return EBuildingState::ALLOWED;
|
||||
}
|
||||
|
||||
std::set<BuildingID> CGameInfoCallback::getBuildingRequiments( const CGTownInstance *t, BuildingID ID )
|
||||
{
|
||||
ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", std::set<BuildingID>());
|
||||
ERROR_RET_VAL_IF(!t->town->buildings.count(ID), "No such building!", std::set<BuildingID>());
|
||||
|
||||
std::set<int> used;
|
||||
used.insert(ID);
|
||||
auto reqs = t->town->buildings.at(ID)->requirements;
|
||||
|
||||
bool found;
|
||||
do
|
||||
{
|
||||
found = false;
|
||||
for(auto i=reqs.begin();i!=reqs.end();i++)
|
||||
{
|
||||
if(used.find(*i)==used.end()) //we haven't added requirements for this building
|
||||
{
|
||||
found = true;
|
||||
auto & requires = t->town->buildings.at(*i)->requirements;
|
||||
|
||||
used.insert(*i);
|
||||
for(auto & require : requires)
|
||||
reqs.insert(require);//creating full list of requirements
|
||||
}
|
||||
}
|
||||
}
|
||||
while (found);
|
||||
|
||||
return reqs;
|
||||
}
|
||||
|
||||
const CMapHeader * CGameInfoCallback::getMapHeader() const
|
||||
{
|
||||
return gs->map;
|
||||
|
@ -130,14 +130,12 @@ public:
|
||||
std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
|
||||
std::string getTavernGossip(const CGObjectInstance * townOrTavern) const;
|
||||
EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
|
||||
std::set<BuildingID> getBuildingRequiments(const CGTownInstance *t, BuildingID ID);
|
||||
virtual bool getTownInfo(const CGObjectInstance *town, InfoAboutTown &dest) const;
|
||||
const CTown *getNativeTown(PlayerColor color) const;
|
||||
|
||||
//from gs
|
||||
const TeamState *getTeam(TeamID teamID) const;
|
||||
const TeamState *getPlayerTeam(PlayerColor color) const;
|
||||
std::set<BuildingID> getBuildingRequiments(const CGTownInstance *t, BuildingID ID) const;
|
||||
EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID) const;// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
|
||||
};
|
||||
|
||||
|
12
lib/LogicalExpression.cpp
Normal file
12
lib/LogicalExpression.cpp
Normal 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
326
lib/LogicalExpression.h
Normal 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;
|
||||
}
|
||||
};
|
@ -2352,7 +2352,8 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
|
||||
const CBuilding * requestedBuilding = t->town->buildings.at(requestedID);
|
||||
|
||||
//Vector with future list of built building and buildings in auto-mode that are not yet built.
|
||||
std::vector<const CBuilding*> buildingsThatWillBe, remainingAutoBuildings;
|
||||
std::vector<const CBuilding*> remainingAutoBuildings;
|
||||
std::set<BuildingID> buildingsThatWillBe;
|
||||
|
||||
//Check validity of request
|
||||
if(!force)
|
||||
@ -2360,13 +2361,13 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
|
||||
switch (requestedBuilding->mode)
|
||||
{
|
||||
case CBuilding::BUILD_NORMAL :
|
||||
case CBuilding::BUILD_AUTO :
|
||||
if (gs->canBuildStructure(t, requestedID) != EBuildingState::ALLOWED)
|
||||
COMPLAIN_RET("Cannot build that building!");
|
||||
break;
|
||||
|
||||
case CBuilding::BUILD_AUTO :
|
||||
case CBuilding::BUILD_SPECIAL:
|
||||
COMPLAIN_RET("This building can not be constructed!");
|
||||
COMPLAIN_RET("This building can not be constructed normally!");
|
||||
|
||||
case CBuilding::BUILD_GRAIL :
|
||||
if(requestedBuilding->mode == CBuilding::BUILD_GRAIL) //needs grail
|
||||
@ -2421,22 +2422,21 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
|
||||
};
|
||||
|
||||
//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
|
||||
auto allRequirementsFullfilled = [&buildingsThatWillBe, t](const CBuilding *b)
|
||||
auto areRequirementsFullfilled = [&](const BuildingID & buildID)
|
||||
{
|
||||
for(auto requirementID : b->requirements)
|
||||
if(!vstd::contains(buildingsThatWillBe, t->town->buildings.at(requirementID)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return buildingsThatWillBe.count(buildID);
|
||||
};
|
||||
|
||||
//Init the vectors
|
||||
for(auto & build : t->town->buildings)
|
||||
{
|
||||
if(t->hasBuilt(build.first))
|
||||
buildingsThatWillBe.push_back(build.second);
|
||||
else if(build.second->mode == CBuilding::BUILD_AUTO) //not built auto building
|
||||
remainingAutoBuildings.push_back(build.second);
|
||||
buildingsThatWillBe.insert(build.first);
|
||||
else
|
||||
{
|
||||
if(build.second->mode == CBuilding::BUILD_AUTO) //not built auto building
|
||||
remainingAutoBuildings.push_back(build.second);
|
||||
}
|
||||
}
|
||||
|
||||
//Prepare structure (list of building ids will be filled later)
|
||||
@ -2453,12 +2453,12 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
|
||||
buildingsToAdd.pop();
|
||||
|
||||
ns.bid.insert(b->bid);
|
||||
buildingsThatWillBe.push_back(b);
|
||||
buildingsThatWillBe.insert(b->bid);
|
||||
remainingAutoBuildings -= b;
|
||||
|
||||
for(auto autoBuilding : remainingAutoBuildings)
|
||||
{
|
||||
if(allRequirementsFullfilled(autoBuilding))
|
||||
if (autoBuilding->requirements.test(areRequirementsFullfilled))
|
||||
buildingsToAdd.push(autoBuilding);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user