SnowyGUI/common/src/main/kotlin/fr/username404/snowygui/config/Configuration.kt
Username404 904c0ac831
Only save buttons which can be toggled
Signed-off-by: Username404 <w.iron.zombie@gmail.com>
2022-03-26 11:37:07 +01:00

173 lines
6.4 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 fr.username404.snowygui.ClickGui
import fr.username404.snowygui.Snowy
import fr.username404.snowygui.Snowy.Companion.MissingComponent
import fr.username404.snowygui.gui.feature.shouldSave
import fr.username404.snowygui.gui.feature.Category
import fr.username404.snowygui.gui.feature.Macro
import io.github.config4k.extract
import io.github.config4k.getValue
import net.minecraft.client.Minecraft
import java.io.File
import kotlin.reflect.KProperty
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jetbrains.annotations.ApiStatus
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
@OptIn(ExperimentalSerializationApi::class)
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.key != MissingComponent.key) && Category.fromBox(box)?.categoryColor != box.color
}.map {
it.name.key to it.color
}.toTypedArray()),
)
}
private val configDirectory: String = (Minecraft.getInstance().gameDirectory.absolutePath + File.separator + "config").also { File(it).mkdir() }
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).filterKeys { keyName -> ClickGui.clickBoxes.any {
it.buttons.any { button ->
button.info.shouldSave() && button.title == keyName
}
}})
}
} }
}
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)
}