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

Support for overriding victory/defeat conditions from h3m map or

campaign:
- new file MapFormatJson that implements small subset of Json map
format, as described on wiki
- vcmi will read overrides from file config/mapOverrides.json (currently
empty)
- Json writer for logical expressions

TODO: write data for map overrides
This commit is contained in:
Ivan Savenko 2013-12-30 23:09:58 +00:00
parent a03d01bd3f
commit 7e02f6b670
13 changed files with 406 additions and 18 deletions

View File

@ -3306,11 +3306,15 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
selectedMap = mapNr;
selectedBonus = boost::none;
std::string scenarioName = ourCampaign->camp->header.filename.substr(0, ourCampaign->camp->header.filename.find('.'));
boost::to_lower(scenarioName);
scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
//get header
delete ourHeader;
std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
ourHeader = CMapService::loadMapHeader(buffer, headerStr.size()).release();
ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName).release();
std::map<ui8, std::string> names;
names[1] = settings["general"]["playerName"].String();

2
config/mapOverrides.json Normal file
View File

@ -0,0 +1,2 @@
{
}

View File

@ -869,9 +869,13 @@ void CGameState::initCampaign()
auto campaign = scenarioOps->campState;
assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap));
std::string & mapContent = campaign->camp->mapPieces[*scenarioOps->campState->currentMap];
std::string scenarioName = scenarioOps->mapname.substr(0, scenarioOps->mapname.find('.'));
boost::to_lower(scenarioName);
scenarioName += ':' + boost::lexical_cast<std::string>(*campaign->currentMap);
std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
map = CMapService::loadMap(buffer, mapContent.size()).release();
map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release();
}
void CGameState::initDuel()

View File

