package com.anthonyhilyard.iceberg.compat.configured; import com.anthonyhilyard.iceberg.config.IcebergConfigSpec; import com.electronwill.nightconfig.core.AbstractConfig; import com.electronwill.nightconfig.core.CommentedConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import com.mrcrayfish.configured.Configured; import com.mrcrayfish.configured.api.ConfigType; import com.mrcrayfish.configured.api.IConfigEntry; import com.mrcrayfish.configured.api.IConfigValue; import com.mrcrayfish.configured.api.IModConfig; import com.mrcrayfish.configured.impl.forge.ForgeListValue; import com.mrcrayfish.configured.impl.forge.ForgeValue; import com.mrcrayfish.configured.util.ConfigHelper; import net.minecraft.Util; import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.config.ModConfigEvent; import java.nio.file.Path; import java.util.EnumMap; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.compress.utils.Lists; public class IcebergConfigPlugin implements IModConfig { protected static final EnumMap TYPE_RESOLVER = Util.make(new EnumMap<>(ModConfig.Type.class), (map) -> { map.put(ModConfig.Type.CLIENT, ConfigType.CLIENT); map.put(ModConfig.Type.COMMON, ConfigType.UNIVERSAL); map.put(ModConfig.Type.SERVER, ConfigType.WORLD_SYNC); }); protected final ModConfig config; protected final List allConfigValues; public IcebergConfigPlugin(ModConfig config) { this.config = config; this.allConfigValues = getAllConfigValues(config); } @Override public void update(IConfigEntry entry) { Set> changedValues = ConfigHelper.getChangedValues(entry); if (!changedValues.isEmpty()) { CommentedConfig newConfig = CommentedConfig.copy(this.config.getConfigData()); changedValues.forEach(value -> { if (value instanceof ForgeValue forge) { if (forge instanceof ForgeListValue forgeList) { Function, List> converter = forgeList.getConverter(); if (converter != null) { newConfig.set(forge.configValue.getPath(), converter.apply(forgeList.get())); return; } } newConfig.set(forge.configValue.getPath(), value.get()); } }); this.config.getConfigData().putAll(newConfig); } if (this.getType() == ConfigType.WORLD_SYNC) { if (!ConfigHelper.isPlayingGame()) { // Unload server configs since still in main menu this.config.getHandler().unload(this.config.getFullPath().getParent(), this.config); ConfigHelper.setForgeConfigData(this.config, null); } } else if (!changedValues.isEmpty()) { Configured.LOGGER.info("Sending config reloading event for {}", this.config.getFileName()); this.config.getSpec().afterReload(); ConfigHelper.fireForgeConfigEvent(this.config, new ModConfigEvent.Reloading(this.config)); } } @Override public IConfigEntry getRoot() { return new IcebergFolderEntry(((IcebergConfigSpec) this.config.getSpec()).getValues(), (IcebergConfigSpec) this.config.getSpec()); } @Override public ConfigType getType() { return TYPE_RESOLVER.get(this.config.getType()); } @Override public String getFileName() { return this.config.getFileName(); } @Override public String getModId() { return this.config.getModId(); } @Override public void loadWorldConfig(Path path, Consumer result) { final CommentedFileConfig data = this.config.getHandler().reader(path).apply(this.config); ConfigHelper.setForgeConfigData(this.config, data); result.accept(this); } @Override public void stopEditing() { // Attempts to unload the server config if player simply just went back if (this.config != null && this.getType() == ConfigType.WORLD) { if (!ConfigHelper.isPlayingGame()) { // Unload server configs since still in main menu this.config.getHandler().unload(this.config.getFullPath().getParent(), this.config); ConfigHelper.setForgeConfigData(this.config, null); } } } @Override public boolean isChanged() { // Block world configs since the path is dynamic if (ConfigHelper.isWorldConfig(this) && this.config.getConfigData() == null) { return false; } // Check if any config value doesn't equal it's default return this.allConfigValues.stream().anyMatch(entry -> { return !Objects.equals(entry.value.get(), entry.spec.getDefault()); }); } @Override public void restoreDefaults() { // Block world configs since the path is dynamic if (ConfigHelper.isWorldConfig(this) && this.config.getConfigData() == null) { return; } // Creates a copy of the config data then pushes all at once to avoid multiple IO ops CommentedConfig newConfig = CommentedConfig.copy(this.config.getConfigData()); this.allConfigValues.forEach(entry -> newConfig.set(entry.value.getPath(), entry.spec.getDefault())); this.config.getConfigData().putAll(newConfig); // Finally clear cache of all config values this.allConfigValues.forEach(pair -> pair.value.clearCache()); } private static List getAllConfigValues(ModConfig config) { IcebergConfigSpec icebergConfigSpec = (IcebergConfigSpec) config.getSpec(); List, ForgeConfigSpec.ValueSpec>> values = Lists.newArrayList(); gatherValuesFromIcebergConfig(icebergConfigSpec.getValues(), icebergConfigSpec, values); return ImmutableList.copyOf(values).stream().map(pair -> new IcebergValueEntry(pair.getFirst(), pair.getSecond())).collect(Collectors.toList()); } private static void gatherValuesFromIcebergConfig(UnmodifiableConfig config, IcebergConfigSpec spec, List, ForgeConfigSpec.ValueSpec>> values) { config.valueMap().forEach((key, value) -> { if (value instanceof AbstractConfig) { gatherValuesFromIcebergConfig((UnmodifiableConfig) value, spec, values); } else if (value instanceof ForgeConfigSpec.ConfigValue configValue) { ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(configValue.getPath()); values.add(Pair.of(configValue, valueSpec)); } }); } private record IcebergValueEntry(ForgeConfigSpec.ConfigValue value, ForgeConfigSpec.ValueSpec spec) {} }