2024-11-09 22:29:07 +02:00
/*
* ModManager . 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 "ModManager.h"
# include "ModDescription.h"
# include "ModScope.h"
2024-11-14 17:41:22 +02:00
# include "../constants/StringConstants.h"
2024-11-09 22:29:07 +02:00
# include "../filesystem/Filesystem.h"
# include "../json/JsonNode.h"
2024-11-11 13:19:14 +02:00
# include "../texts/CGeneralTextHandler.h"
2024-11-09 22:29:07 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2024-11-14 20:22:02 +02:00
static std : : string getModDirectory ( const TModID & modName )
2024-11-09 22:29:07 +02:00
{
std : : string result = modName ;
boost : : to_upper ( result ) ;
boost : : algorithm : : replace_all ( result , " . " , " /MODS/ " ) ;
2024-11-14 20:22:02 +02:00
return " MODS/ " + result ;
}
static std : : string getModSettingsDirectory ( const TModID & modName )
{
return getModDirectory ( modName ) + " /MODS/ " ;
2024-11-09 22:29:07 +02:00
}
static JsonPath getModDescriptionFile ( const TModID & modName )
{
2024-11-14 20:22:02 +02:00
return JsonPath : : builtin ( getModDirectory ( modName ) + " /mod " ) ;
2024-11-09 22:29:07 +02:00
}
ModsState : : ModsState ( )
{
modList . push_back ( ModScope : : scopeBuiltin ( ) ) ;
std : : vector < TModID > testLocations = scanModsDirectory ( " MODS/ " ) ;
while ( ! testLocations . empty ( ) )
{
std : : string target = testLocations . back ( ) ;
testLocations . pop_back ( ) ;
modList . push_back ( boost : : algorithm : : to_lower_copy ( target ) ) ;
for ( const auto & submod : scanModsDirectory ( getModSettingsDirectory ( target ) ) )
testLocations . push_back ( target + ' . ' + submod ) ;
}
}
2024-11-14 17:41:22 +02:00
TModList ModsState : : getInstalledMods ( ) const
2024-11-09 22:29:07 +02:00
{
return modList ;
}
2024-11-14 17:41:22 +02:00
uint32_t ModsState : : computeChecksum ( const TModID & modName ) const
{
boost : : crc_32_type modChecksum ;
// first - add current VCMI version into checksum to force re-validation on VCMI updates
2024-11-14 17:50:39 +02:00
modChecksum . process_bytes ( static_cast < const void * > ( GameConstants : : VCMI_VERSION . data ( ) ) , GameConstants : : VCMI_VERSION . size ( ) ) ;
2024-11-14 17:41:22 +02:00
// second - add mod.json into checksum because filesystem does not contains this file
if ( modName ! = ModScope : : scopeBuiltin ( ) )
{
auto modConfFile = getModDescriptionFile ( modName ) ;
ui32 configChecksum = CResourceHandler : : get ( " initial " ) - > load ( modConfFile ) - > calculateCRC32 ( ) ;
2024-11-14 17:50:39 +02:00
modChecksum . process_bytes ( static_cast < const void * > ( & configChecksum ) , sizeof ( configChecksum ) ) ;
2024-11-14 17:41:22 +02:00
}
// third - add all detected text files from this mod into checksum
const auto & filesystem = CResourceHandler : : get ( modName ) ;
auto files = filesystem - > getFilteredFiles ( [ ] ( const ResourcePath & resID )
{
return resID . getType ( ) = = EResType : : JSON & & boost : : starts_with ( resID . getName ( ) , " CONFIG " ) ;
} ) ;
for ( const ResourcePath & file : files )
{
ui32 fileChecksum = filesystem - > load ( file ) - > calculateCRC32 ( ) ;
2024-11-14 17:50:39 +02:00
modChecksum . process_bytes ( static_cast < const void * > ( & fileChecksum ) , sizeof ( fileChecksum ) ) ;
2024-11-14 17:41:22 +02:00
}
return modChecksum . checksum ( ) ;
}
2024-11-14 20:22:02 +02:00
double ModsState : : getInstalledModSizeMegabytes ( const TModID & modName ) const
{
ResourcePath resDir ( getModDirectory ( modName ) , EResType : : DIRECTORY ) ;
std : : string path = CResourceHandler : : get ( ) - > getResourceName ( resDir ) - > string ( ) ;
size_t sizeBytes = 0 ;
for ( boost : : filesystem : : recursive_directory_iterator it ( path ) ; it ! = boost : : filesystem : : recursive_directory_iterator ( ) ; + + it )
{
if ( ! boost : : filesystem : : is_directory ( * it ) )
sizeBytes + = boost : : filesystem : : file_size ( * it ) ;
}
2024-11-18 16:40:15 +02:00
double sizeMegabytes = sizeBytes / static_cast < double > ( 1024 * 1024 ) ;
2024-11-14 20:22:02 +02:00
return sizeMegabytes ;
}
2024-11-09 22:29:07 +02:00
std : : vector < TModID > ModsState : : scanModsDirectory ( const std : : string & modDir ) const
{
size_t depth = boost : : range : : count ( modDir , ' / ' ) ;
const auto & modScanFilter = [ & ] ( const ResourcePath & 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 ;
} ;
auto list = CResourceHandler : : get ( " initial " ) - > getFilteredFiles ( modScanFilter ) ;
//storage for found mods
std : : vector < TModID > foundMods ;
for ( const auto & entry : list )
{
std : : string name = entry . getName ( ) ;
name . erase ( 0 , modDir . size ( ) ) ; //Remove path prefix
if ( name . empty ( ) )
continue ;
if ( name . find ( ' . ' ) ! = std : : string : : npos )
continue ;
2024-11-14 17:41:22 +02:00
if ( ModScope : : isScopeReserved ( boost : : to_lower_copy ( name ) ) )
continue ;
2024-11-09 22:29:07 +02:00
if ( ! CResourceHandler : : get ( " initial " ) - > existsResource ( JsonPath : : builtin ( entry . getName ( ) + " /MOD " ) ) )
continue ;
foundMods . push_back ( name ) ;
}
return foundMods ;
}
///////////////////////////////////////////////////////////////////////////////
2024-11-18 16:40:15 +02:00
ModsPresetState : : ModsPresetState ( )
2024-11-09 22:29:07 +02:00
{
2024-11-18 16:40:15 +02:00
static const JsonPath settingsPath = JsonPath : : builtin ( " config/modSettings.json " ) ;
if ( CResourceHandler : : get ( " local " ) - > existsResource ( ResourcePath ( settingsPath ) ) )
2024-11-09 22:29:07 +02:00
{
2024-11-18 16:40:15 +02:00
modConfig = JsonNode ( settingsPath ) ;
}
else
{
// Probably new install. Create initial configuration
CResourceHandler : : get ( " local " ) - > createResource ( settingsPath . getOriginalName ( ) + " .json " ) ;
2024-11-09 22:29:07 +02:00
}
if ( modConfig [ " presets " ] . isNull ( ) )
{
modConfig [ " activePreset " ] = JsonNode ( " default " ) ;
if ( modConfig [ " activeMods " ] . isNull ( ) )
createInitialPreset ( ) ; // new install
else
importInitialPreset ( ) ; // 1.5 format import
}
}
void ModsPresetState : : createInitialPreset ( )
{
// TODO: scan mods directory for all its content? Probably unnecessary since this looks like new install, but who knows?
2024-11-14 17:41:22 +02:00
modConfig [ " presets " ] [ " default " ] [ " mods " ] . Vector ( ) . emplace_back ( " vcmi " ) ;
2024-11-09 22:29:07 +02:00
}
void ModsPresetState : : importInitialPreset ( )
{
JsonNode preset ;
for ( const auto & mod : modConfig [ " activeMods " ] . Struct ( ) )
{
if ( mod . second [ " active " ] . Bool ( ) )
2024-11-14 17:41:22 +02:00
preset [ " mods " ] . Vector ( ) . emplace_back ( mod . first ) ;
2024-11-09 22:29:07 +02:00
for ( const auto & submod : mod . second [ " mods " ] . Struct ( ) )
preset [ " settings " ] [ mod . first ] [ submod . first ] = submod . second [ " active " ] ;
}
modConfig [ " presets " ] [ " default " ] = preset ;
}
2024-11-11 13:19:14 +02:00
const JsonNode & ModsPresetState : : getActivePresetConfig ( ) const
2024-11-09 22:29:07 +02:00
{
const std : : string & currentPresetName = modConfig [ " activePreset " ] . String ( ) ;
const JsonNode & currentPreset = modConfig [ " presets " ] [ currentPresetName ] ;
2024-11-11 13:19:14 +02:00
return currentPreset ;
}
2024-11-09 22:29:07 +02:00
2024-11-11 13:19:14 +02:00
TModList ModsPresetState : : getActiveRootMods ( ) const
{
const JsonNode & modsToActivateJson = getActivePresetConfig ( ) [ " mods " ] ;
2024-11-14 17:50:39 +02:00
auto modsToActivate = modsToActivateJson . convertTo < std : : vector < TModID > > ( ) ;
2024-11-20 22:44:24 +02:00
if ( ! vstd : : contains ( modsToActivate , ModScope : : scopeBuiltin ( ) ) )
modsToActivate . push_back ( ModScope : : scopeBuiltin ( ) ) ;
2024-11-11 13:19:14 +02:00
return modsToActivate ;
}
2024-11-09 22:29:07 +02:00
2024-11-11 13:19:14 +02:00
std : : map < TModID , bool > ModsPresetState : : getModSettings ( const TModID & modID ) const
{
const JsonNode & modSettingsJson = getActivePresetConfig ( ) [ " settings " ] [ modID ] ;
2024-11-14 17:50:39 +02:00
auto modSettings = modSettingsJson . convertTo < std : : map < TModID , bool > > ( ) ;
2024-11-11 13:19:14 +02:00
return modSettings ;
}
2024-11-14 17:41:22 +02:00
std : : optional < uint32_t > ModsPresetState : : getValidatedChecksum ( const TModID & modName ) const
{
const JsonNode & node = modConfig [ " validatedMods " ] [ modName ] ;
if ( node . isNull ( ) )
return std : : nullopt ;
else
return node . Integer ( ) ;
}
2024-11-14 22:56:19 +02:00
void ModsPresetState : : setModActive ( const TModID & modID , bool isActive )
{
size_t dotPos = modID . find ( ' . ' ) ;
if ( dotPos ! = std : : string : : npos )
{
std : : string rootMod = modID . substr ( 0 , dotPos ) ;
std : : string settingID = modID . substr ( dotPos + 1 ) ;
setSettingActive ( rootMod , settingID , isActive ) ;
}
else
{
if ( isActive )
addRootMod ( modID ) ;
else
eraseRootMod ( modID ) ;
}
}
void ModsPresetState : : addRootMod ( const TModID & modName )
{
const std : : string & currentPresetName = modConfig [ " activePreset " ] . String ( ) ;
JsonNode & currentPreset = modConfig [ " presets " ] [ currentPresetName ] ;
if ( ! vstd : : contains ( currentPreset [ " mods " ] . Vector ( ) , JsonNode ( modName ) ) )
currentPreset [ " mods " ] . Vector ( ) . emplace_back ( modName ) ;
}
void ModsPresetState : : setSettingActive ( const TModID & modName , const TModID & settingName , bool isActive )
2024-11-11 13:19:14 +02:00
{
const std : : string & currentPresetName = modConfig [ " activePreset " ] . String ( ) ;
JsonNode & currentPreset = modConfig [ " presets " ] [ currentPresetName ] ;
currentPreset [ " settings " ] [ modName ] [ settingName ] . Bool ( ) = isActive ;
}
2024-11-26 15:31:32 +02:00
void ModsPresetState : : removeOldMods ( const TModList & modsToKeep )
{
const std : : string & currentPresetName = modConfig [ " activePreset " ] . String ( ) ;
JsonNode & currentPreset = modConfig [ " presets " ] [ currentPresetName ] ;
vstd : : erase_if ( currentPreset [ " mods " ] . Vector ( ) , [ & ] ( const JsonNode & entry ) {
return ! vstd : : contains ( modsToKeep , entry . String ( ) ) ;
} ) ;
vstd : : erase_if ( currentPreset [ " settings " ] . Struct ( ) , [ & ] ( const auto & entry ) {
return ! vstd : : contains ( modsToKeep , entry . first ) ;
} ) ;
}
2024-11-14 22:56:19 +02:00
void ModsPresetState : : eraseRootMod ( const TModID & modName )
2024-11-11 13:19:14 +02:00
{
2024-11-14 22:56:19 +02:00
const std : : string & currentPresetName = modConfig [ " activePreset " ] . String ( ) ;
JsonNode & currentPreset = modConfig [ " presets " ] [ currentPresetName ] ;
vstd : : erase ( currentPreset [ " mods " ] . Vector ( ) , JsonNode ( modName ) ) ;
2024-11-11 13:19:14 +02:00
}
2024-11-14 22:56:19 +02:00
void ModsPresetState : : eraseModSetting ( const TModID & modName , const TModID & settingName )
2024-11-11 13:19:14 +02:00
{
2024-11-14 22:56:19 +02:00
const std : : string & currentPresetName = modConfig [ " activePreset " ] . String ( ) ;
JsonNode & currentPreset = modConfig [ " presets " ] [ currentPresetName ] ;
currentPreset [ " settings " ] [ modName ] . Struct ( ) . erase ( modName ) ;
2024-11-11 13:19:14 +02:00
}
std : : vector < TModID > ModsPresetState : : getActiveMods ( ) const
{
TModList activeRootMods = getActiveRootMods ( ) ;
TModList allActiveMods ;
for ( const auto & activeMod : activeRootMods )
2024-11-09 22:29:07 +02:00
{
2024-11-20 22:44:24 +02:00
assert ( ! vstd : : contains ( allActiveMods , activeMod ) ) ;
2024-11-12 22:00:21 +02:00
allActiveMods . push_back ( activeMod ) ;
2024-11-09 22:29:07 +02:00
2024-11-11 13:19:14 +02:00
for ( const auto & submod : getModSettings ( activeMod ) )
2024-11-20 22:44:24 +02:00
{
2024-11-11 13:19:14 +02:00
if ( submod . second )
2024-11-20 22:44:24 +02:00
{
assert ( ! vstd : : contains ( allActiveMods , activeMod + ' . ' + submod . first ) ) ;
2024-11-11 13:19:14 +02:00
allActiveMods . push_back ( activeMod + ' . ' + submod . first ) ;
2024-11-20 22:44:24 +02:00
}
}
2024-11-09 22:29:07 +02:00
}
2024-11-11 13:19:14 +02:00
return allActiveMods ;
2024-11-09 22:29:07 +02:00
}
2024-11-14 17:41:22 +02:00
void ModsPresetState : : setValidatedChecksum ( const TModID & modName , std : : optional < uint32_t > value )
{
if ( value . has_value ( ) )
modConfig [ " validatedMods " ] [ modName ] . Integer ( ) = * value ;
else
modConfig [ " validatedMods " ] . Struct ( ) . erase ( modName ) ;
}
void ModsPresetState : : saveConfigurationState ( ) const
{
std : : fstream file ( CResourceHandler : : get ( ) - > getResourceName ( ResourcePath ( " config/modSettings.json " ) ) - > c_str ( ) , std : : ofstream : : out | std : : ofstream : : trunc ) ;
file < < modConfig . toCompactString ( ) ;
}
2024-11-13 19:25:59 +02:00
ModsStorage : : ModsStorage ( const std : : vector < TModID > & modsToLoad , const JsonNode & repositoryList )
2024-11-09 22:29:07 +02:00
{
JsonNode coreModConfig ( JsonPath : : builtin ( " config/gameConfig.json " ) ) ;
coreModConfig . setModScope ( ModScope : : scopeBuiltin ( ) ) ;
2024-11-13 19:25:59 +02:00
mods . try_emplace ( ModScope : : scopeBuiltin ( ) , ModScope : : scopeBuiltin ( ) , coreModConfig , JsonNode ( ) ) ;
2024-11-09 22:29:07 +02:00
for ( auto modID : modsToLoad )
{
if ( ModScope : : isScopeReserved ( modID ) )
continue ;
JsonNode modConfig ( getModDescriptionFile ( modID ) ) ;
modConfig . setModScope ( modID ) ;
if ( modConfig [ " modType " ] . isNull ( ) )
{
logMod - > error ( " Can not load mod %s - invalid mod config file! " , modID ) ;
continue ;
}
2024-11-13 19:25:59 +02:00
mods . try_emplace ( modID , modID , modConfig , repositoryList [ modID ] ) ;
}
for ( const auto & mod : repositoryList . Struct ( ) )
{
if ( vstd : : contains ( modsToLoad , mod . first ) )
continue ;
if ( mod . second [ " modType " ] . isNull ( ) | | mod . second [ " name " ] . isNull ( ) )
continue ;
mods . try_emplace ( mod . first , mod . first , JsonNode ( ) , mod . second ) ;
2024-11-09 22:29:07 +02:00
}
}
const ModDescription & ModsStorage : : getMod ( const TModID & fullID ) const
{
return mods . at ( fullID ) ;
}
2024-11-13 19:25:59 +02:00
TModList ModsStorage : : getAllMods ( ) const
{
TModList result ;
for ( const auto & mod : mods )
result . push_back ( mod . first ) ;
return result ;
}
2024-11-09 22:29:07 +02:00
ModManager : : ModManager ( )
2024-11-13 19:25:59 +02:00
: ModManager ( JsonNode ( ) )
2024-11-12 22:00:21 +02:00
{
}
2024-11-13 19:25:59 +02:00
ModManager : : ModManager ( const JsonNode & repositoryList )
2024-11-09 22:29:07 +02:00
: modsState ( std : : make_unique < ModsState > ( ) )
, modsPreset ( std : : make_unique < ModsPresetState > ( ) )
{
2024-11-14 17:41:22 +02:00
modsStorage = std : : make_unique < ModsStorage > ( modsState - > getInstalledMods ( ) , repositoryList ) ;
2024-11-12 22:00:21 +02:00
2024-11-11 13:19:14 +02:00
eraseMissingModsFromPreset ( ) ;
addNewModsToPreset ( ) ;
2024-11-09 22:29:07 +02:00
2024-11-11 13:19:14 +02:00
std : : vector < TModID > desiredModList = modsPreset - > getActiveMods ( ) ;
2024-11-24 19:15:41 +02:00
ModDependenciesResolver newResolver ( desiredModList , * modsStorage ) ;
updatePreset ( newResolver ) ;
2024-11-09 22:29:07 +02:00
}
ModManager : : ~ ModManager ( ) = default ;
const ModDescription & ModManager : : getModDescription ( const TModID & modID ) const
{
2024-11-13 19:25:59 +02:00
assert ( boost : : to_lower_copy ( modID ) = = modID ) ;
2024-11-09 22:29:07 +02:00
return modsStorage - > getMod ( modID ) ;
}
2024-11-21 23:11:02 +02:00
bool ModManager : : isModSettingActive ( const TModID & rootModID , const TModID & modSettingID ) const
{
return modsPreset - > getModSettings ( rootModID ) . at ( modSettingID ) ;
}
2024-11-09 22:29:07 +02:00
bool ModManager : : isModActive ( const TModID & modID ) const
{
2024-11-14 22:56:19 +02:00
return vstd : : contains ( getActiveMods ( ) , modID ) ;
2024-11-09 22:29:07 +02:00
}
const TModList & ModManager : : getActiveMods ( ) const
{
2024-11-14 22:56:19 +02:00
return depedencyResolver - > getActiveMods ( ) ;
2024-11-09 22:29:07 +02:00
}
2024-11-14 17:41:22 +02:00
uint32_t ModManager : : computeChecksum ( const TModID & modName ) const
{
return modsState - > computeChecksum ( modName ) ;
}
std : : optional < uint32_t > ModManager : : getValidatedChecksum ( const TModID & modName ) const
{
return modsPreset - > getValidatedChecksum ( modName ) ;
}
void ModManager : : setValidatedChecksum ( const TModID & modName , std : : optional < uint32_t > value )
{
modsPreset - > setValidatedChecksum ( modName , value ) ;
}
void ModManager : : saveConfigurationState ( ) const
{
modsPreset - > saveConfigurationState ( ) ;
}
2024-11-13 19:25:59 +02:00
TModList ModManager : : getAllMods ( ) const
2024-11-12 22:00:21 +02:00
{
2024-11-13 19:25:59 +02:00
return modsStorage - > getAllMods ( ) ;
2024-11-12 22:00:21 +02:00
}
2024-11-14 20:22:02 +02:00
double ModManager : : getInstalledModSizeMegabytes ( const TModID & modName ) const
{
return modsState - > getInstalledModSizeMegabytes ( modName ) ;
}
2024-11-11 13:19:14 +02:00
void ModManager : : eraseMissingModsFromPreset ( )
{
2024-11-14 17:41:22 +02:00
const TModList & installedMods = modsState - > getInstalledMods ( ) ;
2024-11-11 13:19:14 +02:00
const TModList & rootMods = modsPreset - > getActiveRootMods ( ) ;
2024-11-26 15:31:32 +02:00
modsPreset - > removeOldMods ( installedMods ) ;
2024-11-11 13:19:14 +02:00
for ( const auto & rootMod : rootMods )
{
const auto & modSettings = modsPreset - > getModSettings ( rootMod ) ;
for ( const auto & modSetting : modSettings )
{
TModID fullModID = rootMod + ' . ' + modSetting . first ;
if ( ! vstd : : contains ( installedMods , fullModID ) )
{
2024-11-14 22:56:19 +02:00
modsPreset - > eraseModSetting ( rootMod , modSetting . first ) ;
2024-11-11 13:19:14 +02:00
continue ;
}
}
}
}
void ModManager : : addNewModsToPreset ( )
{
2024-11-14 17:41:22 +02:00
const TModList & installedMods = modsState - > getInstalledMods ( ) ;
2024-11-11 13:19:14 +02:00
for ( const auto & modID : installedMods )
{
size_t dotPos = modID . find ( ' . ' ) ;
if ( dotPos = = std : : string : : npos )
continue ; // only look up submods aka mod settings
std : : string rootMod = modID . substr ( 0 , dotPos ) ;
std : : string settingID = modID . substr ( dotPos + 1 ) ;
const auto & modSettings = modsPreset - > getModSettings ( rootMod ) ;
if ( ! modSettings . count ( settingID ) )
2024-11-20 22:44:24 +02:00
modsPreset - > setSettingActive ( rootMod , settingID , ! modsStorage - > getMod ( modID ) . keepDisabled ( ) ) ;
2024-11-14 22:56:19 +02:00
}
}
TModList ModManager : : collectDependenciesRecursive ( const TModID & modID ) const
{
TModList result ;
TModList toTest ;
toTest . push_back ( modID ) ;
while ( ! toTest . empty ( ) )
{
2024-11-15 13:54:43 +02:00
TModID currentModID = toTest . back ( ) ;
const auto & currentMod = getModDescription ( currentModID ) ;
2024-11-14 22:56:19 +02:00
toTest . pop_back ( ) ;
2024-11-15 13:54:43 +02:00
result . push_back ( currentModID ) ;
2024-11-14 22:56:19 +02:00
2024-11-15 13:54:43 +02:00
if ( ! currentMod . isInstalled ( ) )
2024-11-20 17:24:52 +02:00
throw std : : runtime_error ( " Unable to enable mod " + modID + " ! Dependency " + currentModID + " is not installed! " ) ;
2024-11-15 13:54:43 +02:00
for ( const auto & dependency : currentMod . getDependencies ( ) )
2024-11-14 22:56:19 +02:00
{
if ( ! vstd : : contains ( result , dependency ) )
toTest . push_back ( dependency ) ;
}
}
return result ;
}
2024-11-20 00:15:05 +02:00
void ModManager : : tryEnableMods ( const TModList & modList )
2024-11-14 22:56:19 +02:00
{
2024-11-20 00:15:05 +02:00
TModList requiredActiveMods ;
TModList additionalActiveMods = getActiveMods ( ) ;
2024-11-14 22:56:19 +02:00
2024-11-20 00:15:05 +02:00
for ( const auto & modName : modList )
{
for ( const auto & dependency : collectDependenciesRecursive ( modName ) )
2024-11-20 22:44:24 +02:00
{
2024-11-20 00:15:05 +02:00
if ( ! vstd : : contains ( requiredActiveMods , dependency ) )
2024-11-20 22:44:24 +02:00
{
2024-11-20 00:15:05 +02:00
requiredActiveMods . push_back ( dependency ) ;
2024-11-20 22:44:24 +02:00
vstd : : erase ( additionalActiveMods , dependency ) ;
}
}
2024-11-20 00:15:05 +02:00
assert ( ! vstd : : contains ( additionalActiveMods , modName ) ) ;
assert ( vstd : : contains ( requiredActiveMods , modName ) ) ; // FIXME: fails on attempt to enable broken mod / translation to other language
}
2024-11-14 22:56:19 +02:00
ModDependenciesResolver testResolver ( requiredActiveMods , * modsStorage ) ;
testResolver . tryAddMods ( additionalActiveMods , * modsStorage ) ;
2024-11-26 15:31:32 +02:00
TModList additionalActiveSubmods ;
for ( const auto & modName : modList )
{
if ( modName . find ( ' . ' ) ! = std : : string : : npos )
continue ;
auto modSettings = modsPreset - > getModSettings ( modName ) ;
for ( const auto & entry : modSettings )
{
TModID fullModID = modName + ' . ' + entry . first ;
if ( entry . second & & ! vstd : : contains ( requiredActiveMods , fullModID ) )
additionalActiveSubmods . push_back ( fullModID ) ;
}
}
testResolver . tryAddMods ( additionalActiveSubmods , * modsStorage ) ;
2024-11-20 00:15:05 +02:00
for ( const auto & modName : modList )
if ( ! vstd : : contains ( testResolver . getActiveMods ( ) , modName ) )
2024-11-20 17:24:52 +02:00
throw std : : runtime_error ( " Failed to enable mod! Mod " + modName + " remains disabled! " ) ;
2024-11-14 22:56:19 +02:00
updatePreset ( testResolver ) ;
}
void ModManager : : tryDisableMod ( const TModID & modName )
{
auto desiredActiveMods = getActiveMods ( ) ;
assert ( vstd : : contains ( desiredActiveMods , modName ) ) ;
vstd : : erase ( desiredActiveMods , modName ) ;
ModDependenciesResolver testResolver ( desiredActiveMods , * modsStorage ) ;
if ( vstd : : contains ( testResolver . getActiveMods ( ) , modName ) )
2024-11-20 17:24:52 +02:00
throw std : : runtime_error ( " Failed to disable mod! Mod " + modName + " remains enabled! " ) ;
2024-11-14 22:56:19 +02:00
2024-11-20 22:44:24 +02:00
modsPreset - > setModActive ( modName , false ) ;
2024-11-14 22:56:19 +02:00
updatePreset ( testResolver ) ;
}
void ModManager : : updatePreset ( const ModDependenciesResolver & testResolver )
{
const auto & newActiveMods = testResolver . getActiveMods ( ) ;
const auto & newBrokenMods = testResolver . getBrokenMods ( ) ;
for ( const auto & modID : newActiveMods )
2024-11-15 13:54:43 +02:00
{
assert ( vstd : : contains ( modsState - > getInstalledMods ( ) , modID ) ) ;
2024-11-14 22:56:19 +02:00
modsPreset - > setModActive ( modID , true ) ;
2024-11-15 13:54:43 +02:00
}
2024-11-14 22:56:19 +02:00
for ( const auto & modID : newBrokenMods )
2024-11-20 22:44:24 +02:00
{
const auto & mod = getModDescription ( modID ) ;
if ( vstd : : contains ( newActiveMods , mod . getTopParentID ( ) ) )
modsPreset - > setModActive ( modID , false ) ;
}
2024-11-14 22:56:19 +02:00
std : : vector < TModID > desiredModList = modsPreset - > getActiveMods ( ) ;
2024-11-24 19:15:41 +02:00
// Try to enable all existing compatibility patches. Ignore on failure
for ( const auto & rootMod : modsPreset - > getActiveRootMods ( ) )
{
for ( const auto & modSetting : modsPreset - > getModSettings ( rootMod ) )
{
if ( modSetting . second )
continue ;
2024-11-15 15:25:36 +02:00
2024-11-24 19:15:41 +02:00
TModID fullModID = rootMod + ' . ' + modSetting . first ;
const auto & modDescription = modsStorage - > getMod ( fullModID ) ;
if ( modDescription . isCompatibility ( ) )
desiredModList . push_back ( fullModID ) ;
}
}
depedencyResolver = std : : make_unique < ModDependenciesResolver > ( desiredModList , * modsStorage ) ;
2024-11-14 22:56:19 +02:00
modsPreset - > saveConfigurationState ( ) ;
}
ModDependenciesResolver : : ModDependenciesResolver ( const TModList & modsToResolve , const ModsStorage & storage )
{
tryAddMods ( modsToResolve , storage ) ;
}
const TModList & ModDependenciesResolver : : getActiveMods ( ) const
{
return activeMods ;
}
const TModList & ModDependenciesResolver : : getBrokenMods ( ) const
{
return brokenMods ;
2024-11-11 13:19:14 +02:00
}
2024-11-14 22:56:19 +02:00
void ModDependenciesResolver : : tryAddMods ( TModList modsToResolve , const ModsStorage & storage )
2024-11-09 22:29:07 +02:00
{
// Topological sort algorithm.
boost : : range : : sort ( modsToResolve ) ; // Sort mods per name
2024-11-14 22:56:19 +02:00
std : : vector < TModID > sortedValidMods ( activeMods . begin ( ) , activeMods . end ( ) ) ; // Vector keeps order of elements (LIFO)
std : : set < TModID > resolvedModIDs ( activeMods . begin ( ) , activeMods . end ( ) ) ; // Use a set for validation for performance reason, but set does not keep order of elements
2024-11-09 22:29:07 +02:00
std : : set < TModID > notResolvedModIDs ( modsToResolve . begin ( ) , modsToResolve . end ( ) ) ; // Use a set for validation for performance reason
// Mod is resolved if it has no dependencies or all its dependencies are already resolved
auto isResolved = [ & ] ( const ModDescription & mod ) - > bool
{
2024-11-11 13:19:14 +02:00
if ( mod . isTranslation ( ) & & CGeneralTextHandler : : getPreferredLanguage ( ) ! = mod . getBaseLanguage ( ) )
return false ;
2024-11-09 22:29:07 +02:00
if ( mod . getDependencies ( ) . size ( ) > resolvedModIDs . size ( ) )
return false ;
for ( const TModID & dependency : mod . getDependencies ( ) )
if ( ! vstd : : contains ( resolvedModIDs , dependency ) )
return false ;
for ( const TModID & softDependency : mod . getSoftDependencies ( ) )
if ( vstd : : contains ( notResolvedModIDs , softDependency ) )
return false ;
for ( const TModID & conflict : mod . getConflicts ( ) )
if ( vstd : : contains ( resolvedModIDs , conflict ) )
return false ;
for ( const TModID & reverseConflict : resolvedModIDs )
2024-11-14 22:56:19 +02:00
if ( vstd : : contains ( storage . getMod ( reverseConflict ) . getConflicts ( ) , mod . getID ( ) ) )
2024-11-09 22:29:07 +02:00
return false ;
return true ;
} ;
while ( true )
{
std : : set < TModID > resolvedOnCurrentTreeLevel ;
for ( auto it = modsToResolve . begin ( ) ; it ! = modsToResolve . end ( ) ; ) // One iteration - one level of mods tree
{
2024-11-14 22:56:19 +02:00
if ( isResolved ( storage . getMod ( * it ) ) )
2024-11-09 22:29:07 +02:00
{
resolvedOnCurrentTreeLevel . insert ( * it ) ; // Not to the resolvedModIDs, so current node children will be resolved on the next iteration
2024-11-20 22:44:24 +02:00
assert ( ! vstd : : contains ( sortedValidMods , * it ) ) ;
2024-11-09 22:29:07 +02:00
sortedValidMods . push_back ( * it ) ;
it = modsToResolve . erase ( it ) ;
continue ;
}
it + + ;
}
if ( ! resolvedOnCurrentTreeLevel . empty ( ) )
{
resolvedModIDs . insert ( resolvedOnCurrentTreeLevel . begin ( ) , resolvedOnCurrentTreeLevel . end ( ) ) ;
for ( const auto & it : resolvedOnCurrentTreeLevel )
notResolvedModIDs . erase ( it ) ;
continue ;
}
// If there are no valid mods on the current mods tree level, no more mod can be resolved, should be ended.
break ;
}
2024-11-12 22:00:21 +02:00
assert ( ! sortedValidMods . empty ( ) ) ;
2024-11-09 22:29:07 +02:00
activeMods = sortedValidMods ;
2024-11-14 22:56:19 +02:00
brokenMods . insert ( brokenMods . end ( ) , modsToResolve . begin ( ) , modsToResolve . end ( ) ) ;
2024-11-09 22:29:07 +02:00
}
VCMI_LIB_NAMESPACE_END