@ -33,6 +33,7 @@ set(lib_SRCS
mapping/CMapInfo.cpp
mapping/CMapService.cpp
mapping/MapFormatH3M.cpp
mapping/MapFormatJson.cpp
rmg/CMapGenerator.cpp
rmg/CMapGenOptions.cpp

View File

@ -122,7 +122,7 @@ void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode &
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));
auto idList = getPossibleIdentifiers(ObjectCallback(name.meta, pair.first, type, pair.second, std::function<void(si32)>(), silent));
if (idList.size() == 1)
return idList.front().id;
@ -132,6 +132,20 @@ boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const
return boost::optional<si32>();
}
boost::optional<si32> CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent)
{
auto pair = splitString(name.String(), ':'); // remoteScope:<type.name>
auto pair2 = splitString(pair.second, '.'); // type.name
auto idList = getPossibleIdentifiers(ObjectCallback(name.meta, pair.first, pair2.first, pair2.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 " << pair2.first;
return boost::optional<si32>();
}
void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier)
{
ObjectData data;
@ -144,14 +158,14 @@ void CIdentifierStorage::registerObject(std::string scope, std::string type, std
registeredObjects.insert(std::make_pair(fullID, data));
}
std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(const ObjectCallback & request)
std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request)
{
std::set<std::string> allowedScopes;
if (request.remoteScope.empty())
{
// normally ID's from all required mods, own mod and virtual "core" mod are allowed
if (request.localScope != "core")
if (request.localScope != "core" && request.localScope != "")
allowedScopes = VLC->modh->getModData(request.localScope).dependencies;
allowedScopes.insert(request.localScope);
@ -187,7 +201,7 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(co
bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
{
auto identifiers = getIdentifier(request);
auto identifiers = getPossibleIdentifiers(request);
if (identifiers.size() == 1) // normally resolved ID
{
request.callback(identifiers.front().id);

View File

@ -51,7 +51,7 @@ class CIdentifierStorage
void requestIdentifier(ObjectCallback callback);
bool resolveIdentifier(const ObjectCallback & callback);
std::vector<ObjectData> getIdentifier(const ObjectCallback & callback);
std::vector<ObjectData> getPossibleIdentifiers(const ObjectCallback & callback);
public:
/// request identifier for specific object name.
/// Function callback will be called during ID resolution phase of loading
@ -65,6 +65,7 @@ public:
/// 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);
boost::optional<si32> getIdentifier(const JsonNode & name, bool silent = false);
/// registers new object
void registerObject(std::string scope, std::string type, std::string name, si32 identifier);

View File

@ -223,6 +223,49 @@ namespace LogicalExpressionDetail
}
};
/// Prints expression in human-readable format
template <typename ContainedClass>
class Writer : public boost::static_visitor<JsonNode>
{
typedef ExpressionBase<ContainedClass> Base;
std::function<JsonNode(const typename Base::Value &)> classPrinter;
JsonNode printExpressionList(std::string name, const std::vector<typename Base::Variant> & element) const
{
JsonNode ret;
ret.Vector().resize(1);
ret.Vector().back().String() = name;
for (auto & expr : element)
ret.Vector().push_back(boost::apply_visitor(*this, expr));
return ret;
}
public:
Writer(std::function<JsonNode(const typename Base::Value &)> classPrinter):
classPrinter(classPrinter)
{}
JsonNode operator()(const typename Base::OperatorAny & element) const
{
return printExpressionList("anyOf", element.expressions);
}
JsonNode operator()(const typename Base::OperatorAll & element) const
{
return printExpressionList("allOf", element.expressions);
}
JsonNode operator()(const typename Base::OperatorNone & element) const
{
return printExpressionList("noneOf", element.expressions);
}
JsonNode operator()(const typename Base::Value & element) const
{
return classPrinter(element);
}
};
std::string DLL_LINKAGE getTextForOperator(std::string operation);
/// Prints expression in human-readable format
@ -368,6 +411,12 @@ public:
return boost::apply_visitor(printVisitor, data);
}
JsonNode toJson(std::function<JsonNode(const Value &)> toJson) const
{
LogicalExpressionDetail::Writer<Value> writeVisitor(toJson);
return boost::apply_visitor(writeVisitor, data);
}
template <typename Handler>
void serialize(Handler & h, const int version)
{

View File

@ -64,9 +64,13 @@ unique_ptr<CCampaign> CCampaignHandler::getCampaign( const std::string & name )
scenarioID++;
}
std::string scenarioName = name.substr(0, name.find('.'));
boost::to_lower(scenarioName);
scenarioName += ':' + boost::lexical_cast<std::string>(g-1);
//set map piece appropriately, convert vector to string
ret->mapPieces[scenarioID].assign(reinterpret_cast< const char* >(file[g].data()), file[g].size());
ret->scenarios[scenarioID].scenarioName = CMapService::loadMapHeader((const ui8*)ret->mapPieces[scenarioID].c_str(), ret->mapPieces[scenarioID].size())->name;
ret->scenarios[scenarioID].scenarioName = CMapService::loadMapHeader((const ui8*)ret->mapPieces[scenarioID].c_str(), ret->mapPieces[scenarioID].size(), scenarioName)->name;
scenarioID++;
}

View File

@ -8,30 +8,47 @@
#include "CMap.h"
#include "MapFormatH3M.h"
#include "MapFormatJson.h"
std::unique_ptr<CMap> CMapService::loadMap(const std::string & name)
{
auto stream = getStreamFromFS(name);
return getMapLoader(stream)->loadMap();
std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
std::unique_ptr<CMapHeader> header(map.get());
getMapPatcher(name)->patchMapHeader(header);
header.release();
return std::move(map);
}
std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const std::string & name)
{
auto stream = getStreamFromFS(name);
return getMapLoader(stream)->loadMapHeader();
std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
getMapPatcher(name)->patchMapHeader(header);
return std::move(header);
}
std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size)
std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const std::string & name)
{
auto stream = getStreamFromMem(buffer, size);
return getMapLoader(stream)->loadMap();
std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
std::unique_ptr<CMapHeader> header(map.get());
getMapPatcher(name)->patchMapHeader(header);
header.release();
return std::move(map);
}
std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ui8 * buffer, int size)
std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ui8 * buffer, int size, const std::string & name)
{
auto stream = getStreamFromMem(buffer, size);
return getMapLoader(stream)->loadMapHeader();
std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
getMapPatcher(name)->patchMapHeader(header);
return std::move(header);
}
std::unique_ptr<CInputStream> CMapService::getStreamFromFS(const std::string & name)
@ -68,3 +85,11 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
throw std::runtime_error("Unknown map format");
}
}
std::unique_ptr<IMapPatcher> CMapService::getMapPatcher(std::string scenarioName)
{
boost::to_lower(scenarioName);
logGlobal->debugStream() << "Request to patch map " << scenarioName;
JsonNode node = JsonUtils::assembleFromFiles("config/mapOverrides.json");
return std::unique_ptr<IMapPatcher>(new CMapLoaderJson(node[scenarioName]));
}

View File

