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

View File

@ -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);
@ -1336,27 +1336,37 @@ 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:
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 = CGI->generaltexth->allTexts[52];
ret += "\n" + building->requirements.toString(toStr);
break;
}
ret.erase(ret.size()-2);
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;

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);
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;

View File

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

View File

@ -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:"
}
}

View File

@ -66,6 +66,7 @@ set(lib_SRCS
HeroBonus.cpp
JsonDetail.cpp
JsonNode.cpp
LogicalExpression.cpp
ResourceSet.cpp
VCMI_Lib.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,
std::string name, const std::function<void(si32)> & callback):
std::string name, const std::function<void(si32)> & callback, bool optional):
localScope(localScope),
remoteScope(remoteScope),
type(type),
name(name),
callback(callback)
callback(callback),
optional(optional)
{}
static std::pair<std::string, std::string> splitString(std::string input, char separator)
@ -84,14 +85,14 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type,
{
auto pair = splitString(name, ':'); // remoteScope:name
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback));
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, false));
}
void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
{
auto pair = splitString(name.String(), ':'); // remoteScope:name
requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback));
requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, false));
}
void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback)
@ -99,7 +100,34 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun
auto pair = splitString(name.String(), ':'); // remoteScope:<type.name>
auto pair2 = splitString(pair.second, '.'); // type.name
requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback));
requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback, false));
}
void CIdentifierStorage::tryRequestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback)
{
auto pair = splitString(name, ':'); // remoteScope:name
requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, true));
}
void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
{
auto pair = splitString(name.String(), ':'); // remoteScope:name
requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, true));
}
boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent)
{
auto pair = splitString(name.String(), ':'); // remoteScope:name
auto idList = getIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, std::function<void(si32)>(), silent));
if (idList.size() == 1)
return idList.front().id;
if (!silent)
logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << type;
return boost::optional<si32>();
}
void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier)
@ -114,7 +142,7 @@ void CIdentifierStorage::registerObject(std::string scope, std::string type, std
registeredObjects.insert(std::make_pair(fullID, data));
}
bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(const ObjectCallback & request)
{
std::set<std::string> allowedScopes;
@ -141,35 +169,44 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
auto entries = registeredObjects.equal_range(fullID);
if (entries.first != entries.second)
{
size_t matchesFound = 0;
std::vector<ObjectData> locatedIDs;
for (auto it = entries.first; it != entries.second; it++)
{
if (vstd::contains(allowedScopes, it->second.scope))
{
if (matchesFound == 0) // trigger only once
request.callback(it->second.id);
matchesFound++;
locatedIDs.push_back(it->second);
}
}
return locatedIDs;
}
return std::vector<ObjectData>();
}
if (matchesFound == 1)
return true; // success, only one matching ID
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 (matchesFound == 0)
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 it = entries.first; it != entries.second; it++)
for (auto id : identifiers)
{
logGlobal->errorStream() << "\tID is available in mod " << it->second.scope;
logGlobal->errorStream() << "\tID is available in mod " << id.scope;
}
}
logGlobal->errorStream() << "Unknown identifier " << request.type << "." << request.name << " from mod " << request.localScope;
return false;
}

View File

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

View File

@ -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.Vector())
{
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;
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 = 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();
@ -340,22 +370,11 @@ void CTownHandler::loadStructure(CTown &town, const JsonNode & source)
}
void CTownHandler::loadStructures(CTown &town, const JsonNode & source)
{
if (source.getType() == JsonNode::DATA_VECTOR)
{
for(auto &node : source.Vector())
{
if (!node.isNull())
loadStructure(town, node);
}
}
else
{
for(auto &node : source.Struct())
{
if (!node.second.isNull())
loadStructure(town, node.second);
}
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;

View File

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

View File

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

View File

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

View File

@ -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
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);
//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,23 +2422,22 @@ 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
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)
NewStructures ns;
@ -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);
}
}