scuffed config screen

This commit is contained in:
nea
2022-08-03 02:31:05 +02:00
parent b945546786
commit 2625eeb7de
14 changed files with 387 additions and 56 deletions

View File

@@ -32,6 +32,7 @@ repositories {
includeGroup("maven.modrinth") includeGroup("maven.modrinth")
} }
} }
maven("https://server.bbkr.space/artifactory/libs-release")
mavenLocal() mavenLocal()
} }
@@ -64,6 +65,7 @@ dependencies {
transInclude(implementation(ktor("client-java"))!!) transInclude(implementation(ktor("client-java"))!!)
transInclude(implementation(ktor("serialization-kotlinx-json"))!!) transInclude(implementation(ktor("serialization-kotlinx-json"))!!)
transInclude(implementation(ktor("client-content-negotiation"))!!) transInclude(implementation(ktor("client-content-negotiation"))!!)
modImplementation(include("io.github.cottonmc:LibGui:${project.property("libgui_version")}")!!)
// Dev environment preinstalled mods // Dev environment preinstalled mods
modRuntimeOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.property("rei_version")}") modRuntimeOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.property("rei_version")}")
@@ -110,3 +112,10 @@ tasks.remapJar {
dependsOn(tasks.shadowJar) dependsOn(tasks.shadowJar)
archiveClassifier.set("thicc") archiveClassifier.set("thicc")
} }
tasks.processResources {
filesMatching("**/fabric.mod.json") {
expand(
"version" to project.version
)
}
}

View File

@@ -13,6 +13,7 @@ fabric_loader_version=0.14.8
fabric_api_version=0.58.0+1.19 fabric_api_version=0.58.0+1.19
fabric_kotlin_version=1.8.2+kotlin.1.7.10 fabric_kotlin_version=1.8.2+kotlin.1.7.10
libgui_version=6.0.0+1.19
rei_version=9.1.518 rei_version=9.1.518
devauth_version=1.0.0 devauth_version=1.0.0
modmenu_version=4.0.4 modmenu_version=4.0.4

View File

@@ -2,19 +2,26 @@ package moe.nea.notenoughupdates
import com.mojang.brigadier.Command import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.plugins.* import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import moe.nea.notenoughupdates.gui.RepoManagementGui
import moe.nea.notenoughupdates.repo.RepoManager import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.ConfigHolder
import moe.nea.notenoughupdates.util.ScreenUtil.setScreenLater
import net.fabricmc.api.ClientModInitializer import net.fabricmc.api.ClientModInitializer
import net.fabricmc.api.ModInitializer import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.fabricmc.loader.api.FabricLoader import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.loader.api.Version
import net.fabricmc.loader.api.metadata.ModMetadata
import net.minecraft.client.Minecraft
import net.minecraft.commands.CommandBuildContext import net.minecraft.commands.CommandBuildContext
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
@@ -25,15 +32,17 @@ import kotlin.coroutines.EmptyCoroutineContext
object NotEnoughUpdates : ModInitializer, ClientModInitializer { object NotEnoughUpdates : ModInitializer, ClientModInitializer {
const val MOD_ID = "notenoughupdates" const val MOD_ID = "notenoughupdates"
val DATA_DIR = Path.of(".notenoughupdates").also { Files.createDirectories(it) }
val DEBUG = System.getenv("notenoughupdates.debug") == "true" val DEBUG = System.getenv("notenoughupdates.debug") == "true"
val DATA_DIR: Path = Path.of(".notenoughupdates").also { Files.createDirectories(it) }
val CONFIG_DIR: Path = Path.of("config/notenoughupdates").also { Files.createDirectories(it) }
val logger = LogManager.getLogger("NotEnoughUpdates") val logger = LogManager.getLogger("NotEnoughUpdates")
val metadata by lazy { FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata } val metadata: ModMetadata by lazy { FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata }
val version by lazy { metadata.version } val version: Version by lazy { metadata.version }
val json = Json { val json = Json {
prettyPrint = DEBUG prettyPrint = DEBUG
ignoreUnknownKeys = true ignoreUnknownKeys = true
encodeDefaults = true
} }
val httpClient by lazy { val httpClient by lazy {
@@ -62,10 +71,17 @@ object NotEnoughUpdates : ModInitializer, ClientModInitializer {
RepoManager.neuRepo.reload() RepoManager.neuRepo.reload()
Command.SINGLE_SUCCESS Command.SINGLE_SUCCESS
}) })
dispatcher.register(ClientCommandManager.literal("neu")
.then(ClientCommandManager.literal("repo").executes {
it.source.sendFeedback(Component.literal("Hi, this should work"))
Minecraft.getInstance().setScreenLater(CottonClientScreen(RepoManagementGui()))
Command.SINGLE_SUCCESS
}))
} }
override fun onInitialize() { override fun onInitialize() {
RepoManager.launchAsyncUpdate() RepoManager.initialize()
ConfigHolder.registerEvents()
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands) ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
} }