@ -16,6 +16,7 @@ class CMapHeader;
class CInputStream;
class IMapLoader;
class IMapPatcher;
/**
* The map service provides loading of VCMI/H3 map files. It can
@ -49,9 +50,10 @@ public:
*
* @param buffer a pointer to a buffer containing the map data
* @param size the size of the buffer
* @param name indicates name of file that will be used during map header patching
* @return a unique ptr to the loaded map class
*/
static std::unique_ptr<CMap> loadMap(const ui8 * buffer, int size);
static std::unique_ptr<CMap> loadMap(const ui8 * buffer, int size, const std::string & name);
/**
* Loads the VCMI/H3 map header from a buffer. This method is temporarily
@ -62,9 +64,10 @@ public:
*
* @param buffer a pointer to a buffer containing the map header data
* @param size the size of the buffer
* @param name indicates name of file that will be used during map header patching
* @return a unique ptr to the loaded map class
*/
static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size);
static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size, const std::string & name);
private:
/**
@ -92,6 +95,14 @@ private:
* @return the constructed map loader
*/
static std::unique_ptr<IMapLoader> getMapLoader(std::unique_ptr<CInputStream> & stream);
/**
* Gets a map patcher for specified scenario
*
* @param scenarioName for patcher
* @return the constructed map patcher
*/
static std::unique_ptr<IMapPatcher> getMapPatcher(std::string scenarioName);
};
/**
@ -115,4 +126,12 @@ public:
virtual std::unique_ptr<CMapHeader> loadMapHeader() = 0;
};
class DLL_LINKAGE IMapPatcher : public IMapLoader
{
public:
/**
* Modifies supplied map header using Json data
*
*/
virtual void patchMapHeader(std::unique_ptr<CMapHeader> & header) = 0;
};

View File

@ -1563,9 +1563,11 @@ void CMapLoaderH3M::readObjects()
if(htid == 0xff)
{
hp->power = reader.readUInt8();
logGlobal->infoStream() << "Hero placeholder: by power at " << objPos;
}
else
{
logGlobal->infoStream() << "Hero placeholder: " << VLC->heroh->heroes[htid]->name << " at " << objPos;
hp->power = 0;
}
@ -1684,6 +1686,7 @@ void CMapLoaderH3M::readObjects()
}
if(nobj->ID == Obj::HERO)
{
logGlobal->infoStream() << "Hero: " << VLC->heroh->heroes[nobj->subID]->name << " at " << objPos;
map->heroesOnMap.push_back(static_cast<CGHeroInstance*>(nobj));
}
}

View File

