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

- basic support for dependencies\conflicts for mods

- adventure map objects transparency should be more similar to h3
This commit is contained in:
Ivan Savenko 2013-01-10 18:53:49 +00:00
parent d31277c3cc
commit 7e7d12095b
8 changed files with 227 additions and 59 deletions

View File

@ -34,6 +34,5 @@
}, },
"name" : "In The Wake of Gods", "name" : "In The Wake of Gods",
"description" : "Unnofficial addon for Heroes of Might and Magic III", "description" : "Unnofficial addon for Heroes of Might and Magic III"
"priority" : 5
} }

View File

@ -17,5 +17,9 @@
"name" : "VCMI essential files", "name" : "VCMI essential files",
"description" : "Essential files required for VCMI to run correctly", "description" : "Essential files required for VCMI to run correctly",
"priority" : 10
"requires" :
[
"wog"
]
} }

View File

@ -5921,7 +5921,7 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation &artLoc, const A
} }
//Make sure the status bar is updated so it does not display old text //Make sure the status bar is updated so it does not display old text
if(destaoh != NULL) if(destaoh != NULL && destaoh->getArtPlace(destLoc.slot) != NULL)
{ {
destaoh->getArtPlace(destLoc.slot)->hover(true); destaoh->getArtPlace(destLoc.slot)->hover(true);
} }

View File

@ -194,13 +194,20 @@ Uint32 CSDL_Ext::SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y
void CSDL_Ext::alphaTransform(SDL_Surface *src) void CSDL_Ext::alphaTransform(SDL_Surface *src)
{ {
//NOTE: colors #7 & #8 used in some of WoG objects. Don't know how they're handled by H3
assert(src->format->BitsPerPixel == 8); assert(src->format->BitsPerPixel == 8);
SDL_Color colors[] = {{0,0,0,0}, {0,0,0,32}, {0,0,0,64}, {0,0,0,128}, {0,0,0,128}, SDL_Color colors[] =
{255,255,255,0}, {255,255,255,0}, {255,255,255,0}, {0,0,0,192}, {0,0,0,192}}; {
{ 0, 0, 0, 0}, { 0, 0, 0, 32}, { 0, 0, 0, 64},
{ 0, 0, 0, 128}, { 0, 0, 0, 128}
};
SDL_SetColors(src, colors, 0, ARRAY_COUNT(colors));
SDL_SetColorKey(src, SDL_SRCCOLORKEY, SDL_MapRGBA(src->format, 0, 0, 0, 255)); for (size_t i=0; i< ARRAY_COUNT(colors); i++ )
{
SDL_Color & palColor = src->format->palette->colors[i];
palColor = colors[i];
}
SDL_SetColorKey(src, SDL_SRCCOLORKEY, 0);
} }
static void prepareOutRect(SDL_Rect *src, SDL_Rect *dst, const SDL_Rect & clip_rect) static void prepareOutRect(SDL_Rect *src, SDL_Rect *dst, const SDL_Rect & clip_rect)

View File