View File

@@ -0,0 +1,23 @@
package moe.nea.notenoughupdates.events
import moe.nea.notenoughupdates.events.NEUScreenEvents.OnScreenOpen
import net.fabricmc.fabric.api.event.EventFactory
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.Minecraft
object NEUScreenEvents {
fun interface OnScreenOpen {
/**
* Called when a new Screen is opened via [Minecraft.setScreen]. If [new] is null, this corresponds to closing a [Screen].
* @return true to prevent this event from happening.
*/
fun onScreenOpen(old: Screen?, new: Screen?): Boolean
}
val SCREEN_OPEN = EventFactory.createArrayBacked(OnScreenOpen::class.java) { arr ->
OnScreenOpen { old, new ->
return@OnScreenOpen arr.asSequence().any { it.onScreenOpen(old, new) }
}
}
}

View File

@@ -0,0 +1,77 @@
package moe.nea.notenoughupdates.gui
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription
import io.github.cottonmc.cotton.gui.widget.WGridPanel
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WTextField
import io.github.cottonmc.cotton.gui.widget.WToggleButton
import io.github.cottonmc.cotton.gui.widget.data.Insets
import moe.nea.notenoughupdates.repo.RepoManager
import net.minecraft.network.chat.Component
import java.util.function.Consumer
class RepoManagementGui : LightweightGuiDescription() {
init {
val root = WGridPanel()
setRootPanel(root)
root.setSize(256, 240)
root.insets = Insets.ROOT_PANEL
WLabel(Component.literal("Auto Update")).apply {
root.add(this, 0, 1, 5, 1)
}
WToggleButton(Component.literal("Auto Update")).apply {
this.toggle = RepoManager.config.autoUpdate
this.onToggle = Consumer {
RepoManager.config.autoUpdate = it
RepoManager.markDirty()
}
root.add(this, 5, 1, 1, 1)
}
WLabel(Component.literal("Repo Username")).apply {
root.add(this, 0, 2, 5, 1)
}
WTextField(Component.literal("username")).apply {
this.isEditable = true
this.text = RepoManager.config.user
this.setChangedListener {
RepoManager.config.user = it
RepoManager.markDirty()
}
root.add(this, 5, 2, 6, 1)
}
WLabel(Component.literal("Repo Name")).apply {
root.add(this, 0, 3, 5, 1)
}
WTextField(Component.literal("repo name")).apply {
this.isEditable = true
this.text = RepoManager.config.repo
this.setChangedListener {
RepoManager.config.repo = it
RepoManager.markDirty()
}
root.add(this, 5, 3, 6, 1)
}
WLabel(Component.literal("Repo Branch")).apply {
root.add(this, 0, 4, 5, 1)
}
WTextField(Component.literal("repo name")).apply {
this.isEditable = true
this.text = RepoManager.config.branch
this.setChangedListener {
RepoManager.config.branch = it
RepoManager.markDirty()
}
root.add(this, 5, 4, 6, 1)
}
}
}

View File

@@ -0,0 +1,19 @@
package moe.nea.notenoughupdates.mixins
import moe.nea.notenoughupdates.events.NEUScreenEvents
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen
import org.spongepowered.asm.mixin.Mixin
import org.spongepowered.asm.mixin.injection.At
import org.spongepowered.asm.mixin.injection.Inject
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo
@Suppress("CAST_NEVER_SUCCEEDS")
@Mixin(Minecraft::class)
class MixinMinecraft {
@Inject(method = ["setScreen"], at = [At("HEAD")], cancellable = true)
fun momo(screen: Screen?, ci: CallbackInfo) {
if (NEUScreenEvents.SCREEN_OPEN.invoker().onScreenOpen((this as Minecraft).screen, screen))
ci.cancel()
}
}

View File

