2023-07-30 19:12:25 +02:00
/*
* ContentTypeHandler . 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 "ContentTypeHandler.h"
# include "CModHandler.h"
2024-11-09 22:29:07 +02:00
# include "ModDescription.h"
# include "ModManager.h"
2023-07-30 19:12:25 +02:00
# include "ModScope.h"
# include "../BattleFieldHandler.h"
# include "../CArtHandler.h"
# include "../CCreatureHandler.h"
2024-10-06 21:21:18 +02:00
# include "../CConfigHandler.h"
2024-07-21 12:49:40 +02:00
# include "../entities/faction/CTownHandler.h"
2024-10-11 18:30:16 +02:00
# include "../entities/hero/CHeroClassHandler.h"
# include "../entities/hero/CHeroHandler.h"
2024-07-20 14:55:17 +02:00
# include "../texts/CGeneralTextHandler.h"
2023-07-30 19:12:25 +02:00
# include "../CSkillHandler.h"
# include "../CStopWatch.h"
2024-08-31 13:00:36 +02:00
# include "../IGameSettings.h"
2023-07-30 19:12:25 +02:00
# include "../IHandlerBase.h"
# include "../ObstacleHandler.h"
2024-04-08 12:50:41 +02:00
# include "../mapObjects/ObstacleSetHandler.h"
2023-07-30 19:12:25 +02:00
# include "../RiverHandler.h"
# include "../RoadHandler.h"
# include "../ScriptHandler.h"
2023-08-19 23:22:31 +02:00
# include "../constants/StringConstants.h"
2023-07-30 19:12:25 +02:00
# include "../TerrainHandler.h"
2024-02-11 23:09:01 +02:00
# include "../json/JsonUtils.h"
2023-07-30 19:12:25 +02:00
# include "../mapObjectConstructors/CObjectClassesHandler.h"
# include "../rmg/CRmgTemplateStorage.h"
# include "../spells/CSpellHandler.h"
2024-05-17 00:05:51 +02:00
# include "../VCMI_Lib.h"
2023-07-30 19:12:25 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2024-10-06 17:54:30 +02:00
ContentTypeHandler : : ContentTypeHandler ( IHandlerBase * handler , const std : : string & entityName ) :
2023-07-30 19:12:25 +02:00
handler ( handler ) ,
2024-10-06 17:54:30 +02:00
entityName ( entityName ) ,
2023-07-30 19:12:25 +02:00
originalData ( handler - > loadLegacyData ( ) )
{
for ( auto & node : originalData )
{
2024-02-13 14:34:16 +02:00
node . setModScope ( ModScope : : scopeBuiltin ( ) ) ;
2023-07-30 19:12:25 +02:00
}
}
2024-09-30 21:26:22 +02:00
bool ContentTypeHandler : : preloadModData ( const std : : string & modName , const JsonNode & fileList , bool validate )
2023-07-30 19:12:25 +02:00
{
2024-11-14 17:41:22 +02:00
bool result = true ;
2023-07-30 19:12:25 +02:00
JsonNode data = JsonUtils : : assembleFromFiles ( fileList , result ) ;
2024-02-13 14:34:16 +02:00
data . setModScope ( modName ) ;
2023-07-30 19:12:25 +02:00
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
2023-10-21 18:48:07 +02:00
std : : swap ( modInfo . modData [ entry . first ] , entry . second ) ;
2023-07-30 19:12:25 +02:00
}
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 )
logMod - > warn ( " Redundant namespace definition for %s " , objectName ) ;
logMod - > trace ( " Patching object %s (%s) from %s " , objectName , remoteName , modName ) ;
JsonNode & remoteConf = modData [ remoteName ] . patches [ objectName ] ;
2024-10-06 21:21:18 +02:00
if ( ! remoteConf . isNull ( ) & & settings [ " mods " ] [ " validation " ] . String ( ) ! = " off " )
2024-10-06 17:54:30 +02:00
JsonUtils : : detectConflicts ( conflictList , remoteConf , entry . second , objectName ) ;
2024-10-02 20:58:03 +02:00
2023-07-30 19:12:25 +02:00
JsonUtils : : merge ( remoteConf , entry . second ) ;
}
}
return result ;
}
bool ContentTypeHandler : : loadMod ( const 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 )
2024-10-06 17:54:30 +02:00
result & = JsonUtils : : validate ( data , " vcmi: " + entityName , name ) ;
2023-07-30 19:12:25 +02:00
} ;
// 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 ;
2024-02-13 14:34:16 +02:00
if ( data . getModScope ( ) ! = modName )
2023-10-26 14:32:46 +02:00
{
// in this scenario, entire object record comes from another mod
// normally, this is used to "patch" object from another mod (which is legal)
// however in this case there is no object to patch. This might happen in such cases:
// - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases)
// - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data
// so emit warning and skip such case
2024-10-06 17:54:30 +02:00
logMod - > warn ( " Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist! " , data . getModScope ( ) , name , entityName , modName ) ;
2023-10-26 14:32:46 +02:00
continue ;
}
2023-07-30 19:12:25 +02:00
2024-01-04 23:52:01 +02:00
bool hasIndex = vstd : : contains ( data . Struct ( ) , " index " ) & & ! data [ " index " ] . isNull ( ) ;
if ( hasIndex & & modName ! = " core " )
logMod - > error ( " Mod %s is attempting to load original data! This option is reserved for built-in mod. " , modName ) ;
2023-07-30 19:12:25 +02:00
2024-01-04 23:52:01 +02:00
if ( hasIndex & & modName = = " core " )
{
2023-07-30 19:12:25 +02:00
// try to add H3 object data
size_t index = static_cast < size_t > ( data [ " index " ] . Float ( ) ) ;
if ( originalData . size ( ) > index )
{
logMod - > trace ( " found original data in loadMod(%s) at index %d " , name , index ) ;
JsonUtils : : merge ( originalData [ index ] , data ) ;
std : : swap ( originalData [ index ] , data ) ;
originalData [ index ] . clear ( ) ; // do not use same data twice (same ID)
}
else
{
logMod - > trace ( " no original data in loadMod(%s) at index %d " , name , index ) ;
}
performValidate ( data , name ) ;
handler - > loadObject ( modName , name , data , index ) ;
}
else
{
// normal new object
logMod - > trace ( " no index in loadMod(%s) " , name ) ;
performValidate ( data , name ) ;
handler - > loadObject ( modName , name , data ) ;
}
}
return result ;
}
void ContentTypeHandler : : loadCustom ( )
{
handler - > loadCustom ( ) ;
}
void ContentTypeHandler : : afterLoadFinalization ( )
{
2024-10-06 21:21:18 +02:00
if ( settings [ " mods " ] [ " validation " ] . String ( ) ! = " off " )
2024-01-07 15:14:07 +02:00
{
2024-10-06 21:21:18 +02:00
for ( auto const & data : modData )
2024-01-07 15:14:07 +02:00
{
2024-10-06 21:21:18 +02:00
if ( data . second . modData . isNull ( ) )
{
for ( auto node : data . second . patches . Struct ( ) )
logMod - > warn ( " Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects. " , node . second . getModScope ( ) , node . first , data . first ) ;
}
2024-01-07 15:14:07 +02:00
2024-10-06 21:21:18 +02:00
for ( auto & otherMod : modData )
{
if ( otherMod . first = = data . first )
continue ;
2024-01-07 15:14:07 +02:00
2024-10-06 21:21:18 +02:00
if ( otherMod . second . modData . isNull ( ) )
continue ;
2024-01-07 15:14:07 +02:00
2024-10-06 21:21:18 +02:00
for ( auto & otherObject : otherMod . second . modData . Struct ( ) )
2024-01-07 15:14:07 +02:00
{
2024-10-06 21:21:18 +02:00
if ( data . second . modData . Struct ( ) . count ( otherObject . first ) )
{
logMod - > warn ( " Mod '%s' have added object with name '%s' that is also available in mod '%s' " , data . first , otherObject . first , otherMod . first ) ;
logMod - > warn ( " Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead " , otherMod . first , otherObject . first , data . first ) ;
}
2024-01-07 15:14:07 +02:00
}
}
}
2024-10-06 21:21:18 +02:00
for ( const auto & [ conflictPath , conflictModData ] : conflictList . Struct ( ) )
2024-01-07 15:14:07 +02:00
{
2024-10-06 21:21:18 +02:00
std : : set < std : : string > conflictingMods ;
std : : set < std : : string > resolvedConflicts ;
2024-10-06 17:54:30 +02:00
2024-10-10 22:31:11 +02:00
for ( auto const & conflictModEntry : conflictModData . Struct ( ) )
conflictingMods . insert ( conflictModEntry . first ) ;
2024-10-06 17:54:30 +02:00
2024-10-28 18:35:03 +02:00
for ( auto const & modID : conflictingMods )
{
2024-10-06 21:21:18 +02:00
resolvedConflicts . merge ( VLC - > modh - > getModDependencies ( modID ) ) ;
2024-10-25 17:21:32 +02:00
resolvedConflicts . merge ( VLC - > modh - > getModEnabledSoftDependencies ( modID ) ) ;
}
2024-01-07 15:14:07 +02:00
2024-10-06 21:21:18 +02:00
vstd : : erase_if ( conflictingMods , [ & resolvedConflicts ] ( const std : : string & entry ) { return resolvedConflicts . count ( entry ) ; } ) ;
2024-01-07 15:14:07 +02:00
2024-10-06 21:21:18 +02:00
if ( conflictingMods . size ( ) < 2 )
continue ; // all conflicts were resolved - either via compatibility patch (mod that depends on 2 conflicting mods) or simple mod that depends on another one
2024-10-06 17:54:30 +02:00
2024-10-06 21:21:18 +02:00
bool allEqual = true ;
2024-10-06 17:54:30 +02:00
2024-10-06 21:21:18 +02:00
for ( auto const & modID : conflictingMods )
2024-01-07 15:14:07 +02:00
{
2024-10-06 21:21:18 +02:00
if ( conflictModData [ modID ] ! = conflictModData [ * conflictingMods . begin ( ) ] )
2024-01-07 15:14:07 +02:00
{
2024-10-06 21:21:18 +02:00
allEqual = false ;
break ;
2024-01-07 15:14:07 +02:00
}
}
2024-10-06 17:54:30 +02:00
2024-10-06 21:21:18 +02:00
if ( allEqual )
continue ; // conflict still present, but all mods use the same value for conflicting entry - permit it
2024-10-06 17:54:30 +02:00
2024-10-06 21:21:18 +02:00
logMod - > warn ( " Potential confict in '%s' " , conflictPath ) ;
2024-10-06 17:54:30 +02:00
2024-10-06 21:21:18 +02:00
for ( auto const & modID : conflictingMods )
logMod - > warn ( " Mod '%s' - value set to %s " , modID , conflictModData [ modID ] . toCompactString ( ) ) ;
2024-01-07 15:14:07 +02:00
}
}
2023-07-30 19:12:25 +02:00
handler - > afterLoadFinalization ( ) ;
}
void CContentHandler : : init ( )
{
2023-12-31 23:43:35 +02:00
handlers . insert ( std : : make_pair ( " heroClasses " , ContentTypeHandler ( VLC - > heroclassesh . get ( ) , " heroClass " ) ) ) ;
handlers . insert ( std : : make_pair ( " artifacts " , ContentTypeHandler ( VLC - > arth . get ( ) , " artifact " ) ) ) ;
handlers . insert ( std : : make_pair ( " creatures " , ContentTypeHandler ( VLC - > creh . get ( ) , " creature " ) ) ) ;
handlers . insert ( std : : make_pair ( " factions " , ContentTypeHandler ( VLC - > townh . get ( ) , " faction " ) ) ) ;
handlers . insert ( std : : make_pair ( " objects " , ContentTypeHandler ( VLC - > objtypeh . get ( ) , " object " ) ) ) ;
handlers . insert ( std : : make_pair ( " heroes " , ContentTypeHandler ( VLC - > heroh . get ( ) , " hero " ) ) ) ;
handlers . insert ( std : : make_pair ( " spells " , ContentTypeHandler ( VLC - > spellh . get ( ) , " spell " ) ) ) ;
handlers . insert ( std : : make_pair ( " skills " , ContentTypeHandler ( VLC - > skillh . get ( ) , " skill " ) ) ) ;
handlers . insert ( std : : make_pair ( " templates " , ContentTypeHandler ( VLC - > tplh . get ( ) , " template " ) ) ) ;
2023-07-30 19:12:25 +02:00
# if SCRIPTING_ENABLED
2024-01-10 21:30:12 +02:00
handlers . insert ( std : : make_pair ( " scripts " , ContentTypeHandler ( VLC - > scriptHandler . get ( ) , " script " ) ) ) ;
2023-07-30 19:12:25 +02:00
# endif
2023-12-31 23:43:35 +02:00
handlers . insert ( std : : make_pair ( " battlefields " , ContentTypeHandler ( VLC - > battlefieldsHandler . get ( ) , " battlefield " ) ) ) ;
handlers . insert ( std : : make_pair ( " terrains " , ContentTypeHandler ( VLC - > terrainTypeHandler . get ( ) , " terrain " ) ) ) ;
handlers . insert ( std : : make_pair ( " rivers " , ContentTypeHandler ( VLC - > riverTypeHandler . get ( ) , " river " ) ) ) ;
handlers . insert ( std : : make_pair ( " roads " , ContentTypeHandler ( VLC - > roadTypeHandler . get ( ) , " road " ) ) ) ;
handlers . insert ( std : : make_pair ( " obstacles " , ContentTypeHandler ( VLC - > obstacleHandler . get ( ) , " obstacle " ) ) ) ;
2024-04-08 12:50:41 +02:00
handlers . insert ( std : : make_pair ( " biomes " , ContentTypeHandler ( VLC - > biomeHandler . get ( ) , " biome " ) ) ) ;
2023-07-30 19:12:25 +02:00
}
2024-11-14 17:41:22 +02:00
bool CContentHandler : : preloadData ( const ModDescription & mod , bool validate )
2023-07-30 19:12:25 +02:00
{
bool result = true ;
2024-11-14 17:41:22 +02:00
if ( validate & & mod . getID ( ) ! = ModScope : : scopeBuiltin ( ) ) // TODO: remove workaround
{
if ( ! JsonUtils : : validate ( mod . getLocalConfig ( ) , " vcmi:mod " , mod . getID ( ) ) )
result = false ;
}
2023-07-30 19:12:25 +02:00
for ( auto & handler : handlers )
{
2024-11-14 17:41:22 +02:00
result & = handler . second . preloadModData ( mod . getID ( ) , mod . getLocalValue ( handler . first ) , validate ) ;
2023-07-30 19:12:25 +02:00
}
return result ;
}
2024-11-14 17:41:22 +02:00
bool CContentHandler : : load ( const ModDescription & mod , bool validate )
2023-07-30 19:12:25 +02:00
{
bool result = true ;
for ( auto & handler : handlers )
{
2024-11-14 17:41:22 +02:00
result & = handler . second . loadMod ( mod . getID ( ) , validate ) ;
2023-07-30 19:12:25 +02:00
}
return result ;
}
void CContentHandler : : loadCustom ( )
{
for ( auto & handler : handlers )
{
handler . second . loadCustom ( ) ;
}
}
void CContentHandler : : afterLoadFinalization ( )
{
for ( auto & handler : handlers )
{
handler . second . afterLoadFinalization ( ) ;
}
}
const ContentTypeHandler & CContentHandler : : operator [ ] ( const std : : string & name ) const
{
return handlers . at ( name ) ;
}
VCMI_LIB_NAMESPACE_END