@ -115,37 +115,155 @@ void CModHandler::loadConfigFromFile (std::string name)
modules.MITHRIL = gameModules["MITHRIL"].Bool(); modules.MITHRIL = gameModules["MITHRIL"].Bool();
} }
// 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))
{
tlog0 << "Error: Circular dependency detected! Printing dependency list:\n";
tlog0 << "\t" << mod.name << " -> \n";
return true;
}
currentList.insert(modID);
// recursively check every dependency of this mod
BOOST_FOREACH(const TModID & dependency, mod.dependencies)
{
if (hasCircularDependency(dependency, currentList))
{
tlog0 << "\t" << mod.name << " ->\n"; // conflict detected, print dependency list
return true;
}
}
return false;
}
bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
{
BOOST_FOREACH(const TModID & id, input)
{
const CModInfo & mod = allMods.at(id);
BOOST_FOREACH(const TModID & dep, mod.dependencies)
{
if (!vstd::contains(input, dep))
{
tlog0 << "Error: Mod " << mod.name << " requires missing " << dep << "!\n";
return false;
}
}
BOOST_FOREACH(const TModID & conflicting, mod.conflicts)
{
if (vstd::contains(input, conflicting))
{
tlog0 << "Error: Mod " << mod.name << " conflicts with " << allMods.at(conflicting).name << "!\n";
return false;
}
}
if (hasCircularDependency(id))
return false;
}
return true;
}
std::vector <TModID> CModHandler::resolveDependencies(std::vector <TModID> input) const
{
// 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 cide should be fine
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
{
BOOST_FOREACH(const TModID & dependency, mod.dependencies)
{
if (!vstd::contains(resolvedMods, dependency))
return false;
}
return true;
};
while (!input.empty())
{
for (auto it = input.begin(); it != input.end();)
{
if (isResolved(allMods.at(*it)))
{
resolvedMods.insert(*it);
output.push_back(*it);
it = input.erase(it);
continue;
}
it++;
}
}
return output;
}
void CModHandler::initialize(std::vector<std::string> availableMods) void CModHandler::initialize(std::vector<std::string> availableMods)
{ {
BOOST_FOREACH(std::string &name, availableMods) JsonNode modConfig(ResourceID("config/modSettings.json"));
const JsonNode & modList = modConfig["activeMods"];
JsonNode resultingList;
std::vector <TModID> detectedMods;
BOOST_FOREACH(std::string name, availableMods)
{ {
boost::to_lower(name);
std::string modFileName = "mods/" + name + "/mod.json"; std::string modFileName = "mods/" + name + "/mod.json";
if (CResourceHandler::get()->existsResource(ResourceID(modFileName))) if (CResourceHandler::get()->existsResource(ResourceID(modFileName)))
{ {
const JsonNode config = JsonNode(ResourceID(modFileName)); const JsonNode config = JsonNode(ResourceID(modFileName));
if (!config.isNull()) if (config.isNull())
{ continue;
allMods[name].identifier = name;
allMods[name].name = config["name"].String();
allMods[name].description = config["description"].String();
allMods[name].loadPriority = config["priority"].Float();
activeMods.push_back(name);
tlog1 << "\t\tMod "; if (!modList[name].isNull() && modList[name].Bool() == false )
tlog2 << allMods[name].name; {
tlog1 << " enabled\n"; resultingList[name].Bool() = false;
continue; // disabled mod
} }
resultingList[name].Bool() = true;
CModInfo & mod = allMods[name];
mod.identifier = name;
mod.name = config["name"].String();
mod.description = config["description"].String();
mod.dependencies = config["depends"].convertTo<std::set<std::string> >();
mod.conflicts = config["conflicts"].convertTo<std::set<std::string> >();
detectedMods.push_back(name);
} }
else else
tlog1 << "\t\t Directory " << name << " does not contains VCMI mod\n"; tlog1 << "\t\t Directory " << name << " does not contains VCMI mod\n";
} }
std::sort(activeMods.begin(), activeMods.end(), [&](std::string a, std::string b) if (!checkDependencies(detectedMods))
{ {
return allMods[a].loadPriority < allMods[b].loadPriority; tlog0 << "Critical error: failed to load mods! Exiting...\n";
}); exit(1);
}
activeMods = resolveDependencies(detectedMods);
modConfig["activeMods"] = resultingList;
CResourceHandler::get()->createResource("CONFIG/modSettings.json");
std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
file << modConfig;
} }
@ -162,8 +280,11 @@ void handleData(Handler handler, const JsonNode & config)
void CModHandler::loadActiveMods() void CModHandler::loadActiveMods()
{ {
BOOST_FOREACH(std::string & modName, activeMods) BOOST_FOREACH(const TModID & modName, activeMods)
{ {
tlog1 << "\t\tLoading mod ";
tlog2 << allMods[modName].name << "\n";
std::string modFileName = "mods/" + modName + "/mod.json"; std::string modFileName = "mods/" + modName + "/mod.json";
const JsonNode config = JsonNode(ResourceID(modFileName)); const JsonNode config = JsonNode(ResourceID(modFileName));

View File

@ -51,19 +51,18 @@ public:
std::string name; std::string name;
std::string description; std::string description;
/// priority in which this mod should be loaded /// list of mods that should be loaded before this one
/// may be somewhat ignored to load required mods first or overriden by user std::set <TModID> dependencies;
double loadPriority;
/// TODO: list of mods that should be loaded before this one /// list of mods that can't be used in the same time as this one
std::set <TModID> requirements; std::set <TModID> conflicts;
// mod configuration (mod.json). (no need to store it right now) // mod configuration (mod.json). (no need to store it right now)
// std::shared_ptr<JsonNode> config; //TODO: unique_ptr can't be serialized // std::shared_ptr<JsonNode> config; //TODO: unique_ptr can't be serialized
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & name & requirements; h & identifier & description & name & dependencies & conflicts;
} }
}; };
@ -73,6 +72,18 @@ class DLL_LINKAGE CModHandler
std::vector <TModID> activeMods;//active mods, in order in which they were loaded std::vector <TModID> activeMods;//active mods, in order in which they were loaded
void loadConfigFromFile (std::string name); void loadConfigFromFile (std::string name);
bool hasCircularDependency(TModID mod, std::set <TModID> currentList = std::set <TModID>()) const;
//returns false if mod list is incorrec and prints error to console. Possible errors are:
// - missing dependency mod
// - conflicting mod in load order
// - circular dependencies
bool checkDependencies(const std::vector <TModID> & input) const;
// returns load order in which all dependencies are resolved, e.g. loaded after required mods
// function assumes that input list is valid (checkDependencies returned true)
std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
public: public:
CIdentifierStorage identifiers; CIdentifierStorage identifiers;

