diff options
author | Empa <42304516+ItsEmpa@users.noreply.github.com> | 2024-06-09 19:41:41 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-09 19:41:41 +0200 |
commit | cc629382945460d48fc9fa6472106df9fcbb589d (patch) | |
tree | 64caa8a0cee2d321f700b7d97fb13754319f82d4 /src/main/java/at/hannibal2/skyhanni/features | |
parent | fc8e81a9f88b01ec63d8fa1d7f0d6ebbdc51d836 (diff) | |
download | skyhanni-cc629382945460d48fc9fa6472106df9fcbb589d.tar.gz skyhanni-cc629382945460d48fc9fa6472106df9fcbb589d.tar.bz2 skyhanni-cc629382945460d48fc9fa6472106df9fcbb589d.zip |
Feature: Custom Wardrobe (#2039)
Co-authored-by: J10a1n15 <45315647+j10a1n15@users.noreply.github.com>
Co-authored-by: Cal <cwolfson58@gmail.com>
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/features')
5 files changed, 885 insertions, 74 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/CustomWardrobe.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/CustomWardrobe.kt new file mode 100644 index 000000000..243b3132f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/CustomWardrobe.kt @@ -0,0 +1,595 @@ +package at.hannibal2.skyhanni.features.inventory.wardrobe + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.core.config.Position +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryUpdatedEvent +import at.hannibal2.skyhanni.features.inventory.wardrobe.WardrobeAPI.MAX_PAGES +import at.hannibal2.skyhanni.features.inventory.wardrobe.WardrobeAPI.MAX_SLOT_PER_PAGE +import at.hannibal2.skyhanni.mixins.transformers.gui.AccessorGuiContainer +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ColorUtils.addAlpha +import at.hannibal2.skyhanni.utils.ColorUtils.darker +import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor +import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColorInt +import at.hannibal2.skyhanni.utils.ColorUtils.withAlpha +import at.hannibal2.skyhanni.utils.ConditionalUtils +import at.hannibal2.skyhanni.utils.ConditionalUtils.transformIf +import at.hannibal2.skyhanni.utils.ConfigUtils.jumpToEditor +import at.hannibal2.skyhanni.utils.DelayedRun +import at.hannibal2.skyhanni.utils.EntityUtils.getFakePlayer +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.removeEnchants +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.HorizontalAlignment +import at.hannibal2.skyhanni.utils.RenderUtils.VerticalAlignment +import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderable +import at.hannibal2.skyhanni.utils.renderables.Renderable +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.client.renderer.GlStateManager +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.awt.Color +import kotlin.math.min +import kotlin.time.Duration.Companion.milliseconds + +@SkyHanniModule +object CustomWardrobe { + + val config get() = SkyHanniMod.feature.inventory.customWardrobe + + private var displayRenderable: Renderable? = null + private var inventoryButton: Renderable? = null + private var editMode = false + private var waitingForInventoryUpdate = false + + private var activeScale: Int = 100 + private var currentMaxSize: Pair<Int, Int>? = null + private var lastScreenSize: Pair<Int, Int>? = null + private var guiName = "Custom Wardrobe" + + @SubscribeEvent + fun onGuiRender(event: GuiContainerEvent.BeforeDraw) { + if (!isEnabled() || editMode) return + val renderable = displayRenderable ?: run { + update() + displayRenderable ?: return + } + + val gui = event.gui + val screenSize = gui.width to gui.height + + if (screenSize != lastScreenSize) { + lastScreenSize = screenSize + val shouldUpdate = updateScreenSize(screenSize) + if (shouldUpdate) { + update() + return + } + } + + val (width, height) = renderable.width to renderable.height + val pos = Position((gui.width - width) / 2, (gui.height - height) / 2) + if (waitingForInventoryUpdate && config.loadingText) { + val loadingRenderable = Renderable.string( + "§cLoading...", + scale = activeScale / 100.0 + ) + val loadingPos = + Position(pos.rawX + (width - loadingRenderable.width) / 2, pos.rawY - loadingRenderable.height) + loadingPos.renderRenderable(loadingRenderable, posLabel = guiName, addToGuiManager = false) + } + + GlStateManager.translate(0f, 0f, 100f) + pos.renderRenderable(renderable, posLabel = guiName, addToGuiManager = false) + GlStateManager.translate(0f, 0f, -100f) + event.cancel() + } + + // Edit button in normal wardrobe while in edit mode + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { + if (!isEnabled()) return + if (!editMode) return + val gui = Minecraft.getMinecraft().currentScreen as? GuiContainer ?: return + val renderable = inventoryButton ?: addReEnableButton().also { inventoryButton = it } + val accessorGui = gui as AccessorGuiContainer + val posX = accessorGui.guiLeft + (1.05 * accessorGui.width).toInt() + val posY = accessorGui.guiTop + (accessorGui.height - renderable.height) / 2 + Position(posX, posY).renderRenderable(renderable, posLabel = guiName, addToGuiManager = false) + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + waitingForInventoryUpdate = false + if (!isEnabled()) return + DelayedRun.runDelayed(250.milliseconds) { + if (!WardrobeAPI.inWardrobe()) { + reset() + } + } + } + + @SubscribeEvent + fun onConfigUpdate(event: ConfigLoadEvent) { + with(config.spacing) { + ConditionalUtils.onToggle( + globalScale, outlineThickness, outlineBlur, + slotWidth, slotHeight, playerScale, + maxPlayersPerRow, horizontalSpacing, verticalSpacing, + buttonSlotsVerticalSpacing, buttonHorizontalSpacing, buttonVerticalSpacing, + buttonWidth, buttonHeight, backgroundPadding, + ) { + currentMaxSize = null + lastScreenSize = null + } + } + } + + @SubscribeEvent + fun onInventoryUpdate(event: InventoryUpdatedEvent) { + if (!isEnabled() || editMode) return + update() + } + + private fun update() { + displayRenderable = createRenderables() + } + + private fun updateScreenSize(gui: Pair<Int, Int>): Boolean { + val renderable = currentMaxSize ?: run { + activeScale = config.spacing.globalScale.get() + update() + return true + } + val previousActiveScale = activeScale + val unscaledRenderableWidth = renderable.first / activeScale + val unscaledRenderableHeight = renderable.second / activeScale + val autoScaleWidth = 0.95 * gui.first / unscaledRenderableWidth + val autoScaleHeight = 0.95 * gui.second / unscaledRenderableHeight + val maxScale = min(autoScaleWidth, autoScaleHeight).toInt() + + activeScale = config.spacing.globalScale.get().coerceAtMost(maxScale) + + return activeScale != previousActiveScale + } + + private fun createWarning(list: List<WardrobeSlot>): Pair<String?, List<WardrobeSlot>> { + var wardrobeWarning: String? = null + var wardrobeSlots = list + + if (wardrobeSlots.isEmpty()) wardrobeWarning = "§cYour wardrobe is empty :(" + + if (config.hideLockedSlots) { + wardrobeSlots = wardrobeSlots.filter { !it.locked } + if (wardrobeSlots.isEmpty()) wardrobeWarning = "§cAll your slots are locked? Somehow" + } + + if (config.hideEmptySlots) { + wardrobeSlots = wardrobeSlots.filter { !it.isEmpty() } + if (wardrobeSlots.isEmpty()) wardrobeWarning = "§cAll slots are empty :(" + } + if (config.onlyFavorites) { + wardrobeSlots = wardrobeSlots.filter { it.favorite || it.isCurrentSlot() } + if (wardrobeSlots.isEmpty()) wardrobeWarning = "§cDidn't set any favorites" + } + + return wardrobeWarning to wardrobeSlots + } + + private fun createArmorTooltipRenderable( + slot: WardrobeSlot, + containerHeight: Int, + containerWidth: Int, + ): Renderable { + val loreList = mutableListOf<Renderable>() + val height = containerHeight - 3 + + // This is needed to keep the background size the same as the player renderable size + val hoverableSizes = MutableList(4) { height / 4 }.apply { + for (k in 0 until height % 4) this[k]++ + } + + for (armorIndex in 0 until 4) { + val stack = slot.armor[armorIndex]?.copy() + if (stack == null) { + loreList.add(Renderable.placeholder(containerWidth, hoverableSizes[armorIndex])) + } else { + loreList.add( + Renderable.hoverable( + Renderable.hoverTips( + Renderable.placeholder(containerWidth, hoverableSizes[armorIndex]), + stack.getTooltip(Minecraft.getMinecraft().thePlayer, false) + ), + Renderable.placeholder(containerWidth, hoverableSizes[armorIndex]), + bypassChecks = true + ) + ) + } + } + return Renderable.verticalContainer(loreList, spacing = 1) + } + + private fun createFakePlayerRenderable( + slot: WardrobeSlot, + playerWidth: Double, + containerHeight: Int, + containerWidth: Int, + ): Renderable { + val fakePlayer = getFakePlayer() + var scale = playerWidth + + fakePlayer.inventory.armorInventory = + slot.armor.map { it?.copy()?.removeEnchants() }.reversed().toTypedArray() + + val playerColor = if (!slot.isInCurrentPage()) { + scale *= 0.9 + Color.GRAY.withAlpha(100) + } else null + + return Renderable.fakePlayer( + fakePlayer, + followMouse = config.eyesFollowMouse, + width = containerWidth, + height = containerHeight, + entityScale = scale.toInt(), + padding = 0, + color = playerColor, + ) + } + + private fun createRenderables(): Renderable { + val (wardrobeWarning, list) = createWarning(WardrobeAPI.slots) + + val maxPlayersPerRow = config.spacing.maxPlayersPerRow.get().coerceAtLeast(1) + val maxPlayersRows = ((MAX_SLOT_PER_PAGE * MAX_PAGES - 1) / maxPlayersPerRow) + 1 + val containerWidth = (config.spacing.slotWidth.get() * (activeScale / 100.0)).toInt() + val containerHeight = (config.spacing.slotHeight.get() * (activeScale / 100.0)).toInt() + val playerWidth = (containerWidth * (config.spacing.playerScale.get() / 100.0)) + val horizontalSpacing = (config.spacing.horizontalSpacing.get() * (activeScale / 100.0)).toInt() + val verticalSpacing = (config.spacing.verticalSpacing.get() * (activeScale / 100.0)).toInt() + val backgroundPadding = (config.spacing.backgroundPadding.get() * (activeScale / 100.0)).toInt() + val buttonVerticalSpacing = (config.spacing.buttonVerticalSpacing.get() * (activeScale / 100.0)).toInt() + + var maxRenderableWidth = maxPlayersPerRow * containerWidth + (maxPlayersPerRow - 1) * horizontalSpacing + var maxRenderableHeight = maxPlayersRows * containerHeight + (maxPlayersRows - 1) * verticalSpacing + + val button = addButtons() + + if (button.width > maxRenderableWidth) maxRenderableWidth = button.width + maxRenderableHeight += button.height + buttonVerticalSpacing + + maxRenderableWidth += 2 * backgroundPadding + maxRenderableHeight += 2 * backgroundPadding + currentMaxSize = maxRenderableWidth to maxRenderableHeight + + wardrobeWarning?.let { text -> + val warningRenderable = Renderable.wrappedString( + text, + maxRenderableWidth, + 3.0 * (activeScale / 100.0), + horizontalAlign = HorizontalAlignment.CENTER + ) + val withButtons = Renderable.verticalContainer( + listOf(warningRenderable, button), + buttonVerticalSpacing, + horizontalAlign = HorizontalAlignment.CENTER + ) + return addGuiBackground(withButtons, backgroundPadding) + } + + val chunkedList = list.chunked(maxPlayersPerRow) + + val rowsRenderables = chunkedList.map { row -> + val slotsRenderables = row.map { slot -> + val armorTooltipRenderable = createArmorTooltipRenderable(slot, containerHeight, containerWidth) + + val playerBackground = createHoverableRenderable( + armorTooltipRenderable, + topLayerRenderable = addSlotHoverableButtons(slot), + hoveredColor = slot.getSlotColor(), + borderOutlineThickness = config.spacing.outlineThickness.get(), + borderOutlineBlur = config.spacing.outlineBlur.get(), + onClick = { slot.clickSlot() } + ) + + val playerRenderable = createFakePlayerRenderable(slot, playerWidth, containerHeight, containerWidth) + + Renderable.doubleLayered(playerBackground, playerRenderable, false) + } + Renderable.horizontalContainer(slotsRenderables, horizontalSpacing) + } + + val allSlotsRenderable = Renderable.verticalContainer( + rowsRenderables, + verticalSpacing, + horizontalAlign = HorizontalAlignment.CENTER + ) + + val withButtons = Renderable.verticalContainer( + listOf(allSlotsRenderable, button), + buttonVerticalSpacing, + horizontalAlign = HorizontalAlignment.CENTER + ) + + return addGuiBackground(withButtons, backgroundPadding) + } + + private fun addGuiBackground(renderable: Renderable, borderPadding: Int) = + Renderable.drawInsideRoundedRect( + Renderable.doubleLayered( + renderable, + Renderable.clickable( + Renderable.string( + "§7SkyHanni", + horizontalAlign = HorizontalAlignment.RIGHT, + verticalAlign = VerticalAlignment.BOTTOM, + scale = 1.0 * (activeScale / 100.0) + ).let { Renderable.hoverable(hovered = Renderable.underlined(it), unhovered = it) }, + onClick = { + config::enabled.jumpToEditor() + reset() + WardrobeAPI.currentPage = null + } + ), + blockBottomHover = false + ), + config.color.backgroundColor.toChromaColor(), + padding = borderPadding + ) + + private fun reset() { + WardrobeAPI.inCustomWardrobe = false + editMode = false + displayRenderable = null + inventoryButton = null + } + + private fun addButtons(): Renderable { + val (horizontalSpacing, verticalSpacing) = with(config.spacing) { + buttonHorizontalSpacing.get() * (activeScale / 100.0) to buttonVerticalSpacing.get() * (activeScale / 100.0) + } + + val backButton = createLabeledButton( + "§aBack", + onClick = { + InventoryUtils.clickSlot(48) + reset() + WardrobeAPI.currentPage = null + } + ) + val exitButton = createLabeledButton( + "§cClose", + onClick = { + InventoryUtils.clickSlot(49) + reset() + WardrobeAPI.currentPage = null + } + ) + + val greenColor = Color(85, 255, 85, 200) + val redColor = Color(255, 85, 85, 200) + + val onlyFavoriteButton = createLabeledButton( + "§eFavorite", + hoveredColor = if (config.onlyFavorites) greenColor else redColor, + onClick = { + config.onlyFavorites = !config.onlyFavorites + update() + } + ) + + val editButton = createLabeledButton( + "§bEdit", + onClick = { + DelayedRun.runNextTick { + reset() + editMode = true + } + } + ) + + val row = Renderable.horizontalContainer( + listOf(backButton, exitButton, onlyFavoriteButton), + horizontalSpacing.toInt(), + horizontalAlign = HorizontalAlignment.CENTER, + ) + + val total = Renderable.verticalContainer( + listOf(row, editButton), + verticalSpacing.toInt(), + horizontalAlign = HorizontalAlignment.CENTER, + verticalAlign = VerticalAlignment.CENTER + ) + + return total + } + + private fun addReEnableButton(): Renderable { + val color = Color(116, 150, 255, 200) + return createLabeledButton( + "§bEdit", + hoveredColor = color, + unhoveredColor = color.darker(0.8), + onClick = { + WardrobeAPI.inCustomWardrobe = false + editMode = false + update() + } + ) + } + + private fun addSlotHoverableButtons(wardrobeSlot: WardrobeSlot): Renderable { + val list = mutableListOf<Renderable>() + val textScale = 1.5 * (activeScale / 100.0) + list.add( + Renderable.clickable( + Renderable.hoverable( + Renderable.string( + (if (wardrobeSlot.favorite) "§c" else "§7") + "❤", + scale = textScale, + horizontalAlign = HorizontalAlignment.CENTER, + verticalAlign = VerticalAlignment.CENTER + ), + Renderable.string( + (if (wardrobeSlot.favorite) "§4" else "§8") + "❤", + scale = textScale, + horizontalAlign = HorizontalAlignment.CENTER, + verticalAlign = VerticalAlignment.CENTER + ) + ), + onClick = { + wardrobeSlot.favorite = !wardrobeSlot.favorite + update() + } + ) + ) + + if (config.estimatedValue && !wardrobeSlot.isEmpty()) { + val lore = WardrobeAPI.createPriceLore(wardrobeSlot) + list.add( + Renderable.hoverTips( + Renderable.string( + "§2$", + scale = textScale, + horizontalAlign = HorizontalAlignment.CENTER, + verticalAlign = VerticalAlignment.CENTER, + ), + lore, + ), + ) + } + + return Renderable.verticalContainer(list, 1, HorizontalAlignment.RIGHT) + } + + private fun createLabeledButton( + text: String, + hoveredColor: Color = Color(130, 130, 130, 200), + unhoveredColor: Color = hoveredColor.darker(0.57), + onClick: () -> Unit, + ): Renderable { + val buttonWidth = (config.spacing.buttonWidth.get() * (activeScale / 100.0)).toInt() + val buttonHeight = (config.spacing.buttonHeight.get() * (activeScale / 100.0)).toInt() + val textScale = (activeScale / 100.0) + + val renderable = Renderable.hoverable( + Renderable.drawInsideRoundedRectWithOutline( + Renderable.doubleLayered( + Renderable.clickable( + Renderable.placeholder(buttonWidth, buttonHeight), + onClick + ), + Renderable.string( + text, + horizontalAlign = HorizontalAlignment.CENTER, + verticalAlign = VerticalAlignment.CENTER, + scale = textScale + ), + false, + ), + hoveredColor, + padding = 0, + topOutlineColor = config.color.topBorderColor.toChromaColorInt(), + bottomOutlineColor = config.color.bottomBorderColor.toChromaColorInt(), + borderOutlineThickness = 2, + horizontalAlign = HorizontalAlignment.CENTER + ), + Renderable.drawInsideRoundedRect( + Renderable.doubleLayered( + Renderable.placeholder(buttonWidth, buttonHeight), + Renderable.string( + text, + horizontalAlign = HorizontalAlignment.CENTER, + verticalAlign = VerticalAlignment.CENTER, + scale = textScale + ), + ), + unhoveredColor.darker(0.57), + padding = 0, + horizontalAlign = HorizontalAlignment.CENTER + ) + ) + + return renderable + } + + private fun createHoverableRenderable( + hoveredRenderable: Renderable, + unhoveredRenderable: Renderable = Renderable.placeholder(hoveredRenderable.width, hoveredRenderable.height), + topLayerRenderable: Renderable = Renderable.placeholder(0, 0), + padding: Int = 0, + horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER, + verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER, + hoveredColor: Color, + unHoveredColor: Color = hoveredColor, + borderOutlineThickness: Int, + borderOutlineBlur: Float = 0.5f, + onClick: () -> Unit, + onHover: () -> Unit = {}, + ): Renderable = + Renderable.hoverable( + Renderable.drawInsideRoundedRectWithOutline( + Renderable.doubleLayered( + Renderable.clickable( + hoveredRenderable, + onClick, + ), + topLayerRenderable, + ), + hoveredColor, + padding = padding, + topOutlineColor = config.color.topBorderColor.toChromaColorInt(), + bottomOutlineColor = config.color.bottomBorderColor.toChromaColorInt(), + borderOutlineThickness = borderOutlineThickness, + blur = borderOutlineBlur, + horizontalAlign = horizontalAlignment, + verticalAlign = verticalAlignment, + ), + Renderable.drawInsideRoundedRect( + unhoveredRenderable, + unHoveredColor, + padding = padding, + horizontalAlign = horizontalAlignment, + verticalAlign = verticalAlignment + ), + onHover = { onHover() }, + ) + + private fun WardrobeSlot.clickSlot() { + val previousPageSlot = 45 + val nextPageSlot = 53 + val wardrobePage = WardrobeAPI.currentPage ?: return + if (isInCurrentPage()) { + if (isEmpty() || locked || waitingForInventoryUpdate) return + WardrobeAPI.currentSlot = if (isCurrentSlot()) null else id + InventoryUtils.clickSlot(inventorySlot) + } else { + if (page < wardrobePage) { + WardrobeAPI.currentPage = wardrobePage - 1 + waitingForInventoryUpdate = true + InventoryUtils.clickSlot(previousPageSlot) + } else if (page > wardrobePage) { + WardrobeAPI.currentPage = wardrobePage + 1 + waitingForInventoryUpdate = true + InventoryUtils.clickSlot(nextPageSlot) + } + } + update() + } + + private fun WardrobeSlot.getSlotColor(): Color = with(config.color) { + when { + isCurrentSlot() -> equippedColor + favorite -> favoriteColor + else -> null + }?.toChromaColor()?.transformIf({ isInCurrentPage() }) { darker() } + ?: (if (isInCurrentPage()) samePageColor else otherPageColor).toChromaColor() + .transformIf({ locked || isEmpty() }) { darker(0.2) }.addAlpha(100) + } + + fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled && WardrobeAPI.inWardrobe() +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/EstimatedWardrobePrice.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/EstimatedWardrobePrice.kt new file mode 100644 index 000000000..d55b30cf1 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/EstimatedWardrobePrice.kt @@ -0,0 +1,41 @@ +package at.hannibal2.skyhanni.features.inventory.wardrobe + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.events.LorenzToolTipEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@SkyHanniModule +object EstimatedWardrobePrice { + + private val config get() = SkyHanniMod.feature.inventory.estimatedItemValues + + @SubscribeEvent + fun onTooltip(event: LorenzToolTipEvent) { + if (!isEnabled()) return + + val slot = WardrobeAPI.slots.firstOrNull { + event.slot.slotNumber == it.inventorySlot && it.isInCurrentPage() + } ?: return + + + val lore = WardrobeAPI.createPriceLore(slot) + if (lore.isEmpty()) return + + val tooltip = event.toolTip + var index = 3 + + tooltip.add(index++, "") + tooltip.addAll(index, lore) + } + + private fun isEnabled() = + LorenzUtils.inSkyBlock && config.armor && WardrobeAPI.inWardrobe() && !WardrobeAPI.inCustomWardrobe + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(3, "misc.estimatedIemValueArmor", "misc.estimatedItemValues.armor") + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/WardrobeAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/WardrobeAPI.kt new file mode 100644 index 000000000..013aa1866 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/WardrobeAPI.kt @@ -0,0 +1,207 @@ +package at.hannibal2.skyhanni.features.inventory.wardrobe + +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.events.DebugDataCollectEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryOpenEvent +import at.hannibal2.skyhanni.events.InventoryUpdatedEvent +import at.hannibal2.skyhanni.features.misc.items.EstimatedItemValueCalculator +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.DelayedRun +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.name +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import com.google.gson.annotations.Expose +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.EnumDyeColor +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration.Companion.milliseconds + +@SkyHanniModule +object WardrobeAPI { + + val storage get() = ProfileStorageData.profileSpecific?.wardrobe + + private val repoGroup = RepoPattern.group("inventory.wardrobe") + private val inventoryPattern by repoGroup.pattern( + "inventory.name", + "Wardrobe \\((?<currentPage>\\d+)/\\d+\\)" + ) + + /** + * REGEX-TEST: §7Slot 4: §aEquipped + */ + private val equippedSlotPattern by repoGroup.pattern( + "equippedslot", + "§7Slot \\d+: §aEquipped" + ) + + private const val FIRST_SLOT = 36 + private const val FIRST_HELMET_SLOT = 0 + private const val FIRST_CHESTPLATE_SLOT = 9 + private const val FIRST_LEGGINGS_SLOT = 18 + private const val FIRST_BOOTS_SLOT = 27 + const val MAX_SLOT_PER_PAGE = 9 + const val MAX_PAGES = 2 + + var slots = listOf<WardrobeSlot>() + var inCustomWardrobe = false + + internal fun emptyArmor(): List<ItemStack?> = listOf(null, null, null, null) + + var currentSlot: Int? + get() = storage?.currentSlot + set(value) { + storage?.currentSlot = value + } + + var currentPage: Int? = null + private var inWardrobe = false + + init { + val list = mutableListOf<WardrobeSlot>() + var id = 0 + + for (page in 1..MAX_PAGES) { + for (slot in 0 until MAX_SLOT_PER_PAGE) { + val inventorySlot = FIRST_SLOT + slot + val helmetSlot = FIRST_HELMET_SLOT + slot + val chestplateSlot = FIRST_CHESTPLATE_SLOT + slot + val leggingsSlot = FIRST_LEGGINGS_SLOT + slot + val bootsSlot = FIRST_BOOTS_SLOT + slot + list.add(WardrobeSlot(++id, page, inventorySlot, helmetSlot, chestplateSlot, leggingsSlot, bootsSlot)) + } + } + slots = list + } + + private fun getWardrobeItem(itemStack: ItemStack?) = + if (itemStack?.item == ItemStack(Blocks.stained_glass_pane).item || itemStack == null) null else itemStack + + private fun getWardrobeSlotFromId(id: Int?) = slots.find { it.id == id } + + fun inWardrobe() = InventoryUtils.inInventory() && inWardrobe + + fun createPriceLore(slot: WardrobeSlot) = buildList { + if (slot.isEmpty()) return@buildList + add("§aEstimated Armor Value:") + var totalPrice = 0.0 + for (stack in slot.armor.filterNotNull()) { + val price = EstimatedItemValueCalculator.getTotalPrice(stack) + add(" §7- ${stack.name}: §6${NumberUtil.format(price)}") + totalPrice += price + } + if (totalPrice != 0.0) add(" §aTotal Value: §6§l${NumberUtil.format(totalPrice)} coins") + } + + @SubscribeEvent + fun onInventoryOpen(event: InventoryOpenEvent) { + inventoryPattern.matches(event.inventoryName).let { + inWardrobe = it + if (CustomWardrobe.config.enabled) inCustomWardrobe = it + } + } + + @SubscribeEvent + fun onInventoryUpdate(event: InventoryUpdatedEvent) { + if (!LorenzUtils.inSkyBlock) return + + inventoryPattern.matchMatcher(event.inventoryName) { + inWardrobe = true + currentPage = group("currentPage").formatInt() + } ?: return + + val itemsList = event.inventoryItems + + val allGrayDye = slots.all { + itemsList[it.inventorySlot]?.itemDamage == EnumDyeColor.GRAY.dyeDamage || !it.isInCurrentPage() + } + + if (allGrayDye) { + val allSlotsEmpty = slots.filter { it.isInCurrentPage() }.all { slot -> + (slot.inventorySlots.all { getWardrobeItem(itemsList[it]) == null }) + } + if (allSlotsEmpty) { + for (slot in slots.filter { it.isInCurrentPage() }) { + slot.getData()?.armor = emptyArmor() + } + } else return + } + + val foundCurrentSlot = processSlots(slots, itemsList) + if (!foundCurrentSlot && getWardrobeSlotFromId(currentSlot)?.page == currentPage) { + currentSlot = null + } + } + + private fun processSlots(slots: List<WardrobeSlot>, itemsList: Map<Int, ItemStack>): Boolean { + var foundCurrentSlot = false + + for (slot in slots.filter { it.isInCurrentPage() }) { + slot.getData()?.armor = listOf( + getWardrobeItem(itemsList[slot.helmetSlot]), + getWardrobeItem(itemsList[slot.chestplateSlot]), + getWardrobeItem(itemsList[slot.leggingsSlot]), + getWardrobeItem(itemsList[slot.bootsSlot]), + ) + if (equippedSlotPattern.matches(itemsList[slot.inventorySlot]?.name)) { + currentSlot = slot.id + foundCurrentSlot = true + } + slot.locked = (itemsList[slot.inventorySlot] == ItemStack(Items.dye, EnumDyeColor.RED.dyeDamage)) + if (slot.locked) slots.forEach { if (it.id > slot.id) it.locked = true } + } + + return foundCurrentSlot + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + if (!inWardrobe) return + DelayedRun.runDelayed(250.milliseconds) { + if (!inventoryPattern.matches(InventoryUtils.openInventoryName())) { + inWardrobe = false + currentPage = null + } + } + } + + @SubscribeEvent + fun onDebugCollect(event: DebugDataCollectEvent) { + event.title("Wardrobe") + event.addIrrelevant { + for (slot in slots) { + val slotInfo = buildString { + append("Slot ${slot.id}") + if (slot.favorite) append(" - Favorite: true") + } + if (slot.locked) { + add("$slotInfo is locked") + } else if (slot.isEmpty()) { + add("$slotInfo is empty") + } else { + add(slotInfo) + setOf("Helmet", "Chestplate", "Leggings", "Boots").forEachIndexed { id, armourName -> + slot.getData()?.armor?.get(id)?.name?.let { name -> + add(" $armourName: $name") + } + } + } + } + } + } + + class WardrobeData( + @Expose val id: Int, + @Expose var armor: List<ItemStack?>, + @Expose var locked: Boolean, + @Expose var favorite: Boolean, + ) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/WardrobeSlot.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/WardrobeSlot.kt new file mode 100644 index 000000000..e500887f9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/wardrobe/WardrobeSlot.kt @@ -0,0 +1,42 @@ +package at.hannibal2.skyhanni.features.inventory.wardrobe + +class WardrobeSlot( + val id: Int, + val page: Int, + val inventorySlot: Int, + val helmetSlot: Int, + val chestplateSlot: Int, + val leggingsSlot: Int, + val bootsSlot: Int, +) { + fun getData() = WardrobeAPI.storage?.data?.getOrPut(id) { + WardrobeAPI.WardrobeData( + id, + armor = WardrobeAPI.emptyArmor(), + locked = true, + favorite = false, + ) + } + + var locked: Boolean + get() = getData()?.locked ?: true + set(value) { + getData()?.locked = value + } + + var favorite: Boolean + get() = getData()?.favorite ?: false + set(value) { + getData()?.favorite = value + } + + val armor get() = getData()?.armor ?: WardrobeAPI.emptyArmor() + + val inventorySlots = listOf(helmetSlot, chestplateSlot, leggingsSlot, bootsSlot) + + fun isEmpty(): Boolean = armor.all { it == null } + + fun isCurrentSlot() = getData()?.id == WardrobeAPI.currentSlot + + fun isInCurrentPage() = (WardrobeAPI.currentPage == null && page == 1) || (page == WardrobeAPI.currentPage) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedWardrobePrice.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedWardrobePrice.kt deleted file mode 100644 index 5e536ad01..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedWardrobePrice.kt +++ /dev/null @@ -1,74 +0,0 @@ -package at.hannibal2.skyhanni.features.misc.items - -import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator -import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent -import at.hannibal2.skyhanni.events.LorenzToolTipEvent -import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.utils.InventoryUtils -import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull -import at.hannibal2.skyhanni.utils.ItemUtils.name -import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.NumberUtil -import net.minecraft.item.ItemStack -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - -@SkyHanniModule -object EstimatedWardrobePrice { - - private val config get() = SkyHanniMod.feature.inventory.estimatedItemValues - var data = mutableMapOf<Int, MutableList<ItemStack>>() - - @SubscribeEvent - fun onTooltip(event: LorenzToolTipEvent) { - if (!LorenzUtils.inSkyBlock) return - if (!config.armor) return - if (!InventoryUtils.openInventoryName().contains("Wardrobe")) return - - val slot = event.slot.slotNumber - val id = slot % 9 - // Only showing in the armor select line - if (slot - id != 36) return - val items = data[id] ?: return - - var index = 3 - val toolTip = event.toolTip - if (toolTip.size < 4) return - toolTip.add(index++, "") - toolTip.add(index++, "§aEstimated Armor Value:") - - var totalPrice = 0.0 - for (item in items) { - - val price = EstimatedItemValueCalculator.getTotalPrice(item) - totalPrice += price - - toolTip.add(index++, " §7- ${item.name}: §6${NumberUtil.format(price)}") - } - toolTip.add(index, " §aTotal Value: §6§l${NumberUtil.format(totalPrice)} coins") - } - - @SubscribeEvent - fun onInventoryOpen(event: InventoryFullyOpenedEvent) { - if (!LorenzUtils.inSkyBlock) return - if (!config.armor) return - if (!event.inventoryName.startsWith("Wardrobe")) return - - val map = mutableMapOf<Int, MutableList<ItemStack>>() - - for ((slot, item) in event.inventoryItems) { - item.getInternalNameOrNull() ?: continue - val price = EstimatedItemValueCalculator.getTotalPrice(item) - if (price == 0.0) continue - val id = slot % 9 - val list = map.getOrPut(id) { mutableListOf() } - list.add(item) - } - data = map - } - - @SubscribeEvent - fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { - event.move(3, "misc.estimatedIemValueArmor", "misc.estimatedItemValues.armor") - } -} |