1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-02 09:02:03 +02:00
vcmi/lib/CModHandler.cpp
Arseniy Shestakov 10dbbead2d Fix indentation of logging code and around it
That wouldn't be as big issue if problem affected few files, but it everywhere in codebase.
Fixed it everywhere since in most files that is the only code with wrong indentation.
2016-03-12 04:46:21 +03:00

939 lines
30 KiB
C++

#include "StdInc.h"
#include "CModHandler.h"
#include "mapObjects/CObjectClassesHandler.h"
#include "JsonNode.h"
#include "filesystem/FileStream.h"
#include "filesystem/Filesystem.h"
#include "filesystem/AdapterLoaders.h"
#include "filesystem/CFilesystemLoader.h"
#include "CCreatureHandler.h"
#include "CArtHandler.h"
#include "CTownHandler.h"
#include "CHeroHandler.h"
#include "mapObjects/CObjectHandler.h"
#include "StringConstants.h"
#include "CStopWatch.h"
#include "IHandlerBase.h"
#include "spells/CSpellHandler.h"
/*
* CModHandler.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
*
*/
CIdentifierStorage::CIdentifierStorage():
state(LOADING)
{
}
void CIdentifierStorage::checkIdentifier(std::string & ID)
{
if (boost::algorithm::ends_with(ID, "."))
logGlobal->warnStream() << "BIG WARNING: identifier " << ID << " seems to be broken!";
else
{
size_t pos = 0;
do
{
if (std::tolower(ID[pos]) != ID[pos] ) //Not in camelCase
{
logGlobal->warnStream() << "Warning: identifier " << ID << " is not in camelCase!";
ID[pos] = std::tolower(ID[pos]);// Try to fix the ID
}
pos = ID.find('.', pos);
}
while(pos++ != std::string::npos);
}
}
CIdentifierStorage::ObjectCallback::ObjectCallback(std::string localScope, std::string remoteScope, std::string type,
std::string name, const std::function<void(si32)> & callback, bool optional):
localScope(localScope),
remoteScope(remoteScope),
type(type),
name(name),
callback(callback),
optional(optional)
{}
static std::pair<std::string, std::string> splitString(std::string input, char separator)
{
std::pair<std::string, std::string> ret;
size_t splitPos = input.find(separator);
if (splitPos == std::string::npos)
{
ret.first.clear();
ret.second = input;
}
else
{
ret.first = input.substr(0, splitPos);
ret.second = input.substr(splitPos + 1);
}
return ret;
}
void CIdentifierStorage::requestIdentifier(ObjectCallback callback)
{
checkIdentifier(callback.type);
checkIdentifier(callback.name);
assert(!callback.localScope.empty());
if (state != FINISHED) // enqueue request if loading is still in progress
scheduledRequests.push_back(callback);
else // execute immediately for "late" requests
resolveIdentifier(callback);
}
void CIdentifierStorage::requestIdentifier(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, false));
}
void CIdentifierStorage::requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)>& callback)
{
auto scopeAndFullName = splitString(fullName, ':');
auto typeAndName = splitString(scopeAndFullName.second, '.');
requestIdentifier(ObjectCallback(scope, scopeAndFullName.first, typeAndName.first, typeAndName.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, false));
}
void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback)
{
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, 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 scope, std::string type, std::string name, bool silent)
{
auto pair = splitString(name, ':'); // remoteScope:name
auto idList = getPossibleIdentifiers(ObjectCallback(scope, 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 << " of type " << type << " from mod " << scope;
return boost::optional<si32>();
}
boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent)
{
auto pair = splitString(name.String(), ':'); // remoteScope:name
auto idList = getPossibleIdentifiers(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() << " of type " << type << " from mod " << name.meta;
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() << " of type " << pair2.first << " from mod " << name.meta;
return boost::optional<si32>();
}
void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier)
{
ObjectData data;
data.scope = scope;
data.id = identifier;
std::string fullID = type + '.' + name;
checkIdentifier(fullID);
registeredObjects.insert(std::make_pair(fullID, data));
}
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" && request.localScope != "")
allowedScopes = VLC->modh->getModData(request.localScope).dependencies;
allowedScopes.insert(request.localScope);
allowedScopes.insert("core");
}
else
{
//...unless destination mod was specified explicitly
//note: getModData does not work for "core" by design
//for map format support core mod has access to any mod
//TODO: better solution for access from map?
if(request.localScope == "core" || request.localScope == "")
{
allowedScopes.insert(request.remoteScope);
}
else
{
// allow only available to all core mod or dependencies
auto myDeps = VLC->modh->getModData(request.localScope).dependencies;
if (request.remoteScope == "core" || myDeps.count(request.remoteScope))
allowedScopes.insert(request.remoteScope);
}
}
std::string fullID = request.type + '.' + request.name;
auto entries = registeredObjects.equal_range(fullID);
if (entries.first != entries.second)
{
std::vector<ObjectData> locatedIDs;
for (auto it = entries.first; it != entries.second; it++)
{
if (vstd::contains(allowedScopes, it->second.scope))
{
locatedIDs.push_back(it->second);
}
}
return locatedIDs;
}
return std::vector<ObjectData>();
}
bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
{
auto identifiers = getPossibleIdentifiers(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;
}
return false;
}
void CIdentifierStorage::finalize()
{
state = FINALIZING;
bool errorsFound = false;
//Note: we may receive new requests during resolution phase -> end may change -> range for can't be used
for(auto it = scheduledRequests.begin(); it != scheduledRequests.end(); it++)
{
errorsFound |= !resolveIdentifier(*it);
}
if (errorsFound)
{
for(auto object : registeredObjects)
{
logGlobal->traceStream() << object.second.scope << " : " << object.first << " -> " << object.second.id;
}
logGlobal->errorStream() << "All known identifiers were dumped into log file";
}
assert(errorsFound == false);
state = FINISHED;
}
CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName):
handler(handler),
objectName(objectName),
originalData(handler->loadLegacyData(VLC->modh->settings.data["textData"][objectName].Float()))
{
for(auto & node : originalData)
{
node.setMeta("core");
}
}
bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList, bool validate)
{
bool result;
JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
data.setMeta(modName);
ModInfo & modInfo = modData[modName];
for(auto entry : data.Struct())
{
size_t colon = entry.first.find(':');
if (colon == std::string::npos)
{
// normal object, local to this mod
modInfo.modData[entry.first].swap(entry.second);
}
else
{
std::string remoteName = entry.first.substr(0, colon);
std::string objectName = entry.first.substr(colon + 1);
// patching this mod? Send warning and continue - this situation can be handled normally
if (remoteName == modName)
logGlobal->warnStream() << "Redundant namespace definition for " << objectName;
logGlobal->traceStream() << "Patching object " << objectName << " (" << remoteName << ") from " << modName;
JsonNode & remoteConf = modData[remoteName].patches[objectName];
JsonUtils::merge(remoteConf, entry.second);
}
}
return result;
}
bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate)
{
ModInfo & modInfo = modData[modName];
bool result = true;
auto performValidate = [&,this](JsonNode & data, const std::string & name){
handler->beforeValidate(data);
if (validate)
result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
};
// apply patches
if (!modInfo.patches.isNull())
JsonUtils::merge(modInfo.modData, modInfo.patches);
for(auto & entry : modInfo.modData.Struct())
{
const std::string & name = entry.first;
JsonNode & data = entry.second;
if (vstd::contains(data.Struct(), "index") && !data["index"].isNull())
{
// try to add H3 object data
size_t index = data["index"].Float();
if (originalData.size() > index)
{
JsonUtils::merge(originalData[index], data);
performValidate(originalData[index],name);
handler->loadObject(modName, name, originalData[index], index);
originalData[index].clear(); // do not use same data twice (same ID)
continue;
}
}
// normal new object or one with index bigger that data size
performValidate(data,name);
handler->loadObject(modName, name, data);
}
return result;
}
void CContentHandler::ContentTypeHandler::afterLoadFinalization()
{
handler->afterLoadFinalization();
}
CContentHandler::CContentHandler()
{
handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass")));
handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact")));
handlers.insert(std::make_pair("creatures", ContentTypeHandler(VLC->creh, "creature")));
handlers.insert(std::make_pair("factions", ContentTypeHandler(VLC->townh, "faction")));
handlers.insert(std::make_pair("objects", ContentTypeHandler(VLC->objtypeh, "object")));
handlers.insert(std::make_pair("heroes", ContentTypeHandler(VLC->heroh, "hero")));
handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell")));
handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template")));
//TODO: any other types of moddables?
}
bool CContentHandler::preloadModData(std::string modName, JsonNode modConfig, bool validate)
{
bool result = true;
for(auto & handler : handlers)
{
result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >(), validate);
}
return result;
}
bool CContentHandler::loadMod(std::string modName, bool validate)
{
bool result = true;
for(auto & handler : handlers)
{
result &= handler.second.loadMod(modName, validate);
}
return result;
}
void CContentHandler::afterLoadFinalization()
{
for(auto & handler : handlers)
{
handler.second.afterLoadFinalization();
}
}
void CContentHandler::preloadData(CModInfo & mod)
{
bool validate = (mod.validation != CModInfo::PASSED);
// print message in format [<8-symbols checksum>] <modname>
logGlobal->infoStream() << "\t\t[" << std::noshowbase << std::hex << std::setw(8) << std::setfill('0')
<< mod.checksum << "] " << mod.name;
if (validate && mod.identifier != "core")
{
if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier))
mod.validation = CModInfo::FAILED;
}
if (!preloadModData(mod.identifier, mod.config, validate))
mod.validation = CModInfo::FAILED;
}
void CContentHandler::load(CModInfo & mod)
{
bool validate = (mod.validation != CModInfo::PASSED);
if (!loadMod(mod.identifier, validate))
mod.validation = CModInfo::FAILED;
if (validate)
{
if (mod.validation != CModInfo::FAILED)
logGlobal->infoStream() << "\t\t[DONE] " << mod.name;
else
logGlobal->errorStream() << "\t\t[FAIL] " << mod.name;
}
else
logGlobal->infoStream() << "\t\t[SKIP] " << mod.name;
}
static JsonNode loadModSettings(std::string path)
{
if (CResourceHandler::get("local")->existsResource(ResourceID(path)))
{
return JsonNode(ResourceID(path, EResType::TEXT));
}
// Probably new install. Create initial configuration
CResourceHandler::get("local")->createResource(path);
return JsonNode();
}
JsonNode addMeta(JsonNode config, std::string meta)
{
config.setMeta(meta);
return config;
}
CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode & config):
identifier(identifier),
name(config["name"].String()),
description(config["description"].String()),
dependencies(config["depends"].convertTo<std::set<std::string> >()),
conflicts(config["conflicts"].convertTo<std::set<std::string> >()),
validation(PENDING),
config(addMeta(config, identifier))
{
loadLocalData(local);
}
JsonNode CModInfo::saveLocalData() const
{
std::ostringstream stream;
stream << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << checksum;
JsonNode conf;
conf["active"].Bool() = enabled;
conf["validated"].Bool() = validation != FAILED;
conf["checksum"].String() = stream.str();
return conf;
}
std::string CModInfo::getModDir(std::string name)
{
return "MODS/" + boost::algorithm::replace_all_copy(name, ".", "/MODS/");
}
std::string CModInfo::getModFile(std::string name)
{
return getModDir(name) + "/mod.json";
}
void CModInfo::updateChecksum(ui32 newChecksum)
{
// comment-out next line to force validation of all mods ignoring checksum
if (newChecksum != checksum)
{
checksum = newChecksum;
validation = PENDING;
}
}
void CModInfo::loadLocalData(const JsonNode & data)
{
bool validated = false;
enabled = true;
checksum = 0;
if (data.getType() == JsonNode::DATA_BOOL)
{
enabled = data.Bool();
}
if (data.getType() == JsonNode::DATA_STRUCT)
{
enabled = data["active"].Bool();
validated = data["validated"].Bool();
checksum = strtol(data["checksum"].String().c_str(), nullptr, 16);
}
if (enabled)
validation = validated ? PASSED : PENDING;
else
validation = validated ? PASSED : FAILED;
}
CModHandler::CModHandler()
{
for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i)
{
identifiers.registerObject("core", "resource", GameConstants::RESOURCE_NAMES[i], i);
}
for(int i=0; i<GameConstants::PRIMARY_SKILLS; ++i)
identifiers.registerObject("core", "primSkill", PrimarySkill::names[i], i);
}
void CModHandler::loadConfigFromFile (std::string name)
{
std::string paths;
for(auto& p : CResourceHandler::get()->getResourceNames(ResourceID("config/" + name)))
{
paths += p.string() + ", ";
}
paths = paths.substr(0, paths.size() - 2);
logGlobal->debugStream() << "Loading hardcoded features settings from [" << paths << "], result:";
settings.data = JsonUtils::assembleFromFiles("config/" + name);
const JsonNode & hardcodedFeatures = settings.data["hardcodedFeatures"];
settings.MAX_HEROES_AVAILABLE_PER_PLAYER = hardcodedFeatures["MAX_HEROES_AVAILABLE_PER_PLAYER"].Float();
logGlobal->debugStream() << "\tMAX_HEROES_AVAILABLE_PER_PLAYER\t" << settings.MAX_HEROES_AVAILABLE_PER_PLAYER;
settings.MAX_HEROES_ON_MAP_PER_PLAYER = hardcodedFeatures["MAX_HEROES_ON_MAP_PER_PLAYER"].Float();
logGlobal->debugStream() << "\tMAX_HEROES_ON_MAP_PER_PLAYER\t" << settings.MAX_HEROES_ON_MAP_PER_PLAYER;
settings.CREEP_SIZE = hardcodedFeatures["CREEP_SIZE"].Float();
logGlobal->debugStream() << "\tCREEP_SIZE\t" << settings.CREEP_SIZE;
settings.WEEKLY_GROWTH = hardcodedFeatures["WEEKLY_GROWTH_PERCENT"].Float();
logGlobal->debugStream() << "\tWEEKLY_GROWTH\t" << settings.WEEKLY_GROWTH;
settings.NEUTRAL_STACK_EXP = hardcodedFeatures["NEUTRAL_STACK_EXP_DAILY"].Float();
logGlobal->debugStream() << "\tNEUTRAL_STACK_EXP\t" << settings.NEUTRAL_STACK_EXP;
settings.MAX_BUILDING_PER_TURN = hardcodedFeatures["MAX_BUILDING_PER_TURN"].Float();
logGlobal->debugStream() << "\tMAX_BUILDING_PER_TURN\t" << settings.MAX_BUILDING_PER_TURN;
settings.DWELLINGS_ACCUMULATE_CREATURES = hardcodedFeatures["DWELLINGS_ACCUMULATE_CREATURES"].Bool();
logGlobal->debugStream() << "\tDWELLINGS_ACCUMULATE_CREATURES\t" << settings.DWELLINGS_ACCUMULATE_CREATURES;
settings.ALL_CREATURES_GET_DOUBLE_MONTHS = hardcodedFeatures["ALL_CREATURES_GET_DOUBLE_MONTHS"].Bool();
logGlobal->debugStream() << "\tALL_CREATURES_GET_DOUBLE_MONTHS\t" << settings.ALL_CREATURES_GET_DOUBLE_MONTHS;
settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS = hardcodedFeatures["WINNING_HERO_WITH_NO_TROOPS_RETREATS"].Bool();
logGlobal->debugStream() << "\tWINNING_HERO_WITH_NO_TROOPS_RETREATS\t" << settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS;
const JsonNode & gameModules = settings.data["modules"];
modules.STACK_EXP = gameModules["STACK_EXPERIENCE"].Bool();
logGlobal->debugStream() << "\tSTACK_EXP\t" << modules.STACK_EXP;
modules.STACK_ARTIFACT = gameModules["STACK_ARTIFACTS"].Bool();
logGlobal->debugStream() << "\tSTACK_ARTIFACT\t" << modules.STACK_ARTIFACT;
modules.COMMANDERS = gameModules["COMMANDERS"].Bool();
logGlobal->debugStream() << "\tCOMMANDERS\t" << modules.COMMANDERS;
modules.MITHRIL = gameModules["MITHRIL"].Bool();
logGlobal->debugStream() << "\tMITHRIL\t" << modules.MITHRIL;
}
// currentList is passed by value to get current list of depending mods
bool CModHandler::hasCircularDependency(TModID modID, std::set <TModID> currentList) const
{
const CModInfo & mod = allMods.at(modID);
// Mod already present? We found a loop
if (vstd::contains(currentList, modID))
{
logGlobal->errorStream() << "Error: Circular dependency detected! Printing dependency list:";
logGlobal->errorStream() << "\t" << mod.name << " -> ";
return true;
}
currentList.insert(modID);
// recursively check every dependency of this mod
for(const TModID & dependency : mod.dependencies)
{
if (hasCircularDependency(dependency, currentList))
{
logGlobal->errorStream() << "\t" << mod.name << " ->\n"; // conflict detected, print dependency list
return true;
}
}
return false;
}
bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
{
for(const TModID & id : input)
{
const CModInfo & mod = allMods.at(id);
for(const TModID & dep : mod.dependencies)
{
if (!vstd::contains(input, dep))
{
logGlobal->errorStream() << "Error: Mod " << mod.name << " requires missing " << dep << "!";
return false;
}
}
for(const TModID & conflicting : mod.conflicts)
{
if (vstd::contains(input, conflicting))
{
logGlobal->errorStream() << "Error: Mod " << mod.name << " conflicts with " << allMods.at(conflicting).name << "!";
return false;
}
}
if (hasCircularDependency(id))
return false;
}
return true;
}
std::vector <TModID> CModHandler::resolveDependencies(std::vector <TModID> input) const
{
// Topological sort algorithm
// May not be the fastest one but VCMI does not needs any speed here
// Unless user have dozens of mods with complex dependencies this code should be fine
// first - sort input to have input strictly based on name (and not on hashmap or anything else)
boost::range::sort(input);
std::vector <TModID> output;
output.reserve(input.size());
std::set <TModID> resolvedMods;
// Check if all mod dependencies are resolved (moved to resolvedMods)
auto isResolved = [&](const CModInfo mod) -> bool
{
for(const TModID & dependency : mod.dependencies)
{
if (!vstd::contains(resolvedMods, dependency))
return false;
}
return true;
};
while (!input.empty())
{
std::set <TModID> toResolve; // list of mods resolved on this iteration
for (auto it = input.begin(); it != input.end();)
{
if (isResolved(allMods.at(*it)))
{
toResolve.insert(*it);
output.push_back(*it);
it = input.erase(it);
continue;
}
it++;
}
resolvedMods.insert(toResolve.begin(), toResolve.end());
}
return output;
}
std::vector<std::string> CModHandler::getModList(std::string path)
{
std::string modDir = boost::to_upper_copy(path + "MODS/");
size_t depth = boost::range::count(modDir, '/');
auto list = CResourceHandler::get("initial")->getFilteredFiles([&](const ResourceID & id) -> bool
{
if (id.getType() != EResType::DIRECTORY)
return false;
if (!boost::algorithm::starts_with(id.getName(), modDir))
return false;
if (boost::range::count(id.getName(), '/') != depth )
return false;
return true;
});
//storage for found mods
std::vector<std::string> foundMods;
for (auto & entry : list)
{
std::string name = entry.getName();
name.erase(0, modDir.size()); //Remove path prefix
// check if wog is actually present. Hack-ish but better than crash
// TODO: remove soon (hopefully - before 0.96)
if (name == "WOG")
{
if (!CResourceHandler::get("initial")->existsResource(ResourceID("DATA/ZVS", EResType::DIRECTORY)) &&
!CResourceHandler::get("initial")->existsResource(ResourceID("MODS/WOG/DATA/ZVS", EResType::DIRECTORY)))
{
continue;
}
}
if (!name.empty())
foundMods.push_back(name);
}
return foundMods;
}
void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods)
{
for (std::string modName : getModList(path))
{
boost::to_lower(modName);
std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
if (CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
{
CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
if (!parent.empty()) // this is submod, add parent to dependecies
mod.dependencies.insert(parent);
allMods[modFullName] = mod;
if (mod.enabled && enableMods)
activeMods.push_back(modFullName);
loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
}
}
}
void CModHandler::loadMods()
{
const JsonNode modConfig = loadModSettings("config/modSettings.json");
loadMods("", "", modConfig["activeMods"], true);
coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json")));
coreMod.name = "Original game files";
}
std::vector<std::string> CModHandler::getAllMods()
{
std::vector<std::string> modlist;
for (auto & entry : allMods)
modlist.push_back(entry.first);
return modlist;
}
std::vector<std::string> CModHandler::getActiveMods()
{
return activeMods;
}
static JsonNode genDefaultFS()
{
// default FS config for mods: directory "Content" that acts as H3 root directory
JsonNode defaultFS;
defaultFS[""].Vector().resize(2);
defaultFS[""].Vector()[0]["type"].String() = "zip";
defaultFS[""].Vector()[0]["path"].String() = "/Content.zip";
defaultFS[""].Vector()[1]["type"].String() = "dir";
defaultFS[""].Vector()[1]["path"].String() = "/Content";
return defaultFS;
}
static ISimpleResourceLoader * genModFilesystem(const std::string & modName, const JsonNode & conf)
{
static const JsonNode defaultFS = genDefaultFS();
if (!conf["filesystem"].isNull())
return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), conf["filesystem"]);
else
return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), defaultFS);
}
static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoader * filesystem)
{
boost::crc_32_type modChecksum;
// first - add current VCMI version into checksum to force re-validation on VCMI updates
modChecksum.process_bytes(reinterpret_cast<const void*>(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size());
// second - add mod.json into checksum because filesystem does not contains this file
// FIXME: remove workaround for core mod
if (modName != "core")
{
ResourceID modConfFile(CModInfo::getModFile(modName), EResType::TEXT);
ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32();
modChecksum.process_bytes(reinterpret_cast<const void *>(&configChecksum), sizeof(configChecksum));
}
// third - add all detected text files from this mod into checksum
auto files = filesystem->getFilteredFiles([](const ResourceID & resID)
{
return resID.getType() == EResType::TEXT &&
( boost::starts_with(resID.getName(), "DATA") ||
boost::starts_with(resID.getName(), "CONFIG"));
});
for (const ResourceID & file : files)
{
ui32 fileChecksum = filesystem->load(file)->calculateCRC32();
modChecksum.process_bytes(reinterpret_cast<const void *>(&fileChecksum), sizeof(fileChecksum));
}
return modChecksum.checksum();
}
void CModHandler::loadModFilesystems()
{
activeMods = resolveDependencies(activeMods);
coreMod.updateChecksum(calculateModChecksum("core", CResourceHandler::get("core")));
for(std::string & modName : activeMods)
{
CModInfo & mod = allMods[modName];
CResourceHandler::addFilesystem("data", modName, genModFilesystem(modName, mod.config));
}
}
CModInfo & CModHandler::getModData(TModID modId)
{
auto it = allMods.find(modId);
if(it == allMods.end())
{
throw std::runtime_error("Mod not found '" + modId+"'");
}
else
{
return it->second;
}
}
void CModHandler::initializeConfig()
{
loadConfigFromFile("defaultMods.json");
}
void CModHandler::load()
{
CStopWatch totalTime, timer;
CContentHandler content;
logGlobal->infoStream() << "\tInitializing content handler: " << timer.getDiff() << " ms";
for(const TModID & modName : activeMods)
{
logGlobal->traceStream() << "Generating checksum for " << modName;
allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName)));
}
// first - load virtual "core" mod that contains all data
// TODO? move all data into real mods? RoE, AB, SoD, WoG
content.preloadData(coreMod);
for(const TModID & modName : activeMods)
content.preloadData(allMods[modName]);
logGlobal->infoStream() << "\tParsing mod data: " << timer.getDiff() << " ms";
content.load(coreMod);
for(const TModID & modName : activeMods)
content.load(allMods[modName]);
logGlobal->infoStream() << "\tLoading mod data: " << timer.getDiff() << "ms";
VLC->creh->loadCrExpBon();
VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded
identifiers.finalize();
logGlobal->infoStream() << "\tResolving identifiers: " << timer.getDiff() << " ms";
content.afterLoadFinalization();
logGlobal->infoStream() << "\tHandlers post-load finalization: " << timer.getDiff() << " ms";
logGlobal->infoStream() << "\tAll game content loaded in " << totalTime.getDiff() << " ms";
}
void CModHandler::afterLoad()
{
JsonNode modSettings;
for (auto & modEntry : allMods)
{
std::string pointer = "/" + boost::algorithm::replace_all_copy(modEntry.first, ".", "/mods/");
modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData();
}
modSettings["core"] = coreMod.saveLocalData();
FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
file << modSettings;
}
std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const
{
auto p = splitString(identifier, ':');
if(p.first.empty())
p.first = scope;
if(p.first == remoteScope)
p.first.clear();
return p.first.empty() ? p.second : p.first +":"+p.second;
}