From 2625eeb7dedb05b524b08721e1267acdcd93e5c1 Mon Sep 17 00:00:00 2001 From: nea Date: Wed, 3 Aug 2022 02:31:05 +0200 Subject: [PATCH] scuffed config screen --- build.gradle.kts | 9 ++ gradle.properties | 1 + .../nea/notenoughupdates/NotEnoughUpdates.kt | 32 +++-- .../events/NEUScreenEvents.kt | 23 ++++ .../notenoughupdates/gui/RepoManagementGui.kt | 77 ++++++++++++ .../notenoughupdates/mixins/MixinMinecraft.kt | 19 +++ .../nea/notenoughupdates/rei/NEUReiPlugin.kt | 2 +- .../repo/RepoDownloadManager.kt | 34 +++-- .../nea/notenoughupdates/repo/RepoManager.kt | 40 +++++- .../nea/notenoughupdates/util/ConfigHolder.kt | 117 ++++++++++++++++++ .../nea/notenoughupdates/util/ScreenUtil.kt | 37 ++++++ .../assets/notenoughupdates/icon.png | Bin 0 -> 20279 bytes src/main/resources/fabric.mod.json | 51 ++++---- .../resources/notenoughupdates.mixins.json | 1 + 14 files changed, 387 insertions(+), 56 deletions(-) create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/events/NEUScreenEvents.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/gui/RepoManagementGui.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMinecraft.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/ScreenUtil.kt create mode 100644 src/main/resources/assets/notenoughupdates/icon.png diff --git a/build.gradle.kts b/build.gradle.kts index c439e86..c48d666 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 + ) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1624ae7..acc1c97 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt index c4f3f69..ff816f5 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt @@ -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 { @@ -49,23 +58,30 @@ object NotEnoughUpdates : ModInitializer, ClientModInitializer { val globalJob = Job() val coroutineScope = - CoroutineScope(EmptyCoroutineContext + CoroutineName("NotEnoughUpdates")) + SupervisorJob(globalJob) + CoroutineScope(EmptyCoroutineContext + CoroutineName("NotEnoughUpdates")) + SupervisorJob(globalJob) val coroutineScopeIo = coroutineScope + Dispatchers.IO + SupervisorJob(globalJob) private fun registerCommands( - dispatcher: CommandDispatcher, - @Suppress("UNUSED_PARAMETER") - _ctx: CommandBuildContext + dispatcher: CommandDispatcher, + @Suppress("UNUSED_PARAMETER") + _ctx: CommandBuildContext ) { dispatcher.register(ClientCommandManager.literal("neureload").executes { it.source.sendFeedback(Component.literal("Reloading repository from disk. This may lag a bit.")) 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) } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/NEUScreenEvents.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/NEUScreenEvents.kt new file mode 100644 index 0000000..f118be5 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/NEUScreenEvents.kt @@ -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) } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/notenoughupdates/gui/RepoManagementGui.kt b/src/main/kotlin/moe/nea/notenoughupdates/gui/RepoManagementGui.kt new file mode 100644 index 0000000..115b9f8 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/gui/RepoManagementGui.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMinecraft.kt b/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMinecraft.kt new file mode 100644 index 0000000..f017604 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMinecraft.kt @@ -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() + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt index 985743a..624f5d8 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt @@ -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)) } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt index 977c035..34279af 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt @@ -24,22 +24,18 @@ 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()) { - try { - repoMetadataLocation.readText().trim() - } catch (e: IOException) { + if (repoSavedLocation.exists()) { + if (repoMetadataLocation.exists()) { + try { + repoMetadataLocation.readText().trim() + } catch (e: IOException) { + null + } + } else { null } - } else { - null - } - } else null + } else null private fun saveVersionHash(versionHash: String) { latestSavedVersionHash = versionHash @@ -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.") @@ -104,9 +100,9 @@ object RepoDownloadManager { val entry = cis.nextEntry ?: break if (entry.isDirectory) continue val extractedLocation = - repoSavedLocation.resolve( - entry.name.substringAfter('/', missingDelimiterValue = "") - ) + repoSavedLocation.resolve( + entry.name.substringAfter('/', missingDelimiterValue = "") + ) if (repoSavedLocation !in extractedLocation.iterate { it.parent }) { logger.error("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.") throw RuntimeException("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.") diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt index f3d53e8..70ee18e 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt @@ -2,29 +2,59 @@ 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(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()) { - neuRepo.reload() - } + RepoDownloadManager.downloadUpdate() + neuRepo.reload() + } + } + + fun initialize() { + if (config.autoUpdate) { + launchAsyncUpdate() + } else { + neuRepo.reload() } } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt new file mode 100644 index 0000000..003de6c --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt @@ -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(val serializer: KSerializer, + 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 = CopyOnWriteArrayList() + private val allConfigs: MutableMap>, ConfigHolder<*>> = mutableMapOf() + private val dirty: MutableSet>> = mutableSetOf() + + private fun , K> putConfig(kClass: KClass, inst: ConfigHolder) { + allConfigs[kClass] = inst + } + + fun , K> markDirty(kClass: KClass) { + 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 + }) + } + + + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/ScreenUtil.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/ScreenUtil.kt new file mode 100644 index 0000000..c56560e --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/ScreenUtil.kt @@ -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 + } + + +} \ No newline at end of file diff --git a/src/main/resources/assets/notenoughupdates/icon.png b/src/main/resources/assets/notenoughupdates/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..467f962838f7a0ba5d3521439f998c4afa47c3d0 GIT binary patch literal 20279 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index cada5ad..c192d0d 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -5,38 +5,43 @@ "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": { - "main": [ - { - "adapter": "kotlin", - "value": "moe.nea.notenoughupdates.NotEnoughUpdates" - } - ], - "client": [ - { - "adapter": "kotlin", - "value": "moe.nea.notenoughupdates.NotEnoughUpdates" - } - ], - "rei_client": [ - "moe.nea.notenoughupdates.rei.NEUReiPlugin" - ] + "main": [ + { + "adapter": "kotlin", + "value": "moe.nea.notenoughupdates.NotEnoughUpdates" + } + ], + "client": [ + { + "adapter": "kotlin", + "value": "moe.nea.notenoughupdates.NotEnoughUpdates" + } + ], + "rei_client": [ + "moe.nea.notenoughupdates.rei.NEUReiPlugin" + ] }, "mixins": [ - "notenoughupdates.mixins.json" + "notenoughupdates.mixins.json" ], "depends": { - "fabric": "*", - "fabric-language-kotlin": ">=1.8.2+kotlin.1.7.10", - "minecraft": ">=1.18.2" + "fabric": "*", + "fabric-language-kotlin": ">=1.8.2+kotlin.1.7.10", + "minecraft": ">=1.18.2" } } diff --git a/src/main/resources/notenoughupdates.mixins.json b/src/main/resources/notenoughupdates.mixins.json index 3067bcc..1b6dac8 100644 --- a/src/main/resources/notenoughupdates.mixins.json +++ b/src/main/resources/notenoughupdates.mixins.json @@ -3,6 +3,7 @@ "package": "moe.nea.notenoughupdates.mixins", "compatibilityLevel": "JAVA_16", "client": [ + "MixinMinecraft" ], "mixins": [ ],