179 lines
6.7 KiB
Kotlin
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)
|
|
}
|