aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/io
diff options
context:
space:
mode:
authorRoman / Linnea Gräf <roman.graef@gmail.com>2023-03-13 23:43:40 +0100
committerGitHub <noreply@github.com>2023-03-13 23:43:40 +0100
commit86cd165c1ad9a72567cf5d033a8ff92779f72b30 (patch)
tree55960f96bd155aaa29ca03c5f79e61f830aa0ae0 /src/main/kotlin/io
parentd249bbdc8e99bfdab81aa6b215e70c4f21def91e (diff)
downloadNotEnoughUpdates-86cd165c1ad9a72567cf5d033a8ff92779f72b30.tar.gz
NotEnoughUpdates-86cd165c1ad9a72567cf5d033a8ff92779f72b30.tar.bz2
NotEnoughUpdates-86cd165c1ad9a72567cf5d033a8ff92779f72b30.zip
Improve NPC shop generator (#650)
Diffstat (limited to 'src/main/kotlin/io')
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemSearchGui.kt152
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemShopExporter.kt139
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporter.kt28
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporters.kt99
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingContext.kt93
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingInterruptedException.kt24
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/Coroutines.kt88
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/gson.kt22
8 files changed, 645 insertions, 0 deletions
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemSearchGui.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemSearchGui.kt
new file mode 100644
index 00000000..2af7975e
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemSearchGui.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.recipes.generators
+
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.util.Utils
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.client.gui.GuiTextField
+import net.minecraft.client.renderer.Tessellator
+import net.minecraft.init.Items
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.client.GuiScrollingList
+
+class ItemSearchGui(initialText: String, val onSelect: (String?) -> Unit) : GuiScreen() {
+ val textField = GuiTextField(
+ 0, Minecraft.getMinecraft().fontRendererObj,
+ -1,
+ 5,
+ 200,
+ 20
+ )
+ var lastFilter = ""
+
+ class ItemScrollingList(
+ screenWidth: Int,
+ screenHeight: Int,
+ val callback: (ItemStack) -> Unit
+ ) : GuiScrollingList(
+ Minecraft.getMinecraft(),
+ 200,
+ screenHeight - 30,
+ 30,
+ screenHeight,
+ screenWidth / 2 - 100,
+ 20,
+ screenWidth,
+ screenHeight
+ ) {
+ var items: List<ItemStack> = listOf()
+ private set
+ var selected: Int? = null
+
+ fun setItems(newItems: List<ItemStack>) {
+ this.items = newItems
+ selected = null
+ }
+
+ override fun getSize(): Int {
+ return items.size
+ }
+
+ override fun elementClicked(i: Int, doubleClick: Boolean) {
+ if (doubleClick)
+ callback(items[i])
+ selected = i
+ }
+
+ override fun isSelected(i: Int): Boolean {
+ return selected == i
+ }
+
+ override fun drawBackground() {
+
+ }
+
+ override fun drawSlot(i: Int, right: Int, top: Int, height: Int, tessellator: Tessellator?) {
+ val item = items[i]
+ Utils.drawItemStack(item, this.left + 1, top + 1)
+ Utils.drawStringF(item.displayName, this.left + 18F, top + 1F, true, 0xFF00FF00.toInt())
+ }
+ }
+
+ lateinit var items: ItemScrollingList
+
+
+ init {
+ textField.text = initialText
+ }
+
+ override fun initGui() {
+ super.initGui()
+ textField.xPosition = width / 2 - 100
+ textField.setCanLoseFocus(false)
+ textField.isFocused = true
+ items = ItemScrollingList(width, height) {
+ val name = NotEnoughUpdates.INSTANCE.manager.createItemResolutionQuery()
+ .withItemStack(it)
+ .resolveInternalName() ?: return@ItemScrollingList
+ onSelect(name)
+ }
+ updateItems()
+ }
+
+ override fun drawScreen(mouseX: Int, mouseY: Int, partialTicks: Float) {
+ super.drawScreen(mouseX, mouseY, partialTicks)
+ drawDefaultBackground()
+ textField.drawTextBox()
+ items.drawScreen(mouseX, mouseY, partialTicks)
+ }
+
+ override fun onGuiClosed() {
+ onSelect(null)
+ }
+ override fun mouseClicked(mouseX: Int, mouseY: Int, mouseButton: Int) {
+ super.mouseClicked(mouseX, mouseY, mouseButton)
+ textField.mouseClicked(mouseX, mouseY, mouseButton)
+ }
+
+ override fun keyTyped(typedChar: Char, keyCode: Int) {
+ super.keyTyped(typedChar, keyCode)
+ textField.textboxKeyTyped(typedChar, keyCode)
+ }
+
+ fun updateItems() {
+ lastFilter = textField.text
+
+ val candidates =
+ NotEnoughUpdates.INSTANCE.manager.search(textField.text)
+ .map {
+ NotEnoughUpdates.INSTANCE.manager.createItemResolutionQuery()
+ .withKnownInternalName(it)
+ .resolveToItemStack() ?: ItemStack(
+ Items.painting, 1, 10
+ )
+ }
+ items.setItems(candidates)
+ }
+
+ override fun updateScreen() {
+ super.updateScreen()
+ textField.updateCursorCounter()
+ if (textField.text != lastFilter) updateItems()
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemShopExporter.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemShopExporter.kt
new file mode 100644
index 00000000..e2673125
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/ItemShopExporter.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.recipes.generators
+
+import com.google.auto.service.AutoService
+import io.github.moulberry.notenoughupdates.recipes.Ingredient
+import io.github.moulberry.notenoughupdates.recipes.ItemShopRecipe
+import io.github.moulberry.notenoughupdates.util.ItemUtils
+import io.github.moulberry.notenoughupdates.util.JsonUtils
+import io.github.moulberry.notenoughupdates.util.SBInfo
+import io.github.moulberry.notenoughupdates.util.Utils
+import io.github.moulberry.notenoughupdates.util.kotlin.set
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.inventory.ContainerChest
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NBTTagCompound
+import net.minecraft.util.StringUtils
+
+@AutoService(RepoExporter::class)
+class ItemShopExporter : RepoExporter {
+ override suspend fun export(context: RepoExportingContext) {
+ val chest = context.gui as GuiChest
+ val container = chest.inventorySlots as ContainerChest
+ val inventory = container.lowerChestInventory
+ val displayName = inventory.displayName.unformattedText
+ val npcInternalName = displayName.uppercase().replace(" ", "_") + "_NPC"
+ val file = context.manager.repoLocation.resolve("items/$npcInternalName.json")
+ val itemDisplayName = "§9$displayName (NPC)"
+ val baseNPCJson = context.manager.createItemJson(
+ npcInternalName,
+ "minecraft:skull",
+ itemDisplayName,
+ arrayOf(""),
+ null,
+ "WIKI_URL",
+ arrayOf("TODO"),
+ "viewrecipe",
+ 3,
+ NBTTagCompound()
+ )
+ baseNPCJson["x"] = context.mc.thePlayer.posX.toInt()
+ baseNPCJson["y"] = context.mc.thePlayer.posY.toInt()
+ baseNPCJson["z"] = context.mc.thePlayer.posZ.toInt()
+ baseNPCJson["island"] = SBInfo.getInstance().getLocation()
+
+ val recipes = mutableListOf<ItemShopRecipe>()
+ for (slotNum in 0 until inventory.sizeInventory) {
+ val slot = inventory.getStackInSlot(slotNum) ?: continue
+ if (slot.displayName.isNullOrBlank()) continue
+
+ recipes.add(findRecipe(context, slot) ?: continue)
+ }
+ baseNPCJson["recipes"] = JsonUtils.transformListToJsonArray(recipes, ItemShopRecipe::serialize)
+ context.writeFile(file, baseNPCJson)
+ }
+
+ val coinRegex = "^([0-9,]+) Coins$".toRegex()
+ val itemRegex = "^(?<name>.*?)(?: x(?<amount>[0-9,]+))?$".toRegex()
+ suspend fun findRecipe(
+ context: RepoExportingContext,
+ stack: ItemStack,
+ ): ItemShopRecipe? {
+ val resultName =
+ context.manager.createItemResolutionQuery().withItemStack(stack).resolveInternalName() ?: return null
+
+ var inCost = false
+ val costList = mutableListOf<Ingredient>()
+ for (loreLine in ItemUtils.getLore(stack)) {
+ var loreLine = loreLine
+ if (loreLine == "§7Cost") {
+ inCost = true
+ continue
+ } else if (loreLine == "§eClick to trade!") {
+ inCost = false
+ continue
+ } else if (loreLine.startsWith("§7Cost: ")) {
+ loreLine = loreLine.substringAfter("§7Cost: ")
+ inCost = true
+ } else if (!inCost) continue
+ val cleanLine = StringUtils.stripControlCodes(loreLine)
+ if (cleanLine.isBlank()) continue
+ val ingredient = findIngredientForLoreLine(context, cleanLine)
+ if (ingredient == null) {
+ Utils.addChatMessage("Warning: Could not find ingredient for $loreLine")
+ } else {
+ costList.add(ingredient)
+ }
+ }
+ return ItemShopRecipe(
+ null, costList, Ingredient(context.manager, resultName, stack.stackSize.toDouble()), null
+ )
+ }
+
+ suspend fun findIngredientForLoreLine(
+ context: RepoExportingContext,
+ loreLine: String,
+ ): Ingredient? {
+ val coinMatch = coinRegex.matchEntire(loreLine)
+ if (coinMatch != null) {
+ val amount = coinMatch.groupValues[1].replace(",", "").toInt()
+ return Ingredient.coinIngredient(context.manager, amount)
+ }
+ val itemMatch = itemRegex.matchEntire(loreLine)
+ if (itemMatch != null) {
+ val label = itemMatch.groups["name"]!!.value
+ val amount = itemMatch.groups["amount"]?.value?.replace(",", "")?.toDouble() ?: 1.0
+ if (context.manager.itemInformation.containsKey(label.uppercase())) {
+ return Ingredient(context.manager, label, amount)
+ }
+ return Ingredient(context.manager, context.findItemByName(label), amount)
+ }
+ return null
+ }
+
+ override fun canExport(gui: GuiScreen): Boolean {
+ return gui is GuiChest
+ }
+
+ override val name: String
+ get() = "Item Shop"
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporter.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporter.kt
new file mode 100644
index 00000000..c03bb498
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporter.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.recipes.generators
+
+import net.minecraft.client.gui.GuiScreen
+
+interface RepoExporter {
+ suspend fun export(context: RepoExportingContext)
+ fun canExport(gui: GuiScreen): Boolean
+ val name: String
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporters.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporters.kt
new file mode 100644
index 00000000..1ffbd696
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExporters.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.recipes.generators
+
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import io.github.moulberry.notenoughupdates.events.GuiContainerBackgroundDrawnEvent
+import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer
+import io.github.moulberry.notenoughupdates.util.Utils
+import io.github.moulberry.notenoughupdates.util.kotlin.Coroutines
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.GuiButton
+import net.minecraft.client.gui.inventory.GuiContainer
+import net.minecraftforge.client.event.GuiScreenEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.lwjgl.input.Mouse
+import java.util.*
+
+@NEUAutoSubscribe
+object RepoExporters {
+ private val allRepoExporters = ServiceLoader.load(RepoExporter::class.java).let {
+ it.reload()
+ it.toList()
+ }
+ private var lastRenderedButtons = listOf<Pair<GuiButton, RepoExporter>>()
+ private var lastGui: GuiContainer? = null
+
+ @SubscribeEvent
+ fun onGuiRender(event: GuiContainerBackgroundDrawnEvent) {
+ if (!NotEnoughUpdates.INSTANCE.config.apiData.repositoryEditing) return
+ val mouseX = Utils.getMouseX()
+ val mouseY = Utils.getMouseY()
+ val gui = event.container
+ if (gui !is AccessorGuiContainer) return
+ val exporters = allRepoExporters.filter { it.canExport(gui) }
+ synchronized(this) {
+ lastGui = gui
+ lastRenderedButtons = exporters.withIndex().map { (index, exporter) ->
+ GuiButton(
+ 0,
+ 0, -30 - 20 * index,
+ gui.xSize, 20,
+ "Run Exporter: ${exporter.name}"
+ ).also {
+ it.drawButton(
+ Minecraft.getMinecraft(),
+ mouseX - gui.guiLeft,
+ mouseY - gui.guiTop
+ )
+ } to exporter
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onGuiClick(event: GuiScreenEvent.MouseInputEvent.Pre) {
+ if (!Mouse.getEventButtonState()) return
+ val accessor = event.gui as? AccessorGuiContainer ?: return
+
+ val mouseX = Utils.getMouseX() - accessor.guiLeft
+ val mouseY = Utils.getMouseY() - accessor.guiTop
+
+ val exporter = synchronized(this) {
+ if (lastGui !== event.gui) return
+ lastRenderedButtons.forEach { (button, exporter) ->
+ if (button.mousePressed(Minecraft.getMinecraft(), mouseX, mouseY)) {
+ return@synchronized exporter
+ }
+ }
+ return
+ }
+ event.isCanceled = true
+ Coroutines.launchCoroutineOnCurrentThread {
+ try {
+ exporter.export(RepoExportingContext(NotEnoughUpdates.INSTANCE.manager, event.gui))
+ Utils.addChatMessage("Repo export completed")
+ } catch (e: RepoExportingInterruptedException) {
+ Utils.addChatMessage("Repo exporting interrupted")
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingContext.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingContext.kt
new file mode 100644
index 00000000..66381242
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingContext.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.recipes.generators
+
+import com.google.gson.JsonObject
+import io.github.moulberry.notenoughupdates.NEUManager
+import io.github.moulberry.notenoughupdates.util.MinecraftExecutor
+import io.github.moulberry.notenoughupdates.util.kotlin.Coroutines.continueOn
+import io.github.moulberry.notenoughupdates.util.kotlin.Coroutines.waitTicks
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.client.gui.GuiYesNo
+import java.io.File
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class RepoExportingContext(
+ val manager: NEUManager,
+ val gui: GuiScreen,
+) {
+ val mc = Minecraft.getMinecraft()
+ val nameToItemCache = mutableMapOf<String, String>()
+
+ suspend fun writeFile(file: File, json: JsonObject) {
+ if (file.exists()) {
+ if (!askYesNo("Overwrite file?", "The file $file already exists."))
+ return
+ }
+ manager.writeJson(json, file)
+ }
+
+ suspend fun askYesNo(title: String, subtitle: String): Boolean {
+ var wasResolved = false
+ continueOn(MinecraftExecutor.OnThread)
+ val result = suspendCoroutine {
+ mc.displayGuiScreen(object : GuiYesNo({ result, id ->
+ if (!wasResolved) {
+ wasResolved = true
+ it.resume(result)
+ }
+ }, title, subtitle, 0) {
+ override fun onGuiClosed() {
+ if (!wasResolved) {
+ wasResolved = true
+ it.resume(null)
+ }
+ }
+ })
+ }
+ if (result == null) {
+ waitTicks(1) // Wait one tick for gui closing animation
+ }
+ mc.displayGuiScreen(gui)
+ return result ?: false
+ }
+
+ suspend fun findItemByName(name: String): String {
+ val c = nameToItemCache[name]
+ if (c != null) return c
+ var wasResolved = false
+ continueOn(MinecraftExecutor.OnThread)
+ val result = suspendCoroutine { cont ->
+ mc.displayGuiScreen(ItemSearchGui(name) {
+ if (!wasResolved) {
+ wasResolved = true
+ cont.resume(it)
+ }
+ })
+ }
+ waitTicks(1)
+ mc.displayGuiScreen(gui)
+ result ?: throw RepoExportingInterruptedException()
+ nameToItemCache[name] = result
+ return result
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingInterruptedException.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingInterruptedException.kt
new file mode 100644
index 00000000..9839f200
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/recipes/generators/RepoExportingInterruptedException.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.recipes.generators
+
+class RepoExportingInterruptedException : Error() {
+
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/Coroutines.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/Coroutines.kt
new file mode 100644
index 00000000..4bcf0c61
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/Coroutines.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.util.kotlin
+
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import kotlin.coroutines.*
+
+@NEUAutoSubscribe
+object Coroutines {
+ fun <T> launchCoroutineOnCurrentThread(block: suspend () -> T): CompletableFuture<T> {
+ val future = CompletableFuture<T>()
+ val coroutine = block.createCoroutine(object : Continuation<T> {
+ override val context: CoroutineContext
+ get() = EmptyCoroutineContext
+
+ override fun resumeWith(result: Result<T>) {
+ try {
+ future.complete(result.getOrThrow())
+ } catch (ex: Throwable) {
+ future.completeExceptionally(ex)
+ }
+ }
+ })
+ coroutine.resume(Unit)
+ return future
+ }
+
+ suspend fun continueOn(ex: Executor) {
+ suspendCoroutine {
+ ex.execute {
+ it.resume(Unit)
+ }
+ }
+ }
+
+ private data class DelayedTask(val contination: () -> Unit, var tickDelay: Int)
+
+ private val tasks = mutableListOf<DelayedTask>()
+
+ @SubscribeEvent
+ fun onTick(event: TickEvent.ClientTickEvent) {
+ if (event.phase == TickEvent.Phase.END) {
+ val toRun = mutableListOf<DelayedTask>()
+ synchronized(tasks) {
+ tasks.removeIf {
+ it.tickDelay--
+ if (it.tickDelay <= 0) {
+ toRun.add(it)
+ return@removeIf true
+ }
+ false
+ }
+ }
+ toRun.forEach { it.contination() }
+ }
+ }
+
+ suspend fun waitTicks(tickCount: Int) {
+ suspendCoroutine {
+ synchronized(tasks) {
+ tasks.add(DelayedTask({ it.resume(Unit) }, tickCount))
+ }
+ }
+ }
+
+
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/gson.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/gson.kt
index 00c3d729..ec2e3c31 100644
--- a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/gson.kt
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/gson.kt
@@ -20,6 +20,8 @@
package io.github.moulberry.notenoughupdates.util.kotlin
import com.google.gson.Gson
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
@@ -31,3 +33,23 @@ inline fun <reified T : Any> typeToken(): Type {
inline fun <reified T : Any> Gson.fromJson(string: String): T {
return fromJson(string, typeToken<T>())
}
+
+operator fun JsonObject.set(name: String, value: Number) {
+ this.addProperty(name, value)
+}
+
+operator fun JsonObject.set(name: String, value: String) {
+ this.addProperty(name, value)
+}
+
+operator fun JsonObject.set(name: String, value: Boolean) {
+ this.addProperty(name, value)
+}
+
+operator fun JsonObject.set(name: String, value: Char) {
+ this.addProperty(name, value)
+}
+
+operator fun JsonObject.set(name: String, value: JsonElement) {
+ this.add(name, value)
+}