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")
}
}
maven("https://server.bbkr.space/artifactory/libs-release")
mavenLocal()
}
@@ -64,6 +65,7 @@ dependencies {
transInclude(implementation(ktor("client-java"))!!)
transInclude(implementation(ktor("serialization-kotlinx-json"))!!)
transInclude(implementation(ktor("client-content-negotiation"))!!)
modImplementation(include("io.github.cottonmc:LibGui:${project.property("libgui_version")}")!!)
// Dev environment preinstalled mods
modRuntimeOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.property("rei_version")}")
@@ -110,3 +112,10 @@ tasks.remapJar {
dependsOn(tasks.shadowJar)
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_kotlin_version=1.8.2+kotlin.1.7.10
libgui_version=6.0.0+1.19
rei_version=9.1.518
devauth_version=1.0.0
modmenu_version=4.0.4

View File

@@ -2,19 +2,26 @@ package moe.nea.notenoughupdates
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import moe.nea.notenoughupdates.gui.RepoManagementGui
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.ModInitializer
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.FabricClientCommandSource
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.network.chat.Component
import org.apache.logging.log4j.LogManager
@@ -25,15 +32,17 @@ import kotlin.coroutines.EmptyCoroutineContext
object NotEnoughUpdates : ModInitializer, ClientModInitializer {
const val MOD_ID = "notenoughupdates"
val DATA_DIR = Path.of(".notenoughupdates").also { Files.createDirectories(it) }
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 metadata by lazy { FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata }
val version by lazy { metadata.version }
val metadata: ModMetadata by lazy { FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata }
val version: Version by lazy { metadata.version }
val json = Json {
prettyPrint = DEBUG
ignoreUnknownKeys = true
encodeDefaults = true
}
val httpClient by lazy {
@@ -62,10 +71,17 @@ object NotEnoughUpdates : ModInitializer, ClientModInitializer {
RepoManager.neuRepo.reload()
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() {
RepoManager.launchAsyncUpdate()
RepoManager.initialize()
ConfigHolder.registerEvents()
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) {
RepoManager.neuRepo.items.items.values.forEach {
RepoManager.neuRepo.items?.items?.values?.forEach {
if (!it.isVanilla)
registry.addEntry(EntryStack.of(SBItemEntryDefinition, it))
}

View File

@@ -24,10 +24,6 @@ object RepoDownloadManager {
val repoSavedLocation = NotEnoughUpdates.DATA_DIR.resolve("repo-extracted")
val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo-sha.txt")
val user = "NotEnoughUpdates"
val repo = "NotEnoughUpdates-REPO"
val branch = "dangerous"
private fun loadSavedVersionHash(): String? =
if (repoSavedLocation.exists()) {
if (repoMetadataLocation.exists()) {
@@ -54,7 +50,7 @@ object RepoDownloadManager {
private suspend fun requestLatestGithubSha(): String? {
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) {
return null
}
@@ -81,11 +77,11 @@ object RepoDownloadManager {
}
val currentSha = loadSavedVersionHash()
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")
val zipFile = downloadGithubArchive(requestUrl)
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")
withContext(IO) { extractNewRepository(zipFile) }
logger.info("Repository loaded on disk.")

View File

@@ -2,30 +2,60 @@ package moe.nea.notenoughupdates.repo
import io.github.moulberry.repo.NEURepository
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates
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.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 {
registerReloadListener(ItemCache)
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.")
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() {
NotEnoughUpdates.coroutineScope.launch {
if (RepoDownloadManager.downloadUpdate()) {
RepoDownloadManager.downloadUpdate()
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",
"description": "Not Enough Updates - A mod for Hypixel Skyblock",
"authors": [
"nea89"
{
"name": "Linnea Gräf",
"contact": {
"email": "nea@nea89.moe"
}
}
],
"contact": {
"homepage": "https://github.com/romangraef/TODO",
"sources": "https://github.com/romangraef/TODO"
"discord": "https://discord.gg/moulberry",
"sources": "https://git.nea.moe/nea/neurepoparsing/"
},
"license": "LGPL-3.0",
"license": "ARR",
"icon": "assets/notenoughupdates/icon.png",
"environment": "client",
"entrypoints": {

View File

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