aboutsummaryrefslogtreecommitdiff
path: root/src/compat/rei
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-10-28 12:07:55 +0100
committerLinnea Gräf <nea@nea.moe>2024-10-28 12:07:55 +0100
commitc38dcee2c5f483ef5990ae9204355e1bc3c2bf74 (patch)
tree3b70359076001e14514c496e3c44980fdbd92d01 /src/compat/rei
parent8ab44088546bf3360564e1a09f0831fea7659d2e (diff)
downloadFirmament-c38dcee2c5f483ef5990ae9204355e1bc3c2bf74.tar.gz
Firmament-c38dcee2c5f483ef5990ae9204355e1bc3c2bf74.tar.bz2
Firmament-c38dcee2c5f483ef5990ae9204355e1bc3c2bf74.zip
Make REI optional
Diffstat (limited to 'src/compat/rei')
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt35
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt143
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/HoveredItemStackProvider.kt37
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt187
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntrySerializer.kt30
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt94
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt65
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt25
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/math.kt10
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt55
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt62
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt71
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt224
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt108
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBRecipe.kt29
15 files changed, 1175 insertions, 0 deletions
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt
new file mode 100644
index 0000000..9b7b190
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt
@@ -0,0 +1,35 @@
+package moe.nea.firmament.compat.rei
+
+import me.shedaniel.math.Dimension
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds
+import moe.nea.firmament.gui.entity.EntityRenderer
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.gui.Element
+import net.minecraft.entity.LivingEntity
+
+class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() {
+ override fun children(): List<Element> {
+ return emptyList()
+ }
+
+ var hasErrored = false
+
+ override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ try {
+ if (!hasErrored)
+ EntityRenderer.renderEntity(entity, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat())
+ } catch (ex: Exception) {
+ EntityRenderer.logger.error("Failed to render constructed entity: $entity", ex)
+ hasErrored = true
+ }
+ if (hasErrored) {
+ context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt())
+ }
+ }
+
+ override fun getBounds(): Rectangle {
+ return Rectangle(point, Dimension(50, 80))
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt
new file mode 100644
index 0000000..d95d2d1
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt
@@ -0,0 +1,143 @@
+package moe.nea.firmament.compat.rei
+
+import me.shedaniel.rei.api.client.plugins.REIClientPlugin
+import me.shedaniel.rei.api.client.registry.category.CategoryRegistry
+import me.shedaniel.rei.api.client.registry.display.DisplayRegistry
+import me.shedaniel.rei.api.client.registry.entry.CollapsibleEntryRegistry
+import me.shedaniel.rei.api.client.registry.entry.EntryRegistry
+import me.shedaniel.rei.api.client.registry.screen.ExclusionZones
+import me.shedaniel.rei.api.client.registry.screen.OverlayDecider
+import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry
+import me.shedaniel.rei.api.client.registry.transfer.TransferHandler
+import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry
+import me.shedaniel.rei.api.common.entry.EntryStack
+import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry
+import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
+import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.item.ItemStack
+import net.minecraft.text.Text
+import net.minecraft.util.ActionResult
+import net.minecraft.util.Identifier
+import moe.nea.firmament.events.HandledScreenPushREIEvent
+import moe.nea.firmament.features.inventory.CraftingOverlay
+import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
+import moe.nea.firmament.compat.rei.recipes.SBCraftingRecipe
+import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
+import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
+import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
+import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.ScreenUtil
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.guessRecipeId
+import moe.nea.firmament.util.skyblockId
+import moe.nea.firmament.util.unformattedString
+
+
+class FirmamentReiPlugin : REIClientPlugin {
+
+ companion object {
+ fun EntryStack<SBItemStack>.asItemEntry(): EntryStack<ItemStack> {
+ return EntryStack.of(VanillaEntryTypes.ITEM, value.asImmutableItemStack())
+ }
+
+ val SKYBLOCK_ITEM_TYPE_ID = Identifier.of("firmament", "skyblockitems")
+ }
+
+ override fun registerTransferHandlers(registry: TransferHandlerRegistry) {
+ registry.register(TransferHandler { context ->
+ val screen = context.containerScreen
+ val display = context.display
+ if (display !is SBCraftingRecipe) return@TransferHandler TransferHandler.Result.createNotApplicable()
+ val neuItem = RepoManager.getNEUItem(SkyblockId(display.neuRecipe.output.itemId))
+ ?: error("Could not find neu item ${display.neuRecipe.output.itemId} which is used in a recipe output")
+ val useSuperCraft = context.isStackedCrafting || RepoManager.Config.alwaysSuperCraft
+ if (neuItem.isVanilla && useSuperCraft) return@TransferHandler TransferHandler.Result.createFailed(Text.translatable(
+ "firmament.recipe.novanilla"))
+ var shouldReturn = true
+ if (context.isActuallyCrafting && !useSuperCraft) {
+ if (screen !is GenericContainerScreen || screen.title?.unformattedString != CraftingOverlay.CRAFTING_SCREEN_NAME) {
+ MC.sendCommand("craft")
+ shouldReturn = false
+ }
+ CraftingOverlay.setOverlay(screen as? GenericContainerScreen, display.neuRecipe)
+ }
+ if (context.isActuallyCrafting && useSuperCraft) {
+ shouldReturn = false
+ MC.sendCommand("viewrecipe ${neuItem.guessRecipeId()}")
+ }
+ return@TransferHandler TransferHandler.Result.createSuccessful().blocksFurtherHandling(shouldReturn)
+ })
+ }
+
+ override fun registerEntryTypes(registry: EntryTypeRegistry) {
+ registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition)
+ }
+
+ override fun registerCategories(registry: CategoryRegistry) {
+ registry.add(SBCraftingRecipe.Category)
+ registry.add(SBForgeRecipe.Category)
+ registry.add(SBMobDropRecipe.Category)
+ registry.add(SBKatRecipe.Category)
+ registry.add(SBEssenceUpgradeRecipe.Category)
+ }
+
+ override fun registerExclusionZones(zones: ExclusionZones) {
+ zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent.publish(HandledScreenPushREIEvent(it)).rectangles }
+ zones.register(StorageOverlayScreen::class.java) { it.getBounds() }
+ }
+
+ override fun registerDisplays(registry: DisplayRegistry) {
+ registry.registerDisplayGenerator(
+ SBCraftingRecipe.Category.catIdentifier,
+ SkyblockCraftingRecipeDynamicGenerator)
+ registry.registerDisplayGenerator(
+ SBForgeRecipe.Category.categoryIdentifier,
+ SkyblockForgeRecipeDynamicGenerator)
+ registry.registerDisplayGenerator(
+ SBMobDropRecipe.Category.categoryIdentifier,
+ SkyblockMobDropRecipeDynamicGenerator)
+ registry.registerDisplayGenerator(
+ SBKatRecipe.Category.categoryIdentifier,
+ SkyblockKatRecipeDynamicGenerator)
+ registry.registerDisplayGenerator(
+ SBEssenceUpgradeRecipe.Category.categoryIdentifier,
+ SkyblockEssenceRecipeDynamicGenerator
+ )
+ }
+
+ override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) {
+ if (!RepoManager.Config.disableItemGroups)
+ RepoManager.neuRepo.constants.parents.parents
+ .forEach { (parent, children) ->
+ registry.group(
+ SkyblockId(parent).identifier,
+ Text.literal(RepoManager.getNEUItem(SkyblockId(parent))?.displayName ?: parent),
+ (children + parent).map { SBItemEntryDefinition.getEntry(SkyblockId(it)) })
+ }
+ }
+
+ override fun registerScreens(registry: ScreenRegistry) {
+ registry.registerDecider(object : OverlayDecider {
+ override fun <R : Screen?> isHandingScreen(screen: Class<R>?): Boolean {
+ return screen == StorageOverlayScreen::class.java
+ }
+
+ override fun <R : Screen?> shouldScreenBeOverlaid(screen: R): ActionResult {
+ return ActionResult.SUCCESS
+ }
+ })
+ registry.registerFocusedStack(SkyblockItemIdFocusedStackProvider)
+ }
+
+ override fun registerEntries(registry: EntryRegistry) {
+ registry.removeEntryIf { true }
+ RepoManager.neuRepo.items?.items?.values?.forEach { neuItem ->
+ registry.addEntry(SBItemEntryDefinition.getEntry(neuItem.skyblockId))
+ }
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/HoveredItemStackProvider.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/HoveredItemStackProvider.kt
new file mode 100644
index 0000000..3d21b66
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/HoveredItemStackProvider.kt
@@ -0,0 +1,37 @@
+package moe.nea.firmament.compat.rei
+
+import com.google.auto.service.AutoService
+import me.shedaniel.math.impl.PointHelper
+import me.shedaniel.rei.api.client.REIRuntime
+import me.shedaniel.rei.api.client.gui.widgets.Slot
+import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry
+import net.minecraft.client.gui.Element
+import net.minecraft.client.gui.ParentElement
+import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.util.HoveredItemStackProvider
+import moe.nea.firmament.util.compatloader.CompatLoader
+
+@AutoService(HoveredItemStackProvider::class)
+@CompatLoader.RequireMod("roughlyenoughitems")
+class ScreenRegistryHoveredItemStackProvider : HoveredItemStackProvider {
+ override fun provideHoveredItemStack(screen: HandledScreen<*>): ItemStack? {
+ val entryStack = ScreenRegistry.getInstance().getFocusedStack(screen, PointHelper.ofMouse())
+ ?: return null
+ return entryStack.value as? ItemStack ?: entryStack.cheatsAs().value
+ }
+}
+@AutoService(HoveredItemStackProvider::class)
+@CompatLoader.RequireMod("roughlyenoughitems")
+class OverlayHoveredItemStackProvider : HoveredItemStackProvider {
+ override fun provideHoveredItemStack(screen: HandledScreen<*>): ItemStack? {
+ var baseElement: Element? = REIRuntime.getInstance().overlay.orElse(null)
+ val mx = PointHelper.getMouseFloatingX()
+ val my = PointHelper.getMouseFloatingY()
+ while (true) {
+ if (baseElement is Slot) return baseElement.currentEntry.cheatsAs().value
+ if (baseElement !is ParentElement) return null
+ baseElement = baseElement.hoveredElement(mx, my).orElse(null)
+ }
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt
new file mode 100644
index 0000000..a02742b
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntryRenderer.kt
@@ -0,0 +1,187 @@
+/*
+ * SPDX-FileCopyrightText: 2018-2023 shedaniel <daniel@shedaniel.me>
+ * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-License-Identifier: MIT
+ */
+
+package moe.nea.firmament.compat.rei
+
+import com.mojang.blaze3d.platform.GlStateManager.DstFactor
+import com.mojang.blaze3d.platform.GlStateManager.SrcFactor
+import com.mojang.blaze3d.systems.RenderSystem
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.entry.renderer.BatchedEntryRenderer
+import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer
+import me.shedaniel.rei.api.client.gui.widgets.Tooltip
+import me.shedaniel.rei.api.client.gui.widgets.TooltipContext
+import me.shedaniel.rei.api.common.entry.EntryStack
+import net.minecraft.client.MinecraftClient
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.render.DiffuseLighting
+import net.minecraft.client.render.LightmapTextureManager
+import net.minecraft.client.render.OverlayTexture
+import net.minecraft.client.render.VertexConsumerProvider
+import net.minecraft.client.render.model.BakedModel
+import net.minecraft.client.render.model.json.ModelTransformationMode
+import net.minecraft.client.texture.SpriteAtlasTexture
+import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
+import net.minecraft.item.tooltip.TooltipType
+import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
+import moe.nea.firmament.repo.SBItemStack
+
+object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<SBItemStack, BakedModel> {
+ override fun render(
+ entry: EntryStack<SBItemStack>,
+ context: DrawContext,
+ bounds: Rectangle,
+ mouseX: Int,
+ mouseY: Int,
+ delta: Float
+ ) {
+ entry.asItemEntry().render(context, bounds, mouseX, mouseY, delta)
+ }
+
+ val minecraft = MinecraftClient.getInstance()
+
+ override fun getTooltip(entry: EntryStack<SBItemStack>, tooltipContext: TooltipContext): Tooltip? {
+ val stack = entry.value.asImmutableItemStack()
+ val lore = stack.getTooltip(
+ Item.TooltipContext.DEFAULT,
+ null,
+ TooltipType.BASIC
+ )
+ return Tooltip.create(lore)
+ }
+
+ override fun getExtraData(entry: EntryStack<SBItemStack>): BakedModel {
+ return minecraft.itemRenderer.getModel(entry.asItemEntry().value, minecraft.world, minecraft.player, 0)
+ }
+
+ override fun getBatchIdentifier(entry: EntryStack<SBItemStack>?, bounds: Rectangle?, extraData: BakedModel): Int {
+ return 1738923 + if (extraData.isSideLit) 1 else 0
+ }
+
+ override fun startBatch(
+ entry: EntryStack<SBItemStack>,
+ model: BakedModel,
+ graphics: DrawContext,
+ delta: Float
+ ) {
+ val modelViewStack = RenderSystem.getModelViewStack()
+ modelViewStack.pushMatrix()
+ modelViewStack.scale(20.0f, 20.0f, 1.0f)
+ RenderSystem.applyModelViewMatrix()
+ setupGL(model)
+ }
+
+ fun setupGL(model: BakedModel) {
+ minecraft.textureManager.getTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE)
+ .setFilter(false, false)
+ RenderSystem.setShaderTexture(0, SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE)
+ RenderSystem.enableBlend()
+ RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA)
+ RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
+ val sideLit = model.isSideLit
+ if (!sideLit) {
+ DiffuseLighting.disableGuiDepthLighting()
+ }
+ }
+
+ override fun renderBase(
+ entry: EntryStack<SBItemStack>,
+ model: BakedModel,
+ graphics: DrawContext,
+ immediate: VertexConsumerProvider.Immediate,
+ bounds: Rectangle,
+ mouseX: Int,
+ mouseY: Int,
+ delta: Float
+ ) {
+ if (entry.isEmpty) return
+ val value = entry.asItemEntry().value
+ graphics.matrices.push()
+ graphics.matrices.translate(bounds.centerX.toFloat() / 20.0f, bounds.centerY.toFloat() / 20.0f, 0.0f)
+ graphics.matrices.scale(
+ bounds.getWidth().toFloat() / 20.0f,
+ -(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 20.0f,
+ 1.0f
+ )
+ minecraft
+ .itemRenderer
+ .renderItem(
+ value,
+ ModelTransformationMode.GUI,
+ false,
+ graphics.matrices,
+ immediate,
+ LightmapTextureManager.MAX_LIGHT_COORDINATE,
+ OverlayTexture.DEFAULT_UV,
+ model
+ )
+ graphics.matrices.pop()
+
+ }
+
+ override fun afterBase(
+ entry: EntryStack<SBItemStack>,
+ model: BakedModel,
+ graphics: DrawContext,
+ delta: Float
+ ) {
+ RenderSystem.getModelViewStack().popMatrix()
+ RenderSystem.applyModelViewMatrix()
+ this.endGL(model)
+ }
+
+ fun endGL(model: BakedModel) {
+ RenderSystem.enableDepthTest()
+ val sideLit = model.isSideLit
+ if (!sideLit) {
+ DiffuseLighting.enableGuiDepthLighting()
+ }
+ }
+
+ override fun renderOverlay(
+ entry: EntryStack<SBItemStack>,
+ extraData: BakedModel,
+ graphics: DrawContext,
+ immediate: VertexConsumerProvider.Immediate,
+ bounds: Rectangle,
+ mouseX: Int,
+ mouseY: Int,
+ delta: Float
+ ) {
+ val modelViewStack = RenderSystem.getModelViewStack()
+ modelViewStack.pushMatrix()
+ modelViewStack.mul(graphics.matrices.peek().positionMatrix)
+ modelViewStack.translate(bounds.x.toFloat(), bounds.y.toFloat(), 0.0f)
+ modelViewStack.scale(
+ bounds.width.toFloat() / 16.0f,
+ -(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 16.0f,
+ 1.0f
+ )
+ RenderSystem.applyModelViewMatrix()
+ renderOverlay(DrawContext(minecraft, graphics.vertexConsumers), entry.asItemEntry())
+ modelViewStack.popMatrix()
+ RenderSystem.applyModelViewMatrix()
+ }
+
+ fun renderOverlay(graphics: DrawContext, entry: EntryStack<ItemStack>) {
+ if (!entry.isEmpty) {
+ graphics.drawItemInSlot(MinecraftClient.getInstance().textRenderer, entry.value, 0, 0, null)
+ }
+ }
+
+ override fun endBatch(
+ entry: EntryStack<SBItemStack>?,
+ extraData: BakedModel?,
+ graphics: DrawContext?,
+ delta: Float
+ ) {
+ }
+
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntrySerializer.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntrySerializer.kt
new file mode 100644
index 0000000..6a03a48
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/NEUItemEntrySerializer.kt
@@ -0,0 +1,30 @@
+
+
+package moe.nea.firmament.compat.rei
+
+import me.shedaniel.rei.api.common.entry.EntrySerializer
+import me.shedaniel.rei.api.common.entry.EntryStack
+import net.minecraft.nbt.NbtCompound
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.SkyblockId
+
+object NEUItemEntrySerializer : EntrySerializer<SBItemStack> {
+ const val SKYBLOCK_ID_ENTRY = "SKYBLOCK_ID"
+ const val SKYBLOCK_ITEM_COUNT = "SKYBLOCK_ITEM_COUNT"
+
+ override fun supportSaving(): Boolean = true
+ override fun supportReading(): Boolean = true
+
+ override fun read(tag: NbtCompound): SBItemStack {
+ val id = SkyblockId(tag.getString(SKYBLOCK_ID_ENTRY))
+ val count = if (tag.contains(SKYBLOCK_ITEM_COUNT)) tag.getInt(SKYBLOCK_ITEM_COUNT) else 1
+ return SBItemStack(id, count)
+ }
+
+ override fun save(entry: EntryStack<SBItemStack>, value: SBItemStack): NbtCompound {
+ return NbtCompound().apply {
+ putString(SKYBLOCK_ID_ENTRY, value.skyblockId.neuItem)
+ putInt(SKYBLOCK_ITEM_COUNT, value.getStackSize())
+ }
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt
new file mode 100644
index 0000000..a242c1b
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SBItemEntryDefinition.kt
@@ -0,0 +1,94 @@
+package moe.nea.firmament.compat.rei
+
+import io.github.moulberry.repo.data.NEUIngredient
+import java.util.stream.Stream
+import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer
+import me.shedaniel.rei.api.common.entry.EntrySerializer
+import me.shedaniel.rei.api.common.entry.EntryStack
+import me.shedaniel.rei.api.common.entry.comparison.ComparisonContext
+import me.shedaniel.rei.api.common.entry.type.EntryDefinition
+import me.shedaniel.rei.api.common.entry.type.EntryType
+import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes
+import net.minecraft.item.ItemStack
+import net.minecraft.registry.tag.TagKey
+import net.minecraft.text.Text
+import net.minecraft.util.Identifier
+import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
+import moe.nea.firmament.repo.PetData
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.petData
+import moe.nea.firmament.util.skyBlockId
+
+object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
+ override fun equals(o1: SBItemStack, o2: SBItemStack, context: ComparisonContext): Boolean {
+ return o1.skyblockId == o2.skyblockId && o1.getStackSize() == o2.getStackSize()
+ }
+
+ override fun cheatsAs(entry: EntryStack<SBItemStack>?, value: SBItemStack): ItemStack {
+ return value.asCopiedItemStack()
+ }
+
+ override fun getValueType(): Class<SBItemStack> = SBItemStack::class.java
+ override fun getType(): EntryType<SBItemStack> = EntryType.deferred(FirmamentReiPlugin.SKYBLOCK_ITEM_TYPE_ID)
+
+ override fun getRenderer(): EntryRenderer<SBItemStack> = NEUItemEntryRenderer
+
+ override fun getSerializer(): EntrySerializer<SBItemStack> {
+ return NEUItemEntrySerializer
+ }
+
+ override fun getTagsFor(entry: EntryStack<SBItemStack>?, value: SBItemStack?): Stream<out TagKey<*>>? {
+ return Stream.empty()
+ }
+
+ override fun asFormattedText(entry: EntryStack<SBItemStack>, value: SBItemStack): Text {
+ return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asImmutableItemStack())
+ }
+
+ override fun hash(entry: EntryStack<SBItemStack>, value: SBItemStack, context: ComparisonContext): Long {
+ // Repo items are immutable, and get replaced entirely when loaded from disk
+ return value.skyblockId.hashCode() * 31L
+ }
+
+ override fun wildcard(entry: EntryStack<SBItemStack>?, value: SBItemStack): SBItemStack {
+ return value.copy(stackSize = 1, petData = RepoManager.getPotentialStubPetData(value.skyblockId),
+ stars = 0, extraLore = listOf())
+ }
+
+ override fun normalize(entry: EntryStack<SBItemStack>?, value: SBItemStack): SBItemStack {
+ return wildcard(entry, value)
+ }
+
+ override fun copy(entry: EntryStack<SBItemStack>?, value: SBItemStack): SBItemStack {
+ return value
+ }
+
+ override fun isEmpty(entry: EntryStack<SBItemStack>?, value: SBItemStack): Boolean {
+ return value.getStackSize() == 0
+ }
+
+ override fun getIdentifier(entry: EntryStack<SBItemStack>?, value: SBItemStack): Identifier {
+ return value.skyblockId.identifier
+ }
+
+ fun getEntry(sbItemStack: SBItemStack): EntryStack<SBItemStack> =
+ EntryStack.of(this, sbItemStack)
+
+ fun getEntry(skyblockId: SkyblockId, count: Int = 1): EntryStack<SBItemStack> =
+ getEntry(SBItemStack(skyblockId, count))
+
+ fun getEntry(ingredient: NEUIngredient): EntryStack<SBItemStack> =
+ getEntry(SkyblockId(ingredient.itemId), count = ingredient.amount.toInt())
+
+ fun getEntry(stack: ItemStack): EntryStack<SBItemStack> =
+ getEntry(
+ SBItemStack(
+ stack.skyBlockId ?: SkyblockId.NULL,
+ RepoManager.getNEUItem(stack.skyBlockId ?: SkyblockId.NULL),
+ stack.count,
+ petData = stack.petData?.let { PetData.fromHypixel(it) }
+ )
+ )
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt
new file mode 100644
index 0000000..f52f418
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt
@@ -0,0 +1,65 @@
+
+
+package moe.nea.firmament.compat.rei
+
+import io.github.moulberry.repo.data.NEUCraftingRecipe
+import io.github.moulberry.repo.data.NEUForgeRecipe
+import io.github.moulberry.repo.data.NEUKatUpgradeRecipe
+import io.github.moulberry.repo.data.NEUMobDropRecipe
+import io.github.moulberry.repo.data.NEURecipe
+import java.util.Optional
+import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator
+import me.shedaniel.rei.api.client.view.ViewSearchBuilder
+import me.shedaniel.rei.api.common.display.Display
+import me.shedaniel.rei.api.common.entry.EntryStack
+import moe.nea.firmament.compat.rei.recipes.SBCraftingRecipe
+import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
+import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
+import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
+import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
+import moe.nea.firmament.repo.EssenceRecipeProvider
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.repo.SBItemStack
+
+
+val SkyblockCraftingRecipeDynamicGenerator =
+ neuDisplayGenerator<SBCraftingRecipe, NEUCraftingRecipe> { SBCraftingRecipe(it) }
+
+val SkyblockForgeRecipeDynamicGenerator =
+ neuDisplayGenerator<SBForgeRecipe, NEUForgeRecipe> { SBForgeRecipe(it) }
+
+val SkyblockMobDropRecipeDynamicGenerator =
+ neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> { SBMobDropRecipe(it) }
+
+val SkyblockKatRecipeDynamicGenerator =
+ neuDisplayGenerator<SBKatRecipe, NEUKatUpgradeRecipe> { SBKatRecipe(it) }
+val SkyblockEssenceRecipeDynamicGenerator =
+ neuDisplayGenerator<SBEssenceUpgradeRecipe, EssenceRecipeProvider.EssenceUpgradeRecipe> { SBEssenceUpgradeRecipe(it) }
+
+inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(crossinline mapper: (T) -> D) =
+ object : DynamicDisplayGenerator<D> {
+ override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> {
+ if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
+ val item = entry.castValue<SBItemStack>()
+ val recipes = RepoManager.getRecipesFor(item.skyblockId)
+ val craftingRecipes = recipes.filterIsInstance<T>()
+ return Optional.of(craftingRecipes.map(mapper))
+ }
+
+ override fun generate(builder: ViewSearchBuilder): Optional<List<D>> {
+ if (SBCraftingRecipe.Category.catIdentifier !in builder.categories) return Optional.empty()
+ return Optional.of(
+ RepoManager.getAllRecipes().filterIsInstance<T>().map { mapper(it) }
+ .toList()
+ )
+ }
+
+ override fun getUsageFor(entry: EntryStack<*>): Optional<List<D>> {
+ if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
+ val item = entry.castValue<SBItemStack>()
+ val recipes = RepoManager.getUsagesFor(item.skyblockId)
+ val craftingRecipes = recipes.filterIsInstance<T>()
+ return Optional.of(craftingRecipes.map(mapper))
+
+ }
+ }
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt
new file mode 100644
index 0000000..cfb6f74
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt
@@ -0,0 +1,25 @@
+
+
+package moe.nea.firmament.compat.rei
+
+import dev.architectury.event.CompoundEventResult
+import me.shedaniel.math.Point
+import me.shedaniel.rei.api.client.registry.screen.FocusedStackProvider
+import me.shedaniel.rei.api.common.entry.EntryStack
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.gui.screen.ingame.HandledScreen
+import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
+import moe.nea.firmament.util.skyBlockId
+
+object SkyblockItemIdFocusedStackProvider : FocusedStackProvider {
+ override fun provide(screen: Screen?, mouse: Point?): CompoundEventResult<EntryStack<*>> {
+ if (screen !is HandledScreen<*>) return CompoundEventResult.pass()
+ screen as AccessorHandledScreen
+ val focusedSlot = screen.focusedSlot_Firmament ?: return CompoundEventResult.pass()
+ val item = focusedSlot.stack ?: return CompoundEventResult.pass()
+ val skyblockId = item.skyBlockId ?: return CompoundEventResult.pass()
+ return CompoundEventResult.interruptTrue(SBItemEntryDefinition.getEntry(skyblockId))
+ }
+
+ override fun getPriority(): Double = 1_000_000.0
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/math.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/math.kt
new file mode 100644
index 0000000..7db36f2
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/math.kt
@@ -0,0 +1,10 @@
+
+
+package moe.nea.firmament.compat.rei
+
+import me.shedaniel.math.Point
+
+operator fun Point.plus(other: Point): Point = Point(
+ this.x + other.x,
+ this.y + other.y,
+)
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt
new file mode 100644
index 0000000..ed18c6e
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt
@@ -0,0 +1,55 @@
+
+
+package moe.nea.firmament.compat.rei.recipes
+
+import io.github.moulberry.repo.data.NEUCraftingRecipe
+import io.github.moulberry.repo.data.NEUIngredient
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.Renderer
+import me.shedaniel.rei.api.client.gui.widgets.Widget
+import me.shedaniel.rei.api.client.gui.widgets.Widgets
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import me.shedaniel.rei.api.common.util.EntryStacks
+import net.minecraft.block.Blocks
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+
+class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() {
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier
+
+ object Category : DisplayCategory<SBCraftingRecipe> {
+ val catIdentifier = CategoryIdentifier.of<SBCraftingRecipe>(Firmament.MOD_ID, "crafing_recipe")
+ override fun getCategoryIdentifier(): CategoryIdentifier<out SBCraftingRecipe> = catIdentifier
+
+ override fun getTitle(): Text = Text.literal("SkyBlock Crafting")
+
+ override fun getIcon(): Renderer = EntryStacks.of(Blocks.CRAFTING_TABLE)
+ override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List<Widget> {
+ val point = Point(bounds.centerX - 58, bounds.centerY - 27)
+ return buildList {
+ add(Widgets.createRecipeBase(bounds))
+ add(Widgets.createArrow(Point(point.x + 60, point.y + 18)))
+ add(Widgets.createResultSlotBackground(Point(point.x + 95, point.y + 19)))
+ for (i in 0 until 3) {
+ for (j in 0 until 3) {
+ val slot = Widgets.createSlot(Point(point.x + 1 + i * 18, point.y + 1 + j * 18)).markInput()
+ add(slot)
+ val item = display.neuRecipe.inputs[i + j * 3]
+ if (item == NEUIngredient.SENTINEL_EMPTY) continue
+ slot.entry(SBItemEntryDefinition.getEntry(item)) // TODO: make use of stackable item entries
+ }
+ }
+ add(
+ Widgets.createSlot(Point(point.x + 95, point.y + 19))
+ .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.output))
+ .disableBackground().markOutput()
+ )
+ }
+ }
+
+ }
+
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt
new file mode 100644
index 0000000..f81d529
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt
@@ -0,0 +1,62 @@
+
+package moe.nea.firmament.compat.rei.recipes
+
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.Renderer
+import me.shedaniel.rei.api.client.gui.widgets.Widget
+import me.shedaniel.rei.api.client.gui.widgets.Widgets
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.repo.EssenceRecipeProvider
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.SkyblockId
+
+class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe) : SBRecipe() {
+ object Category : DisplayCategory<SBEssenceUpgradeRecipe> {
+ override fun getCategoryIdentifier(): CategoryIdentifier<SBEssenceUpgradeRecipe> =
+ CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade")
+
+ override fun getTitle(): Text {
+ return Text.literal("Essence Upgrades")
+ }
+
+ override fun getIcon(): Renderer {
+ return SBItemEntryDefinition.getEntry(SkyblockId("ESSENCE_WITHER"))
+ }
+
+ override fun setupDisplay(display: SBEssenceUpgradeRecipe, bounds: Rectangle): List<Widget> {
+ val recipe = display.neuRecipe
+ val list = mutableListOf<Widget>()
+ list.add(Widgets.createRecipeBase(bounds))
+ list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 - 18 / 2))
+ .markInput()
+ .entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter - 1))))
+ list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 + 18 / 2))
+ .markInput()
+ .entry(SBItemEntryDefinition.getEntry(recipe.essenceIngredient)))
+ list.add(Widgets.createSlot(Point(bounds.maxX - 12 - 16, bounds.centerY - 8))
+ .markOutput()
+ .entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter))))
+ val extraItems = recipe.extraItems
+ list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2,
+ if (extraItems.isEmpty()) bounds.centerY - 17 / 2
+ else bounds.centerY + 18 / 2)))
+ for ((index, item) in extraItems.withIndex()) {
+ list.add(Widgets.createSlot(
+ Point(bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18,
+ bounds.centerY - 18 / 2))
+ .markInput()
+ .entry(SBItemEntryDefinition.getEntry(item)))
+ }
+ return list
+ }
+ }
+
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> {
+ return Category.categoryIdentifier
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt
new file mode 100644
index 0000000..bb51021
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt
@@ -0,0 +1,71 @@
+
+
+package moe.nea.firmament.compat.rei.recipes
+
+import io.github.moulberry.repo.data.NEUForgeRecipe
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.Renderer
+import me.shedaniel.rei.api.client.gui.widgets.Widget
+import me.shedaniel.rei.api.client.gui.widgets.Widgets
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import me.shedaniel.rei.api.common.util.EntryStacks
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.block.Blocks
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.compat.rei.plus
+
+class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() {
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
+
+ object Category : DisplayCategory<SBForgeRecipe> {
+ override fun getCategoryIdentifier(): CategoryIdentifier<SBForgeRecipe> =
+ CategoryIdentifier.of(Firmament.MOD_ID, "forge_recipe")
+
+ override fun getTitle(): Text = Text.literal("Forge Recipes")
+ override fun getDisplayHeight(): Int {
+ return 104
+ }
+
+ override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL)
+ override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List<Widget> {
+ return buildList {
+ add(Widgets.createRecipeBase(bounds))
+ add(Widgets.createResultSlotBackground(Point(bounds.minX + 124, bounds.minY + 46)))
+ val arrow = Widgets.createArrow(Point(bounds.minX + 90, bounds.minY + 54 - 18 / 2))
+ add(arrow)
+ add(Widgets.createTooltip(arrow.bounds, Text.stringifiedTranslatable("firmament.recipe.forge.time", display.neuRecipe.duration.seconds)))
+ val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8)
+ val count = display.neuRecipe.inputs.size
+ if (count == 1) {
+ add(
+ Widgets.createSlot(Point(ingredientsCenter.x, ingredientsCenter.y)).markInput()
+ .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.inputs.single()))
+ )
+ } else {
+ display.neuRecipe.inputs.forEachIndexed { idx, ingredient ->
+ val rad = Math.PI * 2 * idx / count
+ add(
+ Widgets.createSlot(
+ Point(
+ cos(rad) * 30,
+ sin(rad) * 30,
+ ) + ingredientsCenter
+ ).markInput().entry(SBItemEntryDefinition.getEntry(ingredient))
+ )
+ }
+ }
+ add(
+ Widgets.createSlot(Point(bounds.minX + 124, bounds.minY + 46)).markOutput().disableBackground()
+ .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.outputStack))
+ )
+ }
+ }
+ }
+
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt
new file mode 100644
index 0000000..fc77fa6
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBKatRecipe.kt
@@ -0,0 +1,224 @@
+
+package moe.nea.firmament.compat.rei.recipes
+
+import io.github.moulberry.repo.data.NEUKatUpgradeRecipe
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.MouseEvent
+import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import io.github.notenoughupdates.moulconfig.observer.Property
+import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.Renderer
+import me.shedaniel.rei.api.client.gui.widgets.Widget
+import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds
+import me.shedaniel.rei.api.client.gui.widgets.Widgets
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import me.shedaniel.rei.api.common.util.EntryStacks
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.block.Blocks
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.gui.Element
+import net.minecraft.item.Items
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.repo.PetData
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SkyblockId
+
+class SBKatRecipe(override val neuRecipe: NEUKatUpgradeRecipe) : SBRecipe() {
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
+
+ object Category : DisplayCategory<SBKatRecipe> {
+ override fun getCategoryIdentifier(): CategoryIdentifier<SBKatRecipe> =
+ CategoryIdentifier.of(Firmament.MOD_ID, "kat_recipe")
+
+ override fun getTitle(): Text = Text.literal("Kat Pet Upgrade")
+ override fun getDisplayHeight(): Int {
+ return 100
+ }
+
+ override fun getIcon(): Renderer = EntryStacks.of(Items.BONE)
+ override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List<Widget> {
+ return buildList {
+ val arrowWidth = 24
+ val recipe = display.neuRecipe
+ val levelValue = Property.upgrade(GetSetter.floating(0F))
+ val slider = SliderComponent(levelValue, 1F, 100F, 1f, 100)
+ val outputStack = SBItemStack(SkyblockId(recipe.output.itemId))
+ val inputStack = SBItemStack(SkyblockId(recipe.input.itemId))
+ val inputLevelLabelCenter = Point(bounds.minX + 30 - 18 + 5 + 8, bounds.minY + 25)
+ val inputLevelLabel = Widgets.createLabel(
+ inputLevelLabelCenter,
+ Text.literal("")).centered()
+ val outputLevelLabelCenter = Point(bounds.maxX - 30 + 8, bounds.minY + 25)
+ val outputLevelLabel = Widgets.createLabel(
+ outputLevelLabelCenter,
+ Text.literal("")).centered()
+ val coinStack = SBItemStack(SkyblockId.COINS, recipe.coins.toInt())
+ levelValue.whenChanged { oldValue, newValue ->
+ if (oldValue.toInt() == newValue.toInt()) return@whenChanged
+ val oldInput = inputStack.getPetData() ?: return@whenChanged
+ val newInput = PetData.forLevel(oldInput.petId, oldInput.rarity, newValue.toInt())
+ inputStack.setPetData(newInput)
+ val oldOutput = outputStack.getPetData() ?: return@whenChanged
+ val newOutput = PetData(oldOutput.rarity, oldOutput.petId, newInput.exp)
+ outputStack.setPetData(newOutput)
+ inputLevelLabel.message = Text.literal(newInput.levelData.currentLevel.toString())
+ inputLevelLabel.bounds.location = Point(
+ inputLevelLabelCenter.x - MC.font.getWidth(inputLevelLabel.message) / 2,
+ inputLevelLabelCenter.y)
+ outputLevelLabel.message = Text.literal(newOutput.levelData.currentLevel.toString())
+ outputLevelLabel.bounds.location = Point(
+ outputLevelLabelCenter.x - MC.font.getWidth(outputLevelLabel.message) / 2,
+ outputLevelLabelCenter.y)
+ coinStack.setStackSize((recipe.coins * (1 - 0.3 * newValue / 100)).toInt())
+ }
+ levelValue.set(1F)
+ add(Widgets.createRecipeBase(bounds))
+ add(wrapWidget(Rectangle(bounds.centerX - slider.width / 2,
+ bounds.maxY - 30,
+ slider.width,
+ slider.height),
+ slider))
+ add(Widgets.withTooltip(
+ Widgets.createArrow(Point(bounds.centerX - arrowWidth / 2, bounds.minY + 40)),
+ Text.literal("Upgrade time: " + FirmFormatters.formatTimespan(recipe.seconds.seconds))))
+
+ add(Widgets.createResultSlotBackground(Point(bounds.maxX - 30, bounds.minY + 40)))
+ add(inputLevelLabel)
+ add(outputLevelLabel)
+ add(Widgets.createSlot(Point(bounds.maxX - 30, bounds.minY + 40)).markOutput().disableBackground()
+ .entry(SBItemEntryDefinition.getEntry(outputStack)))
+ add(Widgets.createSlot(Point(bounds.minX + 30 - 18 + 5, bounds.minY + 40)).markInput()
+ .entry(SBItemEntryDefinition.getEntry(inputStack)))
+
+ val allInputs = recipe.items.map { SBItemEntryDefinition.getEntry(it) } +
+ listOf(SBItemEntryDefinition.getEntry(coinStack))
+ for ((index, item) in allInputs.withIndex()) {
+ add(Widgets.createSlot(
+ Point(bounds.centerX + index * 20 - allInputs.size * 18 / 2 - (allInputs.size - 1) * 2 / 2,
+ bounds.minY + 20))
+ .markInput()
+ .entry(item))
+ }
+ }
+ }
+ }
+}
+
+fun wrapWidget(bounds: Rectangle, component: GuiComponent): Widget {
+ return object : WidgetWithBounds() {
+ override fun getBounds(): Rectangle {
+ return bounds
+ }
+
+ override fun children(): List<Element> {
+ return listOf()
+ }
+
+ override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ context.matrices.push()
+ context.matrices.translate(bounds.minX.toFloat(), bounds.minY.toFloat(), 0F)
+ component.render(
+ GuiImmediateContext(
+ ModernRenderContext(context),
+ bounds.minX, bounds.minY,
+ bounds.width, bounds.height,
+ mouseX - bounds.minX, mouseY - bounds.minY,
+ mouseX, mouseY,
+ mouseX.toFloat(), mouseY.toFloat()
+ ))
+ context.matrices.pop()
+ }
+
+ override fun mouseMoved(mouseX: Double, mouseY: Double) {
+ val mouseXInt = mouseX.toInt()
+ val mouseYInt = mouseY.toInt()
+ component.mouseEvent(MouseEvent.Move(0F, 0F),
+ GuiImmediateContext(
+ IMinecraft.instance.provideTopLevelRenderContext(),
+ bounds.minX, bounds.minY,
+ bounds.width, bounds.height,
+ mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+ mouseXInt, mouseYInt,
+ mouseX.toFloat(), mouseY.toFloat()
+ ))
+ }
+
+ override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ val mouseXInt = mouseX.toInt()
+ val mouseYInt = mouseY.toInt()
+ return component.mouseEvent(MouseEvent.Click(button, true),
+ GuiImmediateContext(
+ IMinecraft.instance.provideTopLevelRenderContext(),
+ bounds.minX, bounds.minY,
+ bounds.width, bounds.height,
+ mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+ mouseXInt, mouseYInt,
+ mouseX.toFloat(), mouseY.toFloat()
+ ))
+ }
+
+ override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ val mouseXInt = mouseX.toInt()
+ val mouseYInt = mouseY.toInt()
+ return component.mouseEvent(MouseEvent.Click(button, false),
+ GuiImmediateContext(
+ IMinecraft.instance.provideTopLevelRenderContext(),
+ bounds.minX, bounds.minY,
+ bounds.width, bounds.height,
+ mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+ mouseXInt, mouseYInt,
+ mouseX.toFloat(), mouseY.toFloat()
+ ))
+ }
+
+ override fun mouseDragged(
+ mouseX: Double,
+ mouseY: Double,
+ button: Int,
+ deltaX: Double,
+ deltaY: Double
+ ): Boolean {
+ val mouseXInt = mouseX.toInt()
+ val mouseYInt = mouseY.toInt()
+ return component.mouseEvent(MouseEvent.Move(deltaX.toFloat(), deltaY.toFloat()),
+ GuiImmediateContext(
+ IMinecraft.instance.provideTopLevelRenderContext(),
+ bounds.minX, bounds.minY,
+ bounds.width, bounds.height,
+ mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+ mouseXInt, mouseYInt,
+ mouseX.toFloat(), mouseY.toFloat()
+ ))
+
+ }
+
+ override fun mouseScrolled(
+ mouseX: Double,
+ mouseY: Double,
+ horizontalAmount: Double,
+ verticalAmount: Double
+ ): Boolean {
+ val mouseXInt = mouseX.toInt()
+ val mouseYInt = mouseY.toInt()
+ return component.mouseEvent(MouseEvent.Scroll(verticalAmount.toFloat()),
+ GuiImmediateContext(
+ IMinecraft.instance.provideTopLevelRenderContext(),
+ bounds.minX, bounds.minY,
+ bounds.width, bounds.height,
+ mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+ mouseXInt, mouseYInt,
+ mouseX.toFloat(), mouseY.toFloat()
+ ))
+ }
+ }
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt
new file mode 100644
index 0000000..cb240fc
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBMobDropRecipe.kt
@@ -0,0 +1,108 @@
+
+package moe.nea.firmament.compat.rei.recipes
+
+import io.github.moulberry.repo.data.NEUMobDropRecipe
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.Renderer
+import me.shedaniel.rei.api.client.gui.widgets.Widget
+import me.shedaniel.rei.api.client.gui.widgets.Widgets
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import me.shedaniel.rei.api.common.util.EntryStacks
+import net.minecraft.item.Items
+import net.minecraft.text.Text
+import net.minecraft.util.Identifier
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.gui.entity.EntityRenderer
+import moe.nea.firmament.compat.rei.EntityWidget
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+
+class SBMobDropRecipe(override val neuRecipe: NEUMobDropRecipe) : SBRecipe() {
+ override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
+
+ object Category : DisplayCategory<SBMobDropRecipe> {
+ override fun getCategoryIdentifier(): CategoryIdentifier<SBMobDropRecipe> =
+ CategoryIdentifier.of(Firmament.MOD_ID, "mob_drop_recipe")
+
+ override fun getTitle(): Text = Text.literal("Mob Drops")
+ override fun getDisplayHeight(): Int {
+ return 100
+ }
+
+ override fun getIcon(): Renderer = EntryStacks.of(Items.DIAMOND_SWORD)
+ override fun setupDisplay(display: SBMobDropRecipe, bounds: Rectangle): List<Widget> {
+ return buildList {
+ add(Widgets.createRecipeBase(bounds))
+ val source = display.neuRecipe.render
+ val entity = if (source.startsWith("@")) {
+ EntityRenderer.constructEntity(Identifier.of(source.substring(1)))
+ } else {
+ EntityRenderer.applyModifiers(source, listOf())
+ }
+ if (entity != null) {
+ val level = display.neuRecipe.level
+ val fullMobName =
+ if (level > 0) Text.translatable("firmament.recipe.mobs.name", level, display.neuRecipe.name)
+ else Text.translatable("firmament.recipe.mobs.name.nolevel", display.neuRecipe.name)
+ val tt = mutableListOf<Text>()
+ tt.add((fullMobName))
+ tt.add(Text.literal(""))
+ if (display.neuRecipe.coins > 0) {
+ tt.add(Text.stringifiedTranslatable("firmament.recipe.mobs.coins", display.neuRecipe.coins))
+ }
+ if (display.neuRecipe.combatExperience > 0) {
+ tt.add(
+ Text.stringifiedTranslatable(
+ "firmament.recipe.mobs.combat",
+ display.neuRecipe.combatExperience
+ )
+ )
+ }
+ if (display.neuRecipe.enchantingExperience > 0) {
+ tt.add(
+ Text.stringifiedTranslatable(
+ "firmament.recipe.mobs.exp",
+ display.neuRecipe.enchantingExperience
+ )
+ )
+ }
+ if (display.neuRecipe.extra != null)
+ display.neuRecipe.extra.mapTo(tt) { Text.literal(it) }
+ if (tt.size == 2)
+ tt.removeAt(1)
+ add(
+ Widgets.withTooltip(
+ EntityWidget(entity, Point(bounds.minX + 5, bounds.minY + 15)),
+ tt
+ )
+ )
+ }
+ add(
+ Widgets.createLabel(Point(bounds.minX + 15, bounds.minY + 5), Text.literal(display.neuRecipe.name))
+ .leftAligned()
+ )
+ var x = bounds.minX + 60
+ var y = bounds.minY + 20
+ for (drop in display.neuRecipe.drops) {
+ val lore = drop.extra.mapTo(mutableListOf()) { Text.literal(it) }
+ if (drop.chance != null) {
+ lore += listOf(Text.translatable("firmament.recipe.mobs.drops", drop.chance))
+ }
+ val item = SBItemEntryDefinition.getEntry(drop.dropItem)
+ .value.copy(extraLore = lore)
+ add(
+ Widgets.createSlot(Point(x, y)).markOutput()
+ .entries(listOf(SBItemEntryDefinition.getEntry(item)))
+ )
+ x += 18
+ if (x > bounds.maxX - 30) {
+ x = bounds.minX + 60
+ y += 18
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBRecipe.kt
new file mode 100644
index 0000000..a66a529
--- /dev/null
+++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBRecipe.kt
@@ -0,0 +1,29 @@
+package moe.nea.firmament.compat.rei.recipes
+
+import io.github.moulberry.repo.data.NEUIngredient
+import io.github.moulberry.repo.data.NEURecipe
+import me.shedaniel.rei.api.common.display.Display
+import me.shedaniel.rei.api.common.entry.EntryIngredient
+import moe.nea.firmament.compat.rei.SBItemEntryDefinition
+import moe.nea.firmament.util.SkyblockId
+
+abstract class SBRecipe : Display {
+ abstract val neuRecipe: NEURecipe
+ override fun getInputEntries(): List<EntryIngredient> {
+ return neuRecipe.allInputs
+ .filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY }
+ .map {
+ val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId))
+ EntryIngredient.of(entryStack)
+ }
+ }
+
+ override fun getOutputEntries(): List<EntryIngredient> {
+ return neuRecipe.allOutputs
+ .filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY }
+ .map {
+ val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId))
+ EntryIngredient.of(entryStack)
+ }
+ }
+}