Make REI optional

This commit is contained in:
Linnea Gräf
2024-10-28 12:07:55 +01:00
parent 8ab4408854
commit c38dcee2c5
28 changed files with 451 additions and 374 deletions

View File

@@ -15,8 +15,8 @@ import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.gui.hud.MoulConfigHud
import moe.nea.firmament.rei.SBItemEntryDefinition
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
@@ -197,9 +197,9 @@ object AnniversaryFeatures : FirmamentFeature {
}
val itemStack = if (backedBy is Reward.Items) {
SBItemEntryDefinition.getEntry(backedBy.item, backedBy.amount)
SBItemStack(backedBy.item, backedBy.amount)
} else {
SBItemEntryDefinition.getEntry(SkyblockId.NULL)
SBItemStack(SkyblockId.NULL)
}
@Bind
@@ -207,7 +207,7 @@ object AnniversaryFeatures : FirmamentFeature {
return when (backedBy) {
is Reward.Coins -> "Coins"
is Reward.EXP -> backedBy.skill
is Reward.Items -> itemStack.value.asItemStack().name.string
is Reward.Items -> itemStack.asImmutableItemStack().name.string
is Reward.Unknown -> backedBy.text
}
}

View File

@@ -1,5 +1,6 @@
package moe.nea.firmament.features.inventory
import io.github.moulberry.repo.data.NEUCraftingRecipe
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
import net.minecraft.item.ItemStack
import net.minecraft.util.Formatting
@@ -7,15 +8,14 @@ import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry
import moe.nea.firmament.rei.SBItemEntryDefinition
import moe.nea.firmament.rei.recipes.SBCraftingRecipe
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.skyblockId
object CraftingOverlay : FirmamentFeature {
private var screen: GenericContainerScreen? = null
private var recipe: SBCraftingRecipe? = null
private var recipe: NEUCraftingRecipe? = null
private var useNextScreen = false
private val craftingOverlayIndices = listOf(
10, 11, 12,
@@ -24,7 +24,7 @@ object CraftingOverlay : FirmamentFeature {
)
val CRAFTING_SCREEN_NAME = "Craft Item"
fun setOverlay(screen: GenericContainerScreen?, recipe: SBCraftingRecipe) {
fun setOverlay(screen: GenericContainerScreen?, recipe: NEUCraftingRecipe) {
this.screen = screen
if (screen == null) {
useNextScreen = true
@@ -52,10 +52,12 @@ object CraftingOverlay : FirmamentFeature {
if (slot.inventory != screen?.screenHandler?.inventory) return
val recipeIndex = craftingOverlayIndices.indexOf(slot.index)
if (recipeIndex < 0) return
val expectedItem = recipe.neuRecipe.inputs[recipeIndex]
val expectedItem = recipe.inputs[recipeIndex]
val actualStack = slot.stack ?: ItemStack.EMPTY!!
val actualEntry = SBItemEntryDefinition.getEntry(actualStack).value
if ((actualEntry.skyblockId.neuItem != expectedItem.itemId || actualEntry.getStackSize() < expectedItem.amount) && expectedItem.amount.toInt() != 0) {
val actualEntry = SBItemStack(actualStack)
if ((actualEntry.skyblockId != expectedItem.skyblockId || actualEntry.getStackSize() < expectedItem.amount)
&& expectedItem.amount.toInt() != 0
) {
event.context.fill(
event.slot.x,
event.slot.y,
@@ -65,7 +67,7 @@ object CraftingOverlay : FirmamentFeature {
)
}
if (!slot.hasStack()) {
val itemStack = SBItemEntryDefinition.getEntry(expectedItem).asItemEntry().value
val itemStack = SBItemStack(expectedItem)?.asImmutableItemStack() ?: return
event.context.drawItem(itemStack, event.slot.x, event.slot.y)
event.context.drawItemInSlot(
MC.font,

View File

@@ -1,35 +0,0 @@
package moe.nea.firmament.gui.entity
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 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))
}
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.gui.entity
import com.google.gson.JsonObject
@@ -6,50 +5,49 @@ import net.minecraft.component.DataComponentTypes
import net.minecraft.component.type.DyedColorComponent
import net.minecraft.entity.EquipmentSlot
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ArmorItem
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import moe.nea.firmament.rei.SBItemStack
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.mc.setEncodedSkullOwner
import moe.nea.firmament.util.mc.zeroUUID
object ModifyEquipment : EntityModifier {
val names = mapOf(
"hand" to EquipmentSlot.MAINHAND,
"helmet" to EquipmentSlot.HEAD,
"chestplate" to EquipmentSlot.CHEST,
"leggings" to EquipmentSlot.LEGS,
"feet" to EquipmentSlot.FEET,
)
val names = mapOf(
"hand" to EquipmentSlot.MAINHAND,
"helmet" to EquipmentSlot.HEAD,
"chestplate" to EquipmentSlot.CHEST,
"leggings" to EquipmentSlot.LEGS,
"feet" to EquipmentSlot.FEET,
)
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
names.forEach { (key, slot) ->
info[key]?.let {
entity.equipStack(slot, createItem(it.asString))
}
}
return entity
}
override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity {
names.forEach { (key, slot) ->
info[key]?.let {
entity.equipStack(slot, createItem(it.asString))
}
}
return entity
}
private fun createItem(item: String): ItemStack {
val split = item.split("#")
if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack()
val (type, data) = split
return when (type) {
"SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) }
"LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data)
"LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data)
"LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data)
"LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data)
else -> error("Unknown leather piece: $type")
}
}
private fun createItem(item: String): ItemStack {
val split = item.split("#")
if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack()
val (type, data) = split
return when (type) {
"SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) }
"LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data)
"LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data)
"LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data)
"LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data)
else -> error("Unknown leather piece: $type")
}
}
private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack {
val stack = ItemStack(leatherArmor)
stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false))
return stack
}
private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack {
val stack = ItemStack(leatherArmor)
stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false))
return stack
}
}

View File

@@ -1,142 +0,0 @@
package moe.nea.firmament.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.rei.recipes.SBCraftingRecipe
import moe.nea.firmament.rei.recipes.SBEssenceUpgradeRecipe
import moe.nea.firmament.rei.recipes.SBForgeRecipe
import moe.nea.firmament.rei.recipes.SBKatRecipe
import moe.nea.firmament.rei.recipes.SBMobDropRecipe
import moe.nea.firmament.repo.RepoManager
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)
}
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))
}
}
}