@@ -29,7 +29,7 @@ class NEUReiPlugin : REIClientPlugin {
override fun registerEntries(registry: EntryRegistry) { override fun registerEntries(registry: EntryRegistry) {
RepoManager.neuRepo.items.items.values.forEach { RepoManager.neuRepo.items?.items?.values?.forEach {
if (!it.isVanilla) if (!it.isVanilla)
registry.addEntry(EntryStack.of(SBItemEntryDefinition, it)) registry.addEntry(EntryStack.of(SBItemEntryDefinition, it))
} }

View File

@@ -24,10 +24,6 @@ object RepoDownloadManager {
val repoSavedLocation = NotEnoughUpdates.DATA_DIR.resolve("repo-extracted") val repoSavedLocation = NotEnoughUpdates.DATA_DIR.resolve("repo-extracted")
val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo-sha.txt") val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo-sha.txt")
val user = "NotEnoughUpdates"
val repo = "NotEnoughUpdates-REPO"
val branch = "dangerous"
private fun loadSavedVersionHash(): String? = private fun loadSavedVersionHash(): String? =
if (repoSavedLocation.exists()) { if (repoSavedLocation.exists()) {
if (repoMetadataLocation.exists()) { if (repoMetadataLocation.exists()) {
@@ -54,7 +50,7 @@ object RepoDownloadManager {
private suspend fun requestLatestGithubSha(): String? { private suspend fun requestLatestGithubSha(): String? {
val response = val response =
NotEnoughUpdates.httpClient.get("https://api.github.com/repos/$user/$repo/commits/$branch") NotEnoughUpdates.httpClient.get("https://api.github.com/repos/${RepoManager.config.user}/${RepoManager.config.repo}/commits/${RepoManager.config.branch}")
if (response.status.value != 200) { if (response.status.value != 200) {
return null return null
} }
@@ -81,11 +77,11 @@ object RepoDownloadManager {
} }
val currentSha = loadSavedVersionHash() val currentSha = loadSavedVersionHash()
if (latestSha != currentSha) { if (latestSha != currentSha) {
val requestUrl = "https://github.com/$user/$repo/archive/$latestSha.zip" val requestUrl = "https://github.com/${RepoManager.config.user}/${RepoManager.config.repo}/archive/$latestSha.zip"
logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl") logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
val zipFile = downloadGithubArchive(requestUrl) val zipFile = downloadGithubArchive(requestUrl)
logger.info("Download repository zip file to $zipFile. Deleting old repository") logger.info("Download repository zip file to $zipFile. Deleting old repository")
withContext(IO) { repoSavedLocation.deleteIfExists() } withContext(IO) { repoSavedLocation.toFile().deleteRecursively() }
logger.info("Extracting new repository") logger.info("Extracting new repository")
withContext(IO) { extractNewRepository(zipFile) } withContext(IO) { extractNewRepository(zipFile) }
logger.info("Repository loaded on disk.") logger.info("Repository loaded on disk.")

View File

@@ -2,30 +2,60 @@ package moe.nea.notenoughupdates.repo
import io.github.moulberry.repo.NEURepository import io.github.moulberry.repo.NEURepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.NotEnoughUpdates.logger import moe.nea.notenoughupdates.NotEnoughUpdates.logger
import moe.nea.notenoughupdates.util.ConfigHolder
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket
object RepoManager { object RepoManager : ConfigHolder<RepoManager.Config>(serializer(), "repo", ::Config) {
@Serializable
data class Config(
var user: String = "NotEnoughUpdates",
var repo: String = "NotEnoughUpdates-REPO",
var autoUpdate: Boolean = true,
var branch: String = "dangerous",
)
var recentlyFailedToUpdateItemList = false
val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply { val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply {
registerReloadListener(ItemCache) registerReloadListener(ItemCache)
registerReloadListener { registerReloadListener {
if (Minecraft.getInstance().connection?.handleUpdateRecipes(ClientboundUpdateRecipesPacket(mutableListOf())) == null) { if (!trySendClientboundUpdateRecipesPacket()) {
logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.") logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.")
recentlyFailedToUpdateItemList = true
} }
} }
} }
private fun trySendClientboundUpdateRecipesPacket(): Boolean {
return Minecraft.getInstance().level != null && Minecraft.getInstance().connection?.handleUpdateRecipes(ClientboundUpdateRecipesPacket(mutableListOf())) != null
}
init {
ClientTickEvents.START_WORLD_TICK.register(ClientTickEvents.StartWorldTick {
if (recentlyFailedToUpdateItemList && trySendClientboundUpdateRecipesPacket())
recentlyFailedToUpdateItemList = false
})
}
fun launchAsyncUpdate() { fun launchAsyncUpdate() {
NotEnoughUpdates.coroutineScope.launch { NotEnoughUpdates.coroutineScope.launch {
if (RepoDownloadManager.downloadUpdate()) { RepoDownloadManager.downloadUpdate()
neuRepo.reload() neuRepo.reload()
} }
} }
fun initialize() {
if (config.autoUpdate) {
launchAsyncUpdate()
} else {
neuRepo.reload()
}
} }
} }

View File

@@ -0,0 +1,117 @@
package moe.nea.notenoughupdates.util
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.events.NEUScreenEvents
import net.minecraft.client.Minecraft
import net.minecraft.commands.CommandSource
import net.minecraft.network.chat.Component
import java.io.IOException
import java.nio.file.Path
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.reflect.KClass
abstract class ConfigHolder<T>(val serializer: KSerializer<T>,
val name: String,
val default: () -> T) {
var config: T
private set
init {
config = readValueOrDefault()
putConfig(this::class, this)
}
val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json")
protected fun readValueOrDefault(): T {
if (file.exists())
try {
return NotEnoughUpdates.json.decodeFromString(
serializer,
file.readText()
)
} catch (e: IOException) {
badLoads.add(name)
NotEnoughUpdates.logger.error("IO exception during loading of config file $name. This will reset this config.", e)
} catch (e: SerializationException) {
badLoads.add(name)
NotEnoughUpdates.logger.error("Serialization exception during loading of config file $name. This will reset this config.", e)
}
return default()
}
private fun writeValue(t: T) {
file.writeText(NotEnoughUpdates.json.encodeToString(serializer, t))
}
fun save() {
writeValue(config)
}
fun load() {
config = readValueOrDefault()
}
fun markDirty() {
Companion.markDirty(this::class)
}
companion object {
private var badLoads: MutableList<String> = CopyOnWriteArrayList()
private val allConfigs: MutableMap<KClass<out ConfigHolder<*>>, ConfigHolder<*>> = mutableMapOf()
private val dirty: MutableSet<KClass<out ConfigHolder<*>>> = mutableSetOf()
private fun <T : ConfigHolder<K>, K> putConfig(kClass: KClass<T>, inst: ConfigHolder<K>) {
allConfigs[kClass] = inst
}
fun <T : ConfigHolder<K>, K> markDirty(kClass: KClass<T>) {
if (kClass !in allConfigs) {
NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'ConfigHolder'")
return
}
dirty.add(kClass)
}
private fun performSaves() {
val toSave = dirty.toList().also {
dirty.clear()
}
for (it in toSave) {
val obj = allConfigs[it]
if (obj == null) {
NotEnoughUpdates.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'")
continue
}
obj.save()
}
}
private fun warnForResetConfigs(player: CommandSource) {
if (badLoads.isNotEmpty()) {
player.sendSystemMessage(Component.literal("The following configs have been reset: ${badLoads.joinToString(", ")}. " +
"This can be intentional, but probably isn't."))
badLoads.clear()
}
}
fun registerEvents() {
NEUScreenEvents.SCREEN_OPEN.register(NEUScreenEvents.OnScreenOpen { old, new ->
performSaves()
val p = Minecraft.getInstance().player
if (p != null) {
warnForResetConfigs(p)
}
false
})
}
}
}

View File

@@ -0,0 +1,37 @@
package moe.nea.notenoughupdates.util
import moe.nea.notenoughupdates.NotEnoughUpdates
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen
object ScreenUtil {
init {
ClientTickEvents.START_CLIENT_TICK.register(::onTick)
}
private fun onTick(minecraft: Minecraft) {
if (nextOpenedGui != null) {
val p = minecraft.player
if (p?.containerMenu != null) {
p.closeContainer()
}
minecraft.setScreen(nextOpenedGui)
nextOpenedGui = null
}
}
private var nextOpenedGui: Screen? = null
@Suppress("UnusedReceiverParameter")
fun Minecraft.setScreenLater(nextScreen: Screen) {
val nog = nextOpenedGui
if (nog != null) {
NotEnoughUpdates.logger.warn("Setting screen ${nextScreen::class.qualifiedName} to be opened later, but ${nog::class.qualifiedName} is already queued.")
return
}
nextOpenedGui = nextScreen
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -5,13 +5,18 @@
"name": "Not Enough Updates", "name": "Not Enough Updates",
"description": "Not Enough Updates - A mod for Hypixel Skyblock", "description": "Not Enough Updates - A mod for Hypixel Skyblock",
"authors": [ "authors": [
"nea89" {
"name": "Linnea Gräf",
"contact": {
"email": "nea@nea89.moe"
}
}
], ],
"contact": { "contact": {
"homepage": "https://github.com/romangraef/TODO", "discord": "https://discord.gg/moulberry",
"sources": "https://github.com/romangraef/TODO" "sources": "https://git.nea.moe/nea/neurepoparsing/"
}, },
"license": "LGPL-3.0", "license": "ARR",
"icon": "assets/notenoughupdates/icon.png", "icon": "assets/notenoughupdates/icon.png",
"environment": "client", "environment": "client",
"entrypoints": { "entrypoints": {

View File

@@ -3,6 +3,7 @@
"package": "moe.nea.notenoughupdates.mixins", "package": "moe.nea.notenoughupdates.mixins",
"compatibilityLevel": "JAVA_16", "compatibilityLevel": "JAVA_16",
"client": [ "client": [
"MixinMinecraft"
], ],
"mixins": [ "mixins": [
], ],