View File

@ -6894,39 +6894,51 @@ void CArmedInstance::updateMoraleBonusFromArmy()
} }
//number of alignments and presence of undead //number of alignments and presence of undead
bool canMix = hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX); std::set<TFaction> factions;
std::set<si8> factions; bool hasUndead = false;
for(TSlots::const_iterator i=Slots().begin(); i!=Slots().end(); i++)
BOOST_FOREACH(auto slot, Slots())
{ {
// Take Angelic Alliance troop-mixing freedom of non-evil, non-Conflux units into account. const CStackInstance * inst = slot.second;
const si8 faction = i->second->type->faction; const CCreature * creature = VLC->creh->creatures[inst->getCreatureID()];
if (canMix
&& ((faction >= 0 && faction <= 2) || faction == 6 || faction == 7)) factions.insert(creature->faction);
{ // Check for undead flag instead of faction (undead mummies are neutral)
factions.insert(0); // Insert a single faction of the affected group, Castle will do. hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD);
}
else
{
factions.insert(faction);
}
} }
if(factions.size() == 1) size_t factionsInArmy = factions.size();
// Take Angelic Alliance troop-mixing freedom of non-evil units into account.
if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX))
{
size_t mixableFactions = 0;
BOOST_FOREACH(TFaction f, factions)
{
if (VLC->townh->factions[f].alignment != EAlignment::EVIL)
mixableFactions++;
}
if (mixableFactions > 0)
factionsInArmy -= mixableFactions - 1;
}
if(factionsInArmy == 1)
{ {
b->val = +1; b->val = +1;
b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
} }
else else
{ {
b->val = 2-factions.size(); b->val = 2 - factionsInArmy;
b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factions.size() % b->val); //Troops of %d alignments %d b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
} }
boost::algorithm::trim(b->description); boost::algorithm::trim(b->description);
//-1 modifier for any Necropolis unit in army //-1 modifier for any Undead unit in army
const ui8 UNDEAD_MODIFIER_ID = -2; const ui8 UNDEAD_MODIFIER_ID = -2;
Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID)); Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
if(vstd::contains(factions, ETownType::NECROPOLIS)) if(hasUndead)
{ {
if(!undeadModifier) if(!undeadModifier)
addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116])); addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]));

View File

@ -175,6 +175,20 @@ namespace JsonDetail
} }
}; };
template<typename Type>
struct JsonConverter<std::set<Type> >
{
static std::set<Type> convert(const JsonNode & node)
{
std::set<Type> ret;
BOOST_FOREACH(auto entry, node.Vector())
{
ret.insert(entry.convertTo<Type>());
}
return ret;
}
};
template<typename Type> template<typename Type>
struct JsonConverter<std::vector<Type> > struct JsonConverter<std::vector<Type> >
{ {