diff options
Diffstat (limited to 'src/main/kotlin/moe/nea/notenoughupdates')
9 files changed, 348 insertions, 33 deletions
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<FabricClientCommandSource>, - @Suppress("UNUSED_PARAMETER") - _ctx: CommandBuildContext + dispatcher: CommandDispatcher<FabricClientCommandSource>, + @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<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()) { - 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<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 + }) + } + + + } +}
\ 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 |