@ -0,0 +1,171 @@
/*
* MapFormatJson.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "MapFormatJson.h"
#include "CMap.h"
#include "../CModHandler.h"
#include "../VCMI_Lib.h"
static const std::string conditionNames[] = {
"haveArtifact", "haveCreatures", "haveResources", "haveBuilding",
"control", "destroy", "transport",
"daysPassed", "isHuman", "daysWithoutTown", "standardWin"
};
static const std::string typeNames[] = { "victory", "defeat" };
CMapLoaderJson::CMapLoaderJson(JsonNode stream):
input(stream)
{
}
std::unique_ptr<CMap> CMapLoaderJson::loadMap()
{
map = new CMap();
mapHeader.reset(map);
readMap();
mapHeader.reset();
return std::unique_ptr<CMap>(map);
}
std::unique_ptr<CMapHeader> CMapLoaderJson::loadMapHeader()
{
mapHeader.reset(new CMapHeader);
readHeader();
return std::move(mapHeader);
}
/*
//This code can be used to write map header to console or file in its Json representation
JsonNode out;
JsonNode data;
data["victoryString"].String() = mapHeader->victoryMessage;
data["defeatString"].String() = mapHeader->defeatMessage;
data["victoryIconIndex"].Float() = mapHeader->victoryIconIndex;
data["defeatIconIndex"].Float() = mapHeader->defeatIconIndex;
for (const TriggeredEvent & entry : mapHeader->triggeredEvents)
{
JsonNode event;
event["message"].String() = entry.onFulfill;
event["effect"]["messageToSend"].String() = entry.effect.toOtherMessage;
event["effect"]["type"].String() = typeNames[entry.effect.type];
event["condition"] = entry.trigger.toJson(eventToJson);
data["triggeredEvents"][entry.identifier] = event;
}
out[mapHeader->name] = data;
logGlobal->errorStream() << out;
JsonNode eventToJson(const EventCondition & cond)
{
JsonNode ret;
ret.Vector().resize(2);
ret.Vector()[0].String() = conditionNames[size_t(cond.condition)];
JsonNode & data = ret.Vector()[1];
data["type"].Float() = cond.objectType;
data["value"].Float() = cond.value;
data["position"].Vector().resize(3);
data["position"].Vector()[0].Float() = cond.position.x;
data["position"].Vector()[1].Float() = cond.position.y;
data["position"].Vector()[2].Float() = cond.position.z;
return ret;
}
*/
void CMapLoaderJson::patchMapHeader(std::unique_ptr<CMapHeader> & header)
{
header.swap(mapHeader);
if (!input.isNull())
readPatchData();
header.swap(mapHeader);
}
void CMapLoaderJson::readMap()
{
readHeader();
assert(0); // Not implemented, vcmi does not have its own map format right now
}
void CMapLoaderJson::readHeader()
{
//TODO: read such data like map name & size
readPatchData();
readPlayerInfo();
assert(0); // Not implemented
}
void CMapLoaderJson::readPatchData()
{
mapHeader->victoryMessage = input["victoryString"].String();
mapHeader->victoryIconIndex = input["victoryIconIndex"].Float();
mapHeader->defeatMessage = input["defeatString"].String();
mapHeader->defeatIconIndex = input["defeatIconIndex"].Float();
readTriggeredEvents();
}
void CMapLoaderJson::readTriggeredEvents()
{
mapHeader->triggeredEvents.clear();
for (auto & entry : input["triggeredEvents"].Struct())
{
TriggeredEvent event;
event.identifier = entry.first;
readTriggeredEvent(event, entry.second);
mapHeader->triggeredEvents.push_back(event);
}
}
static EventCondition JsonToCondition(const JsonNode & node)
{
EventCondition event;
event.condition = EventCondition::EWinLoseType(vstd::find_pos(conditionNames, node.Vector()[0].String()));
if (node.Vector().size() > 1)
{
const JsonNode & data = node.Vector()[1];
if (data["type"].getType() == JsonNode::DATA_STRING)
event.objectType = VLC->modh->identifiers.getIdentifier(data["type"]).get();
if (data["type"].getType() == JsonNode::DATA_FLOAT)
event.objectType = data["type"].Float();
if (!data["value"].isNull())
event.value = data["value"].Float();
if (!data["position"].isNull())
{
event.position.x = data["position"].Vector()[0].Float();
event.position.y = data["position"].Vector()[1].Float();
event.position.z = data["position"].Vector()[2].Float();
}
}
return event;
}
void CMapLoaderJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source)
{
event.onFulfill = source["message"].String();
event.description = source["description"].String();
event.effect.type = vstd::find_pos(typeNames, source["effect"]["type"].String());
event.effect.toOtherMessage = source["effect"]["messageToSend"].String();
event.trigger = EventExpression(source["condition"], JsonToCondition); // logical expression
}
void CMapLoaderJson::readPlayerInfo()
{
assert(0); // Not implemented
}

View File

@ -0,0 +1,91 @@
/*
* MapFormatH3M.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CMapService.h"
#include "../JsonNode.h"
class TriggeredEvent;
class DLL_LINKAGE CMapLoaderJson : public IMapPatcher
{
public:
/**
* Default constructor.
*
* @param stream a stream containing the map data
*/
CMapLoaderJson(JsonNode stream);
/**
* Loads the VCMI/Json map file.
*
* @return a unique ptr of the loaded map class
*/
std::unique_ptr<CMap> loadMap();
/**
* Loads the VCMI/Json map header.
*
* @return a unique ptr of the loaded map header class
*/
std::unique_ptr<CMapHeader> loadMapHeader();
/**
* Modifies supplied map header using Json data
*
*/
void patchMapHeader(std::unique_ptr<CMapHeader> & header);
private:
/**
* Reads complete map.
*/
void readMap();
/**
* Reads the map header.
*/
void readHeader();
/**
* Reads subset of header that can be replaced by patching.
*/
void readPatchData();
/**
* Reads player information.
*/
void readPlayerInfo();
/**
* Reads triggered events, including victory/loss conditions
*/
void readTriggeredEvents();
/**
* Reads one of triggered events
*/
void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source);
/** ptr to the map object which gets filled by data from the buffer */
CMap * map;
/**
* ptr to the map header object which gets filled by data from the buffer.
* (when loading map and mapHeader point to the same object)
*/
std::unique_ptr<CMapHeader> mapHeader;
const JsonNode input;
};