diff options
14 files changed, 550 insertions, 3 deletions
diff --git a/src/main/kotlin/features/items/recipes/ArrowWidget.kt b/src/main/kotlin/features/items/recipes/ArrowWidget.kt new file mode 100644 index 0000000..93c768a --- /dev/null +++ b/src/main/kotlin/features/items/recipes/ArrowWidget.kt @@ -0,0 +1,38 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.resources.ResourceLocation + +class ArrowWidget(val point: Point) : RecipeWidget() { + override val rect: Rectangle + get() = Rectangle(point, Dimension(14, 14)) + + companion object { + val arrowSprite = ResourceLocation.withDefaultNamespace("container/furnace/lit_progress") + } + + override fun render( + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float + ) { + guiGraphics.blitSprite( + RenderPipelines.GUI_TEXTURED, + arrowSprite, + 14, + 14, + 0, + 0, + point.x, + point.y, + 14, + 14 + ) + } + +} diff --git a/src/main/kotlin/features/items/recipes/ComponentWidget.kt b/src/main/kotlin/features/items/recipes/ComponentWidget.kt new file mode 100644 index 0000000..b5c4211 --- /dev/null +++ b/src/main/kotlin/features/items/recipes/ComponentWidget.kt @@ -0,0 +1,27 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import moe.nea.firmament.repo.recipes.RecipeLayouter +import moe.nea.firmament.util.MC + +class ComponentWidget(val point: Point, var text: Component) : RecipeWidget(), RecipeLayouter.Updater<Component> { + override fun update(newValue: Component) { + this.text = newValue + } + + override val rect: Rectangle + get() = Rectangle(point, Dimension(MC.font.width(text), MC.font.lineHeight)) + + override fun render( + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float + ) { + guiGraphics.drawString(MC.font, text, point.x, point.y, -1) + } +} diff --git a/src/main/kotlin/features/items/recipes/EntityWidget.kt b/src/main/kotlin/features/items/recipes/EntityWidget.kt new file mode 100644 index 0000000..88d91ad --- /dev/null +++ b/src/main/kotlin/features/items/recipes/EntityWidget.kt @@ -0,0 +1,22 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.world.entity.LivingEntity +import moe.nea.firmament.gui.entity.EntityRenderer + +class EntityWidget(override val rect: Rectangle, val entity: LivingEntity) : RecipeWidget() { + override fun render( + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float + ) { + EntityRenderer.renderEntity( + entity, guiGraphics, + rect.x, rect.y, + rect.width.toDouble(), rect.height.toDouble(), + mouseX.toDouble(), mouseY.toDouble() + ) + } +} diff --git a/src/main/kotlin/features/items/recipes/FireWidget.kt b/src/main/kotlin/features/items/recipes/FireWidget.kt new file mode 100644 index 0000000..0594d0a --- /dev/null +++ b/src/main/kotlin/features/items/recipes/FireWidget.kt @@ -0,0 +1,20 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.GuiGraphics + +class FireWidget(val point: Point, val animationTicks: Int) : RecipeWidget() { + override val rect: Rectangle + get() = Rectangle(point, Dimension(10, 10)) + + override fun render( + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float + ) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt b/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt new file mode 100644 index 0000000..ef0b7d6 --- /dev/null +++ b/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt @@ -0,0 +1,126 @@ +package moe.nea.firmament.features.items.recipes + +import java.util.Optional +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.Tooltip +import net.minecraft.network.chat.Component +import net.minecraft.util.FormattedCharSequence +import net.minecraft.world.inventory.tooltip.TooltipComponent +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import moe.nea.firmament.events.ItemTooltipEvent +import moe.nea.firmament.keybindings.GenericInputAction +import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.repo.ExpensiveItemCacheApi +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.RecipeLayouter +import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.FirmFormatters.shortFormat +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.darkGrey +import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.loreAccordingToNbt + +class ItemSlotWidget( + val point: Point, + var content: List<SBItemStack>, + val slotKind: RecipeLayouter.SlotKind +) : RecipeWidget(), + RecipeLayouter.CyclingItemSlot { + val backgroundTopLeft = + if (slotKind.isBig) Point(point.x - 4, point.y - 4) + else Point(point.x - 1, point.y - 1) + val backgroundSize = + if (slotKind.isBig) Dimension(16 + 8, 16 + 8) + else Dimension(18, 18) + val itemRect = Rectangle(point, Dimension(16, 16)) + override val rect: Rectangle + get() = Rectangle(backgroundTopLeft, backgroundSize) + + @OptIn(ExpensiveItemCacheApi::class) + override fun render( + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float + ) { + val stack = current().asImmutableItemStack() + // TODO: draw slot background + if (stack.isEmpty) return + guiGraphics.renderItem(stack, point.x, point.y) + guiGraphics.renderItemDecorations( + MC.font, stack, point.x, point.y, + if (stack.count >= SHORT_NUM_CUTOFF) shortFormat(stack.count.toDouble()) + else null + ) + if (itemRect.contains(mouseX, mouseY)) + guiGraphics.setTooltipForNextFrame( + MC.font, getTooltip(stack), Optional.empty(), + mouseX, mouseY + ) + } + + companion object { + val SHORT_NUM_CUTOFF = 1000 + var canUseTooltipEvent = true + } + + fun getTooltip(itemStack: ItemStack): List<Component> { + val lore = mutableListOf(itemStack.displayNameAccordingToNbt) + lore.addAll(itemStack.loreAccordingToNbt) + if (canUseTooltipEvent) { + try { + ItemTooltipCallback.EVENT.invoker().getTooltip( + itemStack, Item.TooltipContext.EMPTY, + TooltipFlag.NORMAL, lore + ) + } catch (ex: Exception) { + canUseTooltipEvent = false + ErrorUtil.softError("Failed to use vanilla tooltips", ex) + } + } else { + ItemTooltipEvent.publish( + ItemTooltipEvent( + itemStack, + Item.TooltipContext.EMPTY, + TooltipFlag.NORMAL, + lore + ) + ) + } + if (itemStack.count >= SHORT_NUM_CUTOFF && lore.isNotEmpty()) + lore.add(1, Component.literal("${itemStack.count}x").darkGrey()) + return lore + } + + override fun tick() { + if (SavedKeyBinding.isShiftDown()) return + if (content.size <= 1) return + if (MC.currentTick % 5 != 0) return + index = (index + 1) % content.size + } + + var index = 0 + var onUpdate: () -> Unit = {} + + override fun onUpdate(action: () -> Unit) { + this.onUpdate = action + } + + override fun current(): SBItemStack { + return content.getOrElse(index) { SBItemStack.EMPTY } + } + + override fun update(newValue: SBItemStack) { + content = listOf(newValue) + // SAFE: content was just assigned to a non-empty list + index = index.coerceIn(content.indices) + } +} diff --git a/src/main/kotlin/features/items/recipes/RecipeRegistry.kt b/src/main/kotlin/features/items/recipes/RecipeRegistry.kt new file mode 100644 index 0000000..158f9a4 --- /dev/null +++ b/src/main/kotlin/features/items/recipes/RecipeRegistry.kt @@ -0,0 +1,110 @@ +package moe.nea.firmament.features.items.recipes + +import com.mojang.blaze3d.platform.InputConstants +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import net.fabricmc.fabric.mixin.client.gametest.input.InputUtilMixin +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.events.ReloadRegistrationEvent +import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.GenericRecipeRenderer +import moe.nea.firmament.repo.recipes.SBCraftingRecipeRenderer +import moe.nea.firmament.repo.recipes.SBEssenceUpgradeRecipeRenderer +import moe.nea.firmament.repo.recipes.SBForgeRecipeRenderer +import moe.nea.firmament.repo.recipes.SBReforgeRecipeRenderer +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.focusedItemStack + +object RecipeRegistry { + val recipeTypes: List<GenericRecipeRenderer<*>> = listOf( + SBCraftingRecipeRenderer, + SBForgeRecipeRenderer, + SBReforgeRecipeRenderer, + SBEssenceUpgradeRecipeRenderer, + ) + + + @Subscribe + fun onDebugRecipe(event: HandledScreenKeyPressedEvent) { + if (event.matches(SavedKeyBinding.keyWithoutMods(InputConstants.KEY_R))) { + val stack = event.screen.focusedItemStack ?: return + val recipes = getRecipesFor(SBItemStack(stack)) + MC.screen = RecipeScreen(recipes.firstOrNull() ?: return) + } + } + + + object RecipeIndexes : IReloadable { + + private fun <T : Any> createIndexFor( + neuRepository: NEURepository, + recipeRenderer: GenericRecipeRenderer<T>, + outputs: Boolean, + ): List<Pair<SkyblockId, RenderableRecipe<T>>> { + val indexer: (T) -> Collection<SBItemStack> = + if (outputs) recipeRenderer::getOutputs + else recipeRenderer::getInputs + return recipeRenderer.findAllRecipes(neuRepository) + .flatMap { + val wrappedRecipe = RenderableRecipe(it, recipeRenderer, null) + indexer(it).map { it.skyblockId to wrappedRecipe } + } + } + + fun createIndex(outputs: Boolean): MutableMap<SkyblockId, List<RenderableRecipe<*>>> { + val m: MutableMap<SkyblockId, List<RenderableRecipe<*>>> = mutableMapOf() + recipeTypes.forEach { renderer -> + createIndexFor(RepoManager.neuRepo, renderer, outputs) + .forEach { (stack, recipe) -> + m.merge(stack, listOf(recipe)) { a, b -> a + b } + } + } + return m + } + + lateinit var recipesForIndex : Map<SkyblockId, List<RenderableRecipe<*>>> + lateinit var usagesForIndex : Map<SkyblockId, List<RenderableRecipe<*>>> + override fun reload(recipe: NEURepository) { + recipesForIndex = createIndex(true) + usagesForIndex = createIndex(false) + } + } + + @Subscribe + fun onRepoBuild(event: ReloadRegistrationEvent) { + event.repo.registerReloadListener(RecipeIndexes) + } + + + fun getRecipesFor(itemStack: SBItemStack): Set<RenderableRecipe<*>> { + val recipes = LinkedHashSet<RenderableRecipe<*>>() + recipeTypes.forEach { injectRecipesFor(it, recipes, itemStack, true) } + recipes.addAll(RecipeIndexes.recipesForIndex[itemStack.skyblockId] ?: emptyList()) + return recipes + } + + fun getUsagesFor(itemStack: SBItemStack): Set<RenderableRecipe<*>> { + val recipes = LinkedHashSet<RenderableRecipe<*>>() + recipeTypes.forEach { injectRecipesFor(it, recipes, itemStack, false) } + recipes.addAll(RecipeIndexes.usagesForIndex[itemStack.skyblockId] ?: emptyList()) + return recipes + } + + private fun <T : Any> injectRecipesFor( + recipeRenderer: GenericRecipeRenderer<T>, + collector: MutableCollection<RenderableRecipe<*>>, + relevantItem: SBItemStack, + mustBeInOutputs: Boolean + ) { + collector.addAll( + recipeRenderer.discoverExtraRecipes(RepoManager.neuRepo, relevantItem, mustBeInOutputs) + .map { RenderableRecipe(it, recipeRenderer, relevantItem) } + ) + } + + +} diff --git a/src/main/kotlin/features/items/recipes/RecipeScreen.kt b/src/main/kotlin/features/items/recipes/RecipeScreen.kt new file mode 100644 index 0000000..c94839d --- /dev/null +++ b/src/main/kotlin/features/items/recipes/RecipeScreen.kt @@ -0,0 +1,29 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.screens.Screen +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.util.tr + +class RecipeScreen( + val recipes: RenderableRecipe<*>, +) : Screen(tr("firmament.recipe.screen", "SkyBlock Recipe")) { + + lateinit var layoutedRecipe: StandaloneRecipeRenderer + override fun init() {// TODO: wrap all of this in a scroll layout + super.init() + val bounds = Rectangle( + width / 2 - recipes.renderer.displayWidth / 2, + height / 2 - recipes.renderer.displayHeight / 2, + recipes.renderer.displayWidth, + recipes.renderer.displayHeight + ) + layoutedRecipe = recipes.render(bounds) + layoutedRecipe.widgets.forEach(this::addRenderableWidget) + } + + override fun tick() { + super.tick() + layoutedRecipe.tick() + } +} diff --git a/src/main/kotlin/features/items/recipes/RecipeWidget.kt b/src/main/kotlin/features/items/recipes/RecipeWidget.kt new file mode 100644 index 0000000..2818d05 --- /dev/null +++ b/src/main/kotlin/features/items/recipes/RecipeWidget.kt @@ -0,0 +1,33 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.components.Renderable +import net.minecraft.client.gui.components.events.GuiEventListener +import net.minecraft.client.gui.narration.NarratableEntry +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.client.gui.navigation.ScreenRectangle +import moe.nea.firmament.util.mc.asScreenRectangle + +abstract class RecipeWidget : GuiEventListener, Renderable, NarratableEntry { + override fun narrationPriority(): NarratableEntry.NarrationPriority? { + return NarratableEntry.NarrationPriority.NONE// I am so sorry + } + + override fun updateNarration(narrationElementOutput: NarrationElementOutput) { + } + + open fun tick() {} + private var _focused = false + abstract val rect: Rectangle + override fun setFocused(focused: Boolean) { + this._focused = focused + } + + override fun getRectangle(): ScreenRectangle { + return rect.asScreenRectangle() + } + + override fun isFocused(): Boolean { + return this._focused + } +} diff --git a/src/main/kotlin/features/items/recipes/RenderableRecipe.kt b/src/main/kotlin/features/items/recipes/RenderableRecipe.kt new file mode 100644 index 0000000..3601e9e --- /dev/null +++ b/src/main/kotlin/features/items/recipes/RenderableRecipe.kt @@ -0,0 +1,27 @@ +package moe.nea.firmament.features.items.recipes + +import java.util.Objects +import me.shedaniel.math.Rectangle +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.GenericRecipeRenderer + +class RenderableRecipe<T : Any>( + val recipe: T, + val renderer: GenericRecipeRenderer<T>, + val mainItemStack: SBItemStack?, +) { + fun render(bounds: Rectangle): StandaloneRecipeRenderer { + val layouter = StandaloneRecipeRenderer() + renderer.render(recipe, bounds, layouter, mainItemStack) + return layouter + } + +// override fun equals(other: Any?): Boolean { +// if (other !is RenderableRecipe<*>) return false +// return renderer == other.renderer && recipe == other.recipe +// } +// +// override fun hashCode(): Int { +// return Objects.hash(recipe, renderer) +// } +} diff --git a/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt b/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt new file mode 100644 index 0000000..df7c4de --- /dev/null +++ b/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt @@ -0,0 +1,74 @@ +package moe.nea.firmament.features.items.recipes + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.components.events.AbstractContainerEventHandler +import net.minecraft.client.gui.components.events.GuiEventListener +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.LivingEntity +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.RecipeLayouter + +class StandaloneRecipeRenderer : AbstractContainerEventHandler(), RecipeLayouter { + + fun tick() { + widgets.forEach { it.tick() } + } + + fun <T : RecipeWidget> addWidget(widget: T): T { + this.widgets.add(widget) + return widget + } + + override fun createCyclingItemSlot( + x: Int, + y: Int, + content: List<SBItemStack>, + slotKind: RecipeLayouter.SlotKind + ): RecipeLayouter.CyclingItemSlot { + return addWidget(ItemSlotWidget(Point(x, y), content, slotKind)) + } + + override fun createTooltip( + rectangle: Rectangle, + label: List<Component> + ) { + addWidget(TooltipWidget(rectangle, label)) + } + + override fun createLabel( + x: Int, + y: Int, + text: Component + ): RecipeLayouter.Updater<Component> { + return addWidget(ComponentWidget(Point(x, y), text)) + } + + override fun createArrow(x: Int, y: Int): Rectangle { + return addWidget(ArrowWidget(Point(x, y))).rect + } + + override fun createMoulConfig( + x: Int, + y: Int, + w: Int, + h: Int, + component: GuiComponent + ) { + TODO("Not yet implemented") + } + + override fun createFire(point: Point, animationTicks: Int) { + addWidget(FireWidget(point, animationTicks)) + } + + override fun createEntity(rectangle: Rectangle, entity: LivingEntity) { + addWidget(EntityWidget(rectangle, entity)) + } + + val widgets: MutableList<RecipeWidget> = mutableListOf() + override fun children(): List<GuiEventListener> { + return widgets + } +} diff --git a/src/main/kotlin/features/items/recipes/TooltipWidget.kt b/src/main/kotlin/features/items/recipes/TooltipWidget.kt new file mode 100644 index 0000000..cf9c09a --- /dev/null +++ b/src/main/kotlin/features/items/recipes/TooltipWidget.kt @@ -0,0 +1,27 @@ +package moe.nea.firmament.features.items.recipes + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import moe.nea.firmament.repo.recipes.RecipeLayouter + +class TooltipWidget( + override val rect: Rectangle, + label: List<Component> +) : RecipeWidget(), RecipeLayouter.Updater<List<Component>> { + override fun update(newValue: List<Component>) { + this.formattedComponent = newValue.map { it.visualOrderText } + } + + var formattedComponent = label.map { it.visualOrderText } + override fun render( + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float + ) { + if (rect.contains(mouseX, mouseY)) + guiGraphics.setTooltipForNextFrame(formattedComponent, mouseX, mouseY) + } + +} diff --git a/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt b/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt index c029494..84f1f48 100644 --- a/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt +++ b/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt @@ -16,6 +16,8 @@ interface GenericRecipeRenderer<T : Any> { val title: Component val identifier: ResourceLocation fun findAllRecipes(neuRepository: NEURepository): Iterable<T> + fun discoverExtraRecipes(neuRepository: NEURepository, itemStack: SBItemStack, mustBeInOutputs: Boolean): Iterable<T> = emptyList() val displayHeight: Int get() = 66 + val displayWidth: Int get() = 150 val typ: Class<T> } diff --git a/src/main/kotlin/repo/recipes/RecipeLayouter.kt b/src/main/kotlin/repo/recipes/RecipeLayouter.kt index b211d9c..7a63941 100644 --- a/src/main/kotlin/repo/recipes/RecipeLayouter.kt +++ b/src/main/kotlin/repo/recipes/RecipeLayouter.kt @@ -18,7 +18,8 @@ interface RecipeLayouter { * Create a bigger background and mark the slot as output. The coordinates should still refer the upper left corner of the item stack, not of the bigger background. */ BIG_OUTPUT, - DISPLAY, + DISPLAY,; + val isBig get() = this == BIG_OUTPUT } @@ -32,7 +33,7 @@ interface RecipeLayouter { x: Int, y: Int, content: SBItemStack?, slotKind: SlotKind, - ): ItemSlot + ): ItemSlot = createCyclingItemSlot(x, y, listOfNotNull(content), slotKind) interface CyclingItemSlot : ItemSlot { fun onUpdate(action: () -> Unit) @@ -59,7 +60,7 @@ interface RecipeLayouter { fun createArrow(x: Int, y: Int): Rectangle fun createMoulConfig(x: Int, y: Int, w: Int, h: Int, component: GuiComponent) - fun createFire(ingredientsCenter: Point, animationTicks: Int) + fun createFire(point: Point, animationTicks: Int) fun createEntity(rectangle: Rectangle, entity: LivingEntity) } diff --git a/src/main/kotlin/util/mc/Rectangle.kt b/src/main/kotlin/util/mc/Rectangle.kt new file mode 100644 index 0000000..6495c29 --- /dev/null +++ b/src/main/kotlin/util/mc/Rectangle.kt @@ -0,0 +1,11 @@ +package moe.nea.firmament.util.mc + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.navigation.ScreenAxis +import net.minecraft.client.gui.navigation.ScreenRectangle + +fun Rectangle.asScreenRectangle() = + ScreenRectangle.of( + ScreenAxis.HORIZONTAL, + x, y, width, height + ) |