View File

@@ -1,186 +0,0 @@
/*
* 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.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.rei.FirmamentReiPlugin.Companion.asItemEntry
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
) {
}
}

View File

@@ -1,29 +0,0 @@
package moe.nea.firmament.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.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())
}
}
}

View File

@@ -1,254 +0,0 @@
package moe.nea.firmament.rei
import io.github.moulberry.repo.constants.PetNumbers
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import io.github.moulberry.repo.data.Rarity
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.Formatting
import net.minecraft.util.Identifier
import moe.nea.firmament.rei.FirmamentReiPlugin.Companion.asItemEntry
import moe.nea.firmament.repo.ExpLadders
import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.HypixelPetInfo
import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.petData
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.withColor
// TODO: add in extra data like pet info, into this structure
data class PetData(
val rarity: Rarity,
val petId: String,
val exp: Double,
val isStub: Boolean = false,
) {
companion object {
fun fromHypixel(petInfo: HypixelPetInfo) = PetData(
petInfo.tier, petInfo.type, petInfo.exp,
)
fun forLevel(petId: String, rarity: Rarity, level: Int) = PetData(
rarity, petId, ExpLadders.getExpLadder(petId, rarity).getPetExpForLevel(level).toDouble()
)
}
val levelData by lazy { ExpLadders.getExpLadder(petId, rarity).getPetLevel(exp) }
}
data class SBItemStack constructor(
val skyblockId: SkyblockId,
val neuItem: NEUItem?,
private var stackSize: Int,
private var petData: PetData?,
val extraLore: List<Text> = emptyList(),
// TODO: grab this star data from nbt if possible
val stars: Int = 0,
) {
fun getStackSize() = stackSize
fun setStackSize(newSize: Int) {
this.stackSize = newSize
this.itemStack_ = null
}
fun getPetData() = petData
fun setPetData(petData: PetData?) {
this.petData = petData
this.itemStack_ = null
}
constructor(skyblockId: SkyblockId, petData: PetData) : this(
skyblockId,
RepoManager.getNEUItem(skyblockId),
1,
petData
)
constructor(skyblockId: SkyblockId, stackSize: Int = 1) : this(
skyblockId,
RepoManager.getNEUItem(skyblockId),
stackSize,
RepoManager.getPotentialStubPetData(skyblockId)
)
private fun injectReplacementDataForPetLevel(
petInfo: PetNumbers,
level: Int,
replacementData: MutableMap<String, String>
) {
val stats = petInfo.interpolatedStatsAtLevel(level) ?: return
stats.otherNumbers.forEachIndexed { index, it ->
replacementData[index.toString()] = FirmFormatters.formatCommas(it, 1)
}
stats.statNumbers.forEach { (t, u) ->
replacementData[t] = FirmFormatters.formatCommas(u, 1)
}
}
private fun injectReplacementDataForPets(replacementData: MutableMap<String, String>) {
val petData = this.petData ?: return
val petInfo = RepoManager.neuRepo.constants.petNumbers[petData.petId]?.get(petData.rarity) ?: return
if (petData.isStub) {
val mapLow = mutableMapOf<String, String>()
injectReplacementDataForPetLevel(petInfo, petInfo.lowLevel, mapLow)
val mapHigh = mutableMapOf<String, String>()
injectReplacementDataForPetLevel(petInfo, petInfo.highLevel, mapHigh)
mapHigh.forEach { (key, highValue) ->
mapLow.merge(key, highValue) { a, b -> "$a$b" }
}
replacementData.putAll(mapLow)
replacementData["LVL"] = "${petInfo.lowLevel}${petInfo.highLevel}"
} else {
injectReplacementDataForPetLevel(petInfo, petData.levelData.currentLevel, replacementData)
replacementData["LVL"] = petData.levelData.currentLevel.toString()
}
}
private var itemStack_: ItemStack? = null
private val itemStack: ItemStack
get() {
val itemStack = itemStack_ ?: run {
if (skyblockId == SkyblockId.COINS)
return@run ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) }
val replacementData = mutableMapOf<String, String>()
injectReplacementDataForPets(replacementData)
return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
.copyWithCount(stackSize)
.also { it.appendLore(extraLore) }
.also { enhanceStatsByStars(it, stars) }
}
if (itemStack_ == null)
itemStack_ = itemStack
return itemStack
}
private fun starString(stars: Int): Text {
if (stars <= 0) return Text.empty()
val tiers = listOf(
LegacyFormattingCode.GOLD,
LegacyFormattingCode.LIGHT_PURPLE,
LegacyFormattingCode.AQUA,
)
val maxStars = 5
if (stars > tiers.size * maxStars) return Text.literal(" ${stars}").withColor(Formatting.RED)
val starBaseTier = (stars - 1) / maxStars
val starBaseColor = tiers[starBaseTier]
val starsInCurrentTier = stars - starBaseTier * maxStars
val starString = Text.literal(" " + "".repeat(starsInCurrentTier)).withColor(starBaseColor.modern)
if (starBaseTier > 0) {
val starLastTier = tiers[starBaseTier - 1]
val starsInLastTier = 5 - starsInCurrentTier
starString.append(Text.literal("".repeat(starsInLastTier)).withColor(starLastTier.modern))
}
return starString
}
private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int) {
if (stars == 0) return
// TODO: increase stats and add the star level into the nbt data so star displays work
itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy()
.append(starString(stars))
}
fun asImmutableItemStack(): ItemStack {
return itemStack
}
fun asItemStack(): ItemStack {
return itemStack.copy()
}
}
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.asItemStack()
}
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) }
)
)
}

View File

@@ -1,64 +0,0 @@
package moe.nea.firmament.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.rei.recipes.SBCraftingRecipe
import moe.nea.firmament.rei.recipes.SBEssenceUpgradeRecipe
import moe.nea.firmament.rei.recipes.SBForgeRecipe
import moe.nea.firmament.rei.recipes.SBKatRecipe
import moe.nea.firmament.rei.recipes.SBMobDropRecipe
import moe.nea.firmament.repo.EssenceRecipeProvider
import moe.nea.firmament.repo.RepoManager
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))
}
}

View File

@@ -1,25 +0,0 @@
package moe.nea.firmament.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
}

View File

@@ -1,10 +0,0 @@
package moe.nea.firmament.rei
import me.shedaniel.math.Point
operator fun Point.plus(other: Point): Point = Point(
this.x + other.x,
this.y + other.y,
)

View File

@@ -1,55 +0,0 @@
package moe.nea.firmament.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.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()
)
}
}
}
}

View File

@@ -1,62 +0,0 @@
package moe.nea.firmament.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.rei.SBItemEntryDefinition
import moe.nea.firmament.rei.SBItemStack
import moe.nea.firmament.repo.EssenceRecipeProvider
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
}
}

View File

@@ -1,71 +0,0 @@
package moe.nea.firmament.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.rei.SBItemEntryDefinition
import moe.nea.firmament.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))
)
}
}
}
}

View File

@@ -1,224 +0,0 @@
package moe.nea.firmament.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.rei.PetData
import moe.nea.firmament.rei.SBItemEntryDefinition
import moe.nea.firmament.rei.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()
))
}
}
}

View File

@@ -1,108 +0,0 @@
package moe.nea.firmament.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.gui.entity.EntityWidget
import moe.nea.firmament.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
}
}
}
}
}
}

View File

@@ -1,31 +0,0 @@
package moe.nea.firmament.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.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)
}
}
}

View File

@@ -0,0 +1,24 @@
package moe.nea.firmament.repo
import io.github.moulberry.repo.data.Rarity
import moe.nea.firmament.util.HypixelPetInfo
// TODO: add in extra data like pet info, into this structure
data class PetData(
val rarity: Rarity,
val petId: String,
val exp: Double,
val isStub: Boolean = false,
) {
companion object {
fun fromHypixel(petInfo: HypixelPetInfo) = PetData(
petInfo.tier, petInfo.type, petInfo.exp,
)
fun forLevel(petId: String, rarity: Rarity, level: Int) = PetData(
rarity, petId, ExpLadders.getExpLadder(petId, rarity).getPetExpForLevel(level).toDouble()
)
}
val levelData by lazy { ExpLadders.getExpLadder(petId, rarity).getPetLevel(exp) }
}

View File

@@ -14,7 +14,6 @@ import moe.nea.firmament.Firmament
import moe.nea.firmament.Firmament.logger
import moe.nea.firmament.events.ReloadRegistrationEvent
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.rei.PetData
import moe.nea.firmament.util.MinecraftDispatcher
import moe.nea.firmament.util.SkyblockId
@@ -139,9 +138,9 @@ object RepoManager {
return null
}
val intIndex = rarityIndex.toInt()
if (intIndex !in Rarity.values().indices) return null
if (intIndex !in Rarity.entries.indices) return null
if (petId !in neuRepo.constants.petNumbers) return null
return PetData(Rarity.values()[intIndex], petId, 0.0, true)
return PetData(Rarity.entries[intIndex], petId, 0.0, true)
}
}

View File

@@ -0,0 +1,165 @@
package moe.nea.firmament.repo
import io.github.moulberry.repo.constants.PetNumbers
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
import net.minecraft.util.Formatting
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.petData
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.skyblockId
import moe.nea.firmament.util.withColor
data class SBItemStack constructor(
val skyblockId: SkyblockId,
val neuItem: NEUItem?,
private var stackSize: Int,
private var petData: PetData?,
val extraLore: List<Text> = emptyList(),
// TODO: grab this star data from nbt if possible
val stars: Int = 0,
) {
fun getStackSize() = stackSize
fun setStackSize(newSize: Int) {
this.stackSize = newSize
this.itemStack_ = null
}
fun getPetData() = petData
fun setPetData(petData: PetData?) {
this.petData = petData
this.itemStack_ = null
}
companion object {
operator fun invoke(itemStack: ItemStack): SBItemStack {
val skyblockId = itemStack.skyBlockId ?: SkyblockId.NULL
return SBItemStack(
skyblockId,
RepoManager.getNEUItem(skyblockId),
itemStack.count,
petData = itemStack.petData?.let { PetData.fromHypixel(it) }
)
}
operator fun invoke(neuIngredient: NEUIngredient): SBItemStack? {
if (neuIngredient.skyblockId == SkyblockId.SENTINEL_EMPTY) return null // TODO: better fallback, maybe?
if (neuIngredient.skyblockId == SkyblockId.COINS) {
// TODO: specially handle coins to include the decimals
}
return SBItemStack(neuIngredient.skyblockId, neuIngredient.amount.toInt())
}
}
constructor(skyblockId: SkyblockId, petData: PetData) : this(
skyblockId,
RepoManager.getNEUItem(skyblockId),
1,
petData
)
constructor(skyblockId: SkyblockId, stackSize: Int = 1) : this(
skyblockId,
RepoManager.getNEUItem(skyblockId),
stackSize,
RepoManager.getPotentialStubPetData(skyblockId)
)
private fun injectReplacementDataForPetLevel(
petInfo: PetNumbers,
level: Int,
replacementData: MutableMap<String, String>
) {
val stats = petInfo.interpolatedStatsAtLevel(level) ?: return
stats.otherNumbers.forEachIndexed { index, it ->
replacementData[index.toString()] = FirmFormatters.formatCommas(it, 1)
}
stats.statNumbers.forEach { (t, u) ->
replacementData[t] = FirmFormatters.formatCommas(u, 1)
}
}
private fun injectReplacementDataForPets(replacementData: MutableMap<String, String>) {
val petData = this.petData ?: return
val petInfo = RepoManager.neuRepo.constants.petNumbers[petData.petId]?.get(petData.rarity) ?: return
if (petData.isStub) {
val mapLow = mutableMapOf<String, String>()
injectReplacementDataForPetLevel(petInfo, petInfo.lowLevel, mapLow)
val mapHigh = mutableMapOf<String, String>()
injectReplacementDataForPetLevel(petInfo, petInfo.highLevel, mapHigh)
mapHigh.forEach { (key, highValue) ->
mapLow.merge(key, highValue) { a, b -> "$a$b" }
}
replacementData.putAll(mapLow)
replacementData["LVL"] = "${petInfo.lowLevel}${petInfo.highLevel}"
} else {
injectReplacementDataForPetLevel(petInfo, petData.levelData.currentLevel, replacementData)
replacementData["LVL"] = petData.levelData.currentLevel.toString()
}
}
private var itemStack_: ItemStack? = null
private val itemStack: ItemStack
get() {
val itemStack = itemStack_ ?: run {
if (skyblockId == SkyblockId.COINS)
return@run ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) }
val replacementData = mutableMapOf<String, String>()
injectReplacementDataForPets(replacementData)
return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
.copyWithCount(stackSize)
.also { it.appendLore(extraLore) }
.also { enhanceStatsByStars(it, stars) }
}
if (itemStack_ == null)
itemStack_ = itemStack
return itemStack
}
private fun starString(stars: Int): Text {
if (stars <= 0) return Text.empty()
val tiers = listOf(
LegacyFormattingCode.GOLD,
LegacyFormattingCode.LIGHT_PURPLE,
LegacyFormattingCode.AQUA,
)
val maxStars = 5
if (stars > tiers.size * maxStars) return Text.literal(" ${stars}").withColor(Formatting.RED)
val starBaseTier = (stars - 1) / maxStars
val starBaseColor = tiers[starBaseTier]
val starsInCurrentTier = stars - starBaseTier * maxStars
val starString = Text.literal(" " + "".repeat(starsInCurrentTier)).withColor(starBaseColor.modern)
if (starBaseTier > 0) {
val starLastTier = tiers[starBaseTier - 1]
val starsInLastTier = 5 - starsInCurrentTier
starString.append(Text.literal("".repeat(starsInLastTier)).withColor(starLastTier.modern))
}
return starString
}
private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int) {
if (stars == 0) return
// TODO: increase stats and add the star level into the nbt data so star displays work
itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy()
.append(starString(stars))
}
fun asImmutableItemStack(): ItemStack {
return itemStack
}
fun asCopiedItemStack(): ItemStack {
return itemStack.copy()
}
}

View File

@@ -1,31 +1,27 @@
package moe.nea.firmament.util
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 com.google.auto.service.AutoService
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.item.ItemStack
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.compatloader.CompatLoader
interface HoveredItemStackProvider {
fun provideHoveredItemStack(screen: HandledScreen<*>): ItemStack?
companion object : CompatLoader<HoveredItemStackProvider>(HoveredItemStackProvider::class)
}
@AutoService(HoveredItemStackProvider::class)
class VanillaScreenProvider : HoveredItemStackProvider {
override fun provideHoveredItemStack(screen: HandledScreen<*>): ItemStack? {
screen as AccessorHandledScreen
val vanillaSlot = screen.focusedSlot_Firmament?.stack
return vanillaSlot
}
}
val HandledScreen<*>.focusedItemStack: ItemStack?
get() {
this as AccessorHandledScreen
val vanillaSlot = this.focusedSlot_Firmament?.stack
if (vanillaSlot != null) return vanillaSlot
val focusedSlot = ScreenRegistry.getInstance().getFocusedStack(this, PointHelper.ofMouse())
if (focusedSlot != null) return focusedSlot.cheatsAs().value
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)
}
}
get() =
HoveredItemStackProvider.allValidInstances
.firstNotNullOfOrNull { it.provideHoveredItemStack(this) }

View File

@@ -2,6 +2,7 @@
package moe.nea.firmament.util
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import io.github.moulberry.repo.data.Rarity
import java.util.Optional
@@ -61,7 +62,8 @@ value class SkyblockId(val neuItem: String) {
}
companion object {
val COINS: SkyblockId = SkyblockId("SKYBLOCK_COIN")
val COINS: SkyblockId = SkyblockId(NEUIngredient.NEU_SENTINEL_COINS)
val SENTINEL_EMPTY: SkyblockId = SkyblockId(NEUIngredient.NEU_SENTINEL_EMPTY)
private val bazaarEnchantmentRegex = "ENCHANTMENT_(\\D*)_(\\d+)".toRegex()
val NULL: SkyblockId = SkyblockId("null")
val PET_NULL: SkyblockId = SkyblockId("null_pet")
@@ -70,6 +72,7 @@ value class SkyblockId(val neuItem: String) {
}
val NEUItem.skyblockId get() = SkyblockId(skyblockItemId)
val NEUIngredient.skyblockId get() = SkyblockId(itemId)
fun NEUItem.guessRecipeId(): String? {
if (!skyblockItemId.contains(";")) return skyblockItemId

View File

@@ -2,10 +2,13 @@ package moe.nea.firmament.util.compatloader
import java.util.ServiceLoader
import net.fabricmc.loader.api.FabricLoader
import kotlin.reflect.KClass
import kotlin.streams.asSequence
import moe.nea.firmament.Firmament
abstract class CompatLoader<T : Any>(val kClass: Class<T>) {
constructor(kClass: KClass<T>) : this(kClass.java)
val loader: ServiceLoader<T> = ServiceLoader.load(kClass)
val allValidInstances by lazy {
loader.reload()