SnowyGUI/common/src/main/kotlin/fr/username404/snowygui/config/Configuration.kt
Username404-59 bc14627b97
Fix my stupid configuration system
Signed-off-by: Username404-59 <w.iron.zombie@gmail.com>
2025-04-16 20:46:24 +02:00

179 lines
6.7 KiB
Kotlin

package fr.username404.snowygui.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory.empty
import com.typesafe.config.ConfigFactory.load
import com.typesafe.config.ConfigFactory.parseFile
import com.typesafe.config.ConfigFactory.parseString
import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValueFactory
import dev.architectury.injectables.targets.ArchitecturyTarget
import fr.username404.snowygui.ClickGui
import fr.username404.snowygui.Snowy
import fr.username404.snowygui.Snowy.Companion.MissingComponent
import fr.username404.snowygui.gui.feature.Category
import fr.username404.snowygui.gui.feature.Macro
import fr.username404.snowygui.gui.feature.shouldSave
import io.github.config4k.extract
import io.github.config4k.getValue
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.minecraft.network.chat.contents.TranslatableContents
import org.jetbrains.annotations.ApiStatus
import java.io.File
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.isSuperclassOf
object Configuration {
@Deprecated("Use the getValue or setValue methods instead", level = DeprecationLevel.ERROR)
@JvmStatic
val ModifiableValues: MutableMap<String, Any?> by lazy {
mutableMapOf(
"enabledFeatures" to enabledFeatures,
"macros" to lazy {
Category.MACROS.box.buttons.map {
Json.encodeToString(it as Macro)
}
},
"box_colors" to mapOf<String, Int>(*ClickGui.clickBoxes.filter { box ->
((box.name.contents as TranslatableContents).key != (MissingComponent.contents as TranslatableContents).key) && Category.fromBox(box)?.categoryColor != box.color
}.map {
(it.name as TranslatableContents).key to it.color
}.toTypedArray()),
)
}
private val configDirectory: String =
(if (ArchitecturyTarget.getCurrentTarget() == "fabric") {
Class.forName("net.fabricmc.loader.api.FabricLoader").run {
getMethod("getConfigDir").invoke(getMethod("getInstance").invoke(null))
}
} else {
Class.forName("net.neoforged.fml.loading.FMLPaths")
.getField("CONFIGDIR")
.get(null)
.let { enum ->
enum.javaClass.getMethod("get").invoke(enum)
}
} as Path).absolutePathString()
private val file: File = File(configDirectory + File.separator + "snowy.conf")
private val obtained: Config = run {
var result: Config = empty()
with(file) {
if (!exists()) {
createNewFile()
setWritable(true)
setReadable(true)
} else try {
result = parseFile(file)
} catch (_: ConfigException) {
Snowy.logs.warn("Could not parse the snowy configuration file, the default configuration will be used instead.")
}
}
load(result).withFallback(
parseString(
"""
|Snowy {
| macros = []
| sortAlphabetically = true
|}
""".trimMargin()
)
).extract<Config>("Snowy")
}.also {
runBlocking {
writeConfig(it)
}
}
val enabledFeatures = mutableMapOf<String, Boolean>().apply {
"enabledFeatures".let { obtained.run {
if (hasPath(it)) {
putAll(extract<Map<out String, Boolean>>(it))
}
} }
}
internal val foundMacros: Set<Macro> = run {
obtained.getStringList("macros").map {
Json.decodeFromString<Macro>(it)
}.toSet()
}
init {
Runtime.getRuntime().addShutdownHook(
Thread {
runBlocking {
ClickGui.boxContext {
enabledFeatures.putAll(buttons.filter { it.info.shouldSave() }.map {
it.title to it.toggled
})
}
writeConfig(obtained.withFullModifiableValues()).join()
}
}
)
}
private fun Config.withFullModifiableValues() = @Suppress("DEPRECATION_ERROR")
ModifiableValues.entries.fold(this) { previous, entry ->
previous.withValue(entry.key, entry.value.let {
ConfigValueFactory.fromAnyRef(
if (it !is Lazy<*>) it else it.value
)
})
}
private suspend fun writeConfig(c: Config) = coroutineScope {
launch(start = CoroutineStart.UNDISPATCHED) {
file.writeText(
"""
|Snowy {
|${
c.root().render(
ConfigRenderOptions.defaults()
.setFormatted(true)
.setJson(false)
.setOriginComments(false)
).prependIndent(" ").trimEnd()
}
|}
""".trimMargin()
)
}
}
operator fun invoke() = obtained
@JvmSynthetic
@ApiStatus.Internal
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("This method should only be used in the getValue operator of the Configuration object", level = DeprecationLevel.ERROR)
fun <T: Any> convertValue(type: KClass<T>): T? =
@Suppress("UNCHECKED_CAST")
when {
Number::class.isSuperclassOf(type) -> when (type) {
Double::class -> 2.0
Int::class, UInt::class,
Short::class, UShort::class,
Long::class, ULong::class -> 2
Float::class -> 2F
else -> null
} as T
type == Boolean::class -> false as T
else -> null
}
inline operator fun <reified T: Any> getValue(ref: Any?, property: KProperty<*>): T =
@Suppress("DEPRECATION_ERROR")
if (ModifiableValues.containsKey(property.name))
ModifiableValues[property.name] as T
else try {
invoke().getValue(ref, property)
} catch (e: ConfigException) {
convertValue(T::class) ?: throw e
}
operator fun <T> setValue(ref: Any?, property: KProperty<*>, value: T) = @Suppress("DEPRECATION_ERROR")
ModifiableValues.setValue(ref, property, value)
}