2022-05-06 23:48:24 +02:00
package com.falsepattern.lib.config ;
2022-05-06 23:53:11 +02:00
import com.falsepattern.lib.StableAPI ;
2022-05-06 23:58:01 +02:00
import com.falsepattern.lib.internal.CoreLoadingPlugin ;
2022-05-07 12:40:35 +02:00
import com.falsepattern.lib.internal.FalsePatternLib ;
2022-05-07 16:43:34 +02:00
import cpw.mods.fml.client.config.IConfigElement ;
2022-05-06 23:48:24 +02:00
import cpw.mods.fml.client.event.ConfigChangedEvent ;
2022-05-07 16:43:34 +02:00
import cpw.mods.fml.common.FMLCommonHandler ;
2022-05-06 23:48:24 +02:00
import cpw.mods.fml.common.eventhandler.SubscribeEvent ;
2022-05-07 11:45:57 +02:00
import lombok.* ;
2022-05-06 23:48:24 +02:00
import net.minecraftforge.common.MinecraftForge ;
2022-05-07 16:43:34 +02:00
import net.minecraftforge.common.config.ConfigElement ;
2022-05-06 23:48:24 +02:00
import net.minecraftforge.common.config.Configuration ;
2022-05-07 11:45:57 +02:00
import java.lang.reflect.Field ;
import java.lang.reflect.InvocationTargetException ;
2022-05-06 23:48:24 +02:00
import java.nio.file.Path ;
import java.util.* ;
2022-05-07 01:33:04 +02:00
import java.util.regex.Pattern ;
2022-05-07 11:45:57 +02:00
import java.util.stream.Collectors ;
2022-05-06 23:48:24 +02:00
/**
* Class for controlling the loading of configuration files.
*/
@NoArgsConstructor ( access = AccessLevel . PRIVATE )
2022-05-06 23:53:11 +02:00
@StableAPI ( since = "0.6.0" )
2022-05-06 23:48:24 +02:00
public class ConfigurationManager {
2022-05-07 16:43:34 +02:00
private static final Map < String , Configuration > configs = new HashMap <> ();
private static final Map < Configuration , Set < Class <?>>> configToClassMap = new HashMap <> ();
2022-05-06 23:48:24 +02:00
private static final ConfigurationManager instance = new ConfigurationManager ();
private static boolean initialized = false ;
private static Path configDir ;
/**
2022-05-07 16:43:34 +02:00
* Registers a configuration class to be loaded. This should be done in preInit.
* @param configClass The class to register.
2022-05-06 23:48:24 +02:00
*/
2022-05-07 16:43:34 +02:00
public static void registerConfig ( Class <?> configClass ) throws ConfigException {
val cfg = Optional
. ofNullable ( configClass . getAnnotation ( Config . class ))
. orElseThrow (() -> new ConfigException ( "Class " + configClass . getName () + " does not have a @Config annotation!" ));
val category = Optional
. of ( cfg . category (). trim ())
. map (( cat ) -> cat . length () == 0 ? null : cat )
. orElseThrow (() -> new ConfigException ( "Config class " + configClass . getName () + " has an empty category!" ));
val rawConfig = configs . computeIfAbsent ( cfg . modid (), ( ignored ) -> {
val c = new Configuration ( configDir . resolve ( cfg . modid () + ".cfg" ). toFile ());
c . load ();
return c ;
});
configToClassMap . computeIfAbsent ( rawConfig , ( ignored ) -> new HashSet <> ()). add ( configClass );
2022-05-07 11:45:57 +02:00
try {
2022-05-07 16:43:34 +02:00
processConfigInternal ( configClass , category , rawConfig );
rawConfig . save ();
2022-05-07 11:45:57 +02:00
} catch ( IllegalAccessException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException e ) {
throw new ConfigException ( e );
}
2022-05-06 23:48:24 +02:00
}
2022-05-07 16:43:34 +02:00
/**
* Process the configuration into a list of config elements usable in config GUI code.
* @param configClass The class to process.
* @return The configuration elements.
*/
public static List <? extends IConfigElement <?>> getConfigElements ( Class <?> configClass ) throws ConfigException {
val cfg = Optional
. ofNullable ( configClass . getAnnotation ( Config . class ))
. orElseThrow (() -> new ConfigException ( "Class " + configClass . getName () + " does not have a @Config annotation!" ));
val rawConfig = Optional
. ofNullable ( configs . get ( cfg . modid ()))
. map (( conf ) -> Optional . ofNullable ( configToClassMap . get ( conf ))
. map (( l ) -> l . contains ( configClass ))
. orElse ( false ) ? conf : null )
. orElseThrow (() -> new ConfigException ( "Tried to get config elements for non-registed config class!" ));
val category = cfg . category ();
val elements = new ConfigElement <> ( rawConfig . getCategory ( category )). getChildElements ();
return elements . stream (). map (( element ) -> new IConfigElementProxy <> (( IConfigElement <?> ) element , () -> {
try {
processConfigInternal ( configClass , category , rawConfig );
rawConfig . save ();
} catch ( IllegalAccessException | NoSuchMethodException | InvocationTargetException |
NoSuchFieldException | ConfigException e ) {
e . printStackTrace ();
}
})). collect ( Collectors . toList ());
}
2022-05-06 23:48:24 +02:00
/**
* Internal, do not use.
*/
public static void init () {
if ( initialized ) return ;
2022-05-06 23:58:01 +02:00
configDir = CoreLoadingPlugin . mcDir . toPath (). resolve ( "config" );
2022-05-07 16:43:34 +02:00
FMLCommonHandler . instance (). bus (). register ( instance );
2022-05-06 23:48:24 +02:00
initialized = true ;
}
/**
* Internal, do not use.
* @param event The event.
*/
@SubscribeEvent
public void onConfigChanged ( ConfigChangedEvent . OnConfigChangedEvent event ) {
2022-05-07 16:43:34 +02:00
val config = configs . get ( event . modID );
if ( config == null )
2022-05-06 23:48:24 +02:00
return ;
2022-05-07 16:43:34 +02:00
val configClasses = configToClassMap . get ( config );
configClasses . forEach (( configClass ) -> {
2022-05-06 23:48:24 +02:00
try {
2022-05-07 16:43:34 +02:00
val category = Optional . ofNullable ( configClass . getAnnotation ( Config . class )). map ( Config :: category ). orElseThrow (() -> new ConfigException ( "Failed to get config category for class " + configClass . getName ()));
processConfigInternal ( configClass , category , config );
2022-05-07 11:54:44 +02:00
} catch ( Exception e ) {
2022-05-07 16:43:34 +02:00
e . printStackTrace ();
2022-05-06 23:48:24 +02:00
}
});
}
2022-05-07 16:43:34 +02:00
private static void processConfigInternal ( Class <?> configClass , String category , Configuration rawConfig ) throws IllegalAccessException , NoSuchMethodException , InvocationTargetException , NoSuchFieldException , ConfigException {
2022-05-06 23:48:24 +02:00
val cat = rawConfig . getCategory ( category );
for ( val field : configClass . getDeclaredFields ()) {
if ( field . getAnnotation ( Config . Ignore . class ) != null ) continue ;
field . setAccessible ( true );
2022-05-07 01:14:55 +02:00
val comment = Optional . ofNullable ( field . getAnnotation ( Config . Comment . class )). map ( Config . Comment :: value ). map (( lines ) -> String . join ( "\n" , lines )). orElse ( "" );
val name = Optional . ofNullable ( field . getAnnotation ( Config . Name . class )). map ( Config . Name :: value ). orElse ( field . getName ());
val langKey = Optional . ofNullable ( field . getAnnotation ( Config . LangKey . class )). map ( Config . LangKey :: value ). orElse ( name );
2022-05-07 12:16:05 +02:00
val fieldClass = field . getType ();
2022-05-06 23:48:24 +02:00
var boxed = false ;
2022-05-07 12:16:05 +02:00
if (( boxed = fieldClass . equals ( Boolean . class )) || fieldClass . equals ( boolean . class )) {
2022-05-07 01:33:04 +02:00
val defaultValue = Optional . ofNullable ( field . getAnnotation ( Config . DefaultBoolean . class )). map ( Config . DefaultBoolean :: value ). orElse ( boxed ? ( Boolean ) field . get ( null ) : field . getBoolean ( null ));
field . setBoolean ( null , rawConfig . getBoolean ( name , category , defaultValue , comment , langKey ));
2022-05-07 12:16:05 +02:00
} else if (( boxed = fieldClass . equals ( Integer . class )) || fieldClass . equals ( int . class )) {
2022-05-07 01:14:55 +02:00
val range = Optional . ofNullable ( field . getAnnotation ( Config . RangeInt . class ));
2022-05-07 01:33:04 +02:00
val min = range . map ( Config . RangeInt :: min ). orElse ( Integer . MIN_VALUE );
val max = range . map ( Config . RangeInt :: max ). orElse ( Integer . MAX_VALUE );
val defaultValue = Optional . ofNullable ( field . getAnnotation ( Config . DefaultInt . class )). map ( Config . DefaultInt :: value ). orElse ( boxed ? ( Integer ) field . get ( null ) : field . getInt ( null ));
field . setInt ( null , rawConfig . getInt ( name , category , defaultValue , min , max , comment , langKey ));
2022-05-07 12:16:05 +02:00
} else if (( boxed = fieldClass . equals ( Float . class )) || fieldClass . equals ( float . class )) {
2022-05-07 01:33:04 +02:00
val range = Optional . ofNullable ( field . getAnnotation ( Config . RangeFloat . class ));
val min = range . map ( Config . RangeFloat :: min ). orElse ( Float . MIN_VALUE );
val max = range . map ( Config . RangeFloat :: max ). orElse ( Float . MAX_VALUE );
val defaultValue = Optional . ofNullable ( field . getAnnotation ( Config . DefaultFloat . class )). map ( Config . DefaultFloat :: value ). orElse ( boxed ? ( Float ) field . get ( null ) : field . getFloat ( null ));
field . setDouble ( null , rawConfig . getFloat ( name , category , defaultValue , min , max , comment , langKey ));
2022-05-07 12:16:05 +02:00
} else if ( fieldClass . equals ( String . class )) {
2022-05-07 01:33:04 +02:00
val defaultValue = Optional . ofNullable ( field . getAnnotation ( Config . DefaultString . class )). map ( Config . DefaultString :: value ). orElse (( String ) field . get ( null ));
val pattern = Optional . ofNullable ( field . getAnnotation ( Config . Pattern . class )). map ( Config . Pattern :: value ). map ( Pattern :: compile ). orElse ( null );
field . set ( null , rawConfig . getString ( name , category , defaultValue , comment , langKey , pattern ));
2022-05-07 12:16:05 +02:00
} else if ( fieldClass . isEnum ()) {
val enumValues = Arrays . stream (( Object [] ) fieldClass . getDeclaredMethod ( "values" ). invoke ( null )). map (( obj ) -> ( Enum <?> ) obj ). collect ( Collectors . toList ());
2022-05-07 11:45:57 +02:00
val defaultValue = ( Enum <?> )
Optional . ofNullable ( field . getAnnotation ( Config . DefaultEnum . class ))
. map ( Config . DefaultEnum :: value )
2022-05-07 12:16:05 +02:00
. map (( defName ) -> extractField ( fieldClass , defName ))
2022-05-07 11:45:57 +02:00
. map ( ConfigurationManager :: extractValue )
. orElse ( field . get ( null ));
val possibleValues = enumValues . stream (). map ( Enum :: name ). toArray ( String [] :: new );
2022-05-07 12:40:35 +02:00
var value = rawConfig . getString ( name , category , defaultValue . name (), comment + "\nPossible values: " + Arrays . toString ( possibleValues ) + "\n" , possibleValues , langKey );
try {
if ( ! Arrays . asList ( possibleValues ). contains ( value )) {
throw new NoSuchFieldException ();
}
val enumField = fieldClass . getDeclaredField ( value );
if ( ! enumField . isEnumConstant ()) {
throw new NoSuchFieldException ();
}
field . set ( null , enumField . get ( null ));
} catch ( NoSuchFieldException e ) {
FalsePatternLib . getLog (). warn ( "Invalid value " + value + " for enum configuration field " + field . getName () + " of type " + fieldClass . getName () + " in config class " + configClass . getName () + "! Using default value of " + defaultValue + "!" );
field . set ( null , defaultValue );
}
2022-05-07 11:45:57 +02:00
} else {
2022-05-07 12:16:05 +02:00
throw new ConfigException ( "Illegal config field: " + field . getName () + " in " + configClass . getName () + ": Unsupported type " + fieldClass . getName () + "! Did you forget an @Ignore annotation?" );
2022-05-06 23:48:24 +02:00
}
if ( field . isAnnotationPresent ( Config . RequiresMcRestart . class )) {
cat . setRequiresMcRestart ( true );
}
if ( field . isAnnotationPresent ( Config . RequiresWorldRestart . class )) {
cat . setRequiresWorldRestart ( true );
}
}
}
2022-05-07 11:45:57 +02:00
@SneakyThrows
private static Field extractField ( Class <?> clazz , String field ) {
return clazz . getDeclaredField ( field );
}
@SneakyThrows
private static Object extractValue ( Field field ) {
return field . get ( null );
}
2022-05-06 23:48:24 +02:00
}