diff options
Diffstat (limited to 'src/main')
27 files changed, 905 insertions, 302 deletions
diff --git a/src/main/kotlin/features/debug/SoundVisualizer.kt b/src/main/kotlin/features/debug/SoundVisualizer.kt new file mode 100644 index 0000000..cc14122 --- /dev/null +++ b/src/main/kotlin/features/debug/SoundVisualizer.kt @@ -0,0 +1,65 @@ +package moe.nea.firmament.features.debug + +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.commands.thenLiteral +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.SoundReceiveEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.util.red +import moe.nea.firmament.util.render.RenderInWorldContext + +object SoundVisualizer { + + var showSounds = false + + var sounds = mutableListOf<SoundReceiveEvent>() + + + @Subscribe + fun onSubCommand(event: CommandEvent.SubCommand) { + event.subcommand("dev") { + thenLiteral("sounds") { + thenExecute { + showSounds = !showSounds + if (!showSounds) { + sounds.clear() + } + } + } + } + } + + @Subscribe + fun onWorldSwap(event: WorldReadyEvent) { + sounds.clear() + } + + @Subscribe + fun onRender(event: WorldRenderLastEvent) { + RenderInWorldContext.renderInWorld(event) { + sounds.forEach { event -> + withFacingThePlayer(event.position) { + text( + Text.literal(event.sound.value().id.toString()).also { + if (event.cancelled) + it.red() + }, + verticalAlign = RenderInWorldContext.VerticalAlign.CENTER, + ) + } + } + } + } + + @Subscribe + fun onSoundReceive(event: SoundReceiveEvent) { + if (!showSounds) return + if (sounds.size > 1000) { + sounds.subList(0, 200).clear() + } + sounds.add(event) + } +} diff --git a/src/main/kotlin/features/garden/HideComposterNoises.kt b/src/main/kotlin/features/garden/HideComposterNoises.kt new file mode 100644 index 0000000..69207a9 --- /dev/null +++ b/src/main/kotlin/features/garden/HideComposterNoises.kt @@ -0,0 +1,32 @@ +package moe.nea.firmament.features.garden + +import net.minecraft.entity.passive.WolfSoundVariants +import net.minecraft.sound.SoundEvent +import net.minecraft.sound.SoundEvents +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.SoundReceiveEvent +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland + +object HideComposterNoises { + object TConfig : ManagedConfig("composter", Category.GARDEN) { + val hideComposterNoises by toggle("no-more-noises") { false } + } + + val composterSoundEvents: List<SoundEvent> = listOf( + SoundEvents.BLOCK_PISTON_EXTEND, + SoundEvents.BLOCK_WATER_AMBIENT, + SoundEvents.ENTITY_CHICKEN_EGG, + SoundEvents.WOLF_SOUNDS[WolfSoundVariants.Type.CLASSIC]!!.growlSound().value(), + ) + + @Subscribe + fun onNoise(event: SoundReceiveEvent) { + if (!TConfig.hideComposterNoises) return + if (SBData.skyblockLocation == SkyBlockIsland.GARDEN) { + if (event.sound.value() in composterSoundEvents) + event.cancel() + } + } +} diff --git a/src/main/kotlin/features/inventory/PriceData.kt b/src/main/kotlin/features/inventory/PriceData.kt index a724d63..e247080 100644 --- a/src/main/kotlin/features/inventory/PriceData.kt +++ b/src/main/kotlin/features/inventory/PriceData.kt @@ -1,6 +1,8 @@ package moe.nea.firmament.features.inventory +import org.lwjgl.glfw.GLFW import net.minecraft.text.Text +import net.minecraft.util.StringIdentifiable import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.features.FirmamentFeature @@ -8,6 +10,7 @@ import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.repo.HypixelStaticData import moe.nea.firmament.util.FirmFormatters.formatCommas import moe.nea.firmament.util.bold +import moe.nea.firmament.util.darkGrey import moe.nea.firmament.util.gold import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.tr @@ -20,6 +23,23 @@ object PriceData : FirmamentFeature { object TConfig : ManagedConfig(identifier, Category.INVENTORY) { val tooltipEnabled by toggle("enable-always") { true } val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind") + val stackSizeKey by keyBinding("stack-size-keybind") { GLFW.GLFW_KEY_LEFT_SHIFT } + val avgLowestBin by choice( + "avg-lowest-bin-days", + ) { + AvgLowestBin.THREEDAYAVGLOWESTBIN + } + } + + enum class AvgLowestBin : StringIdentifiable { + OFF, + ONEDAYAVGLOWESTBIN, + THREEDAYAVGLOWESTBIN, + SEVENDAYAVGLOWESTBIN; + + override fun asString(): String { + return name + } } override val config get() = TConfig @@ -32,7 +52,7 @@ object PriceData : FirmamentFeature { .append(": ") .append( Text.literal(formatCommas(price, fractionalDigits = 1)) - .append(if(price != 1.0) " coins" else " coin") + .append(if (price != 1.0) " coins" else " coin") .gold() .bold() ) @@ -44,30 +64,57 @@ object PriceData : FirmamentFeature { return } val sbId = it.stack.skyBlockId + val stackSize = it.stack.count + val isShowingStack = TConfig.stackSizeKey.isPressed() + val multiplier = if (isShowingStack) stackSize else 1 + val multiplierText = + if (isShowingStack) + tr("firmament.tooltip.multiply", "Showing prices for x${stackSize}").darkGrey() + else + tr( + "firmament.tooltip.multiply.hint", + "[${TConfig.stackSizeKey.format()}] to show x${stackSize}" + ).darkGrey() val bazaarData = HypixelStaticData.bazaarData[sbId] val lowestBin = HypixelStaticData.lowestBin[sbId] + val avgBinValue: Double? = when (TConfig.avgLowestBin) { + AvgLowestBin.ONEDAYAVGLOWESTBIN -> HypixelStaticData.avg1dlowestBin[sbId] + AvgLowestBin.THREEDAYAVGLOWESTBIN -> HypixelStaticData.avg3dlowestBin[sbId] + AvgLowestBin.SEVENDAYAVGLOWESTBIN -> HypixelStaticData.avg7dlowestBin[sbId] + AvgLowestBin.OFF -> null + } if (bazaarData != null) { it.lines.add(Text.literal("")) + it.lines.add(multiplierText) it.lines.add( formatPrice( tr("firmament.tooltip.bazaar.sell-order", "Bazaar Sell Order"), - bazaarData.quickStatus.sellPrice + bazaarData.quickStatus.sellPrice * multiplier ) ) it.lines.add( formatPrice( tr("firmament.tooltip.bazaar.buy-order", "Bazaar Buy Order"), - bazaarData.quickStatus.buyPrice + bazaarData.quickStatus.buyPrice * multiplier ) ) } else if (lowestBin != null) { it.lines.add(Text.literal("")) + it.lines.add(multiplierText) it.lines.add( formatPrice( tr("firmament.tooltip.ah.lowestbin", "Lowest BIN"), - lowestBin + lowestBin * multiplier ) ) + if (avgBinValue != null) { + it.lines.add( + formatPrice( + tr("firmament.tooltip.ah.avg-lowestbin", "AVG Lowest BIN"), + avgBinValue * multiplier + ) + ) + } } } } diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt index a46bd76..39feecf 100644 --- a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt +++ b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.features.inventory.buttons import com.mojang.brigadier.StringReader @@ -18,69 +16,86 @@ import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.util.MC import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.collections.memoize +import moe.nea.firmament.util.mc.arbitraryUUID +import moe.nea.firmament.util.mc.createSkullItem import moe.nea.firmament.util.render.drawGuiTexture @Serializable data class InventoryButton( - var x: Int, - var y: Int, - var anchorRight: Boolean, - var anchorBottom: Boolean, - var icon: String? = "", - var command: String? = "", + var x: Int, + var y: Int, + var anchorRight: Boolean, + var anchorBottom: Boolean, + var icon: String? = "", + var command: String? = "", ) { - companion object { - val itemStackParser by lazy { - ItemStackArgumentType.itemStack(CommandRegistryAccess.of(MC.defaultRegistries, - FeatureFlags.VANILLA_FEATURES)) - } - val dimensions = Dimension(18, 18) - val getItemForName = ::getItemForName0.memoize(1024) - fun getItemForName0(icon: String): ItemStack { - val repoItem = RepoManager.getNEUItem(SkyblockId(icon)) - var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon)) - if (repoItem == null) { - val giveSyntaxItem = if (icon.startsWith("/give") || icon.startsWith("give")) - icon.split(" ", limit = 3).getOrNull(2) ?: icon - else icon - val componentItem = - runCatching { - itemStackParser.parse(StringReader(giveSyntaxItem)).createStack(1, false) - }.getOrNull() - if (componentItem != null) - itemStack = componentItem - } - return itemStack - } - } + companion object { + val itemStackParser by lazy { + ItemStackArgumentType.itemStack( + CommandRegistryAccess.of( + MC.defaultRegistries, + FeatureFlags.VANILLA_FEATURES + ) + ) + } + val dimensions = Dimension(18, 18) + val getItemForName = ::getItemForName0.memoize(1024) + fun getItemForName0(icon: String): ItemStack { + val repoItem = RepoManager.getNEUItem(SkyblockId(icon)) + var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon)) + if (repoItem == null) { + when { + icon.startsWith("skull:") -> { + itemStack = createSkullItem( + arbitraryUUID, + "https://textures.minecraft.net/texture/${icon.substring("skull:".length)}" + ) + } + + else -> { + val giveSyntaxItem = if (icon.startsWith("/give") || icon.startsWith("give")) + icon.split(" ", limit = 3).getOrNull(2) ?: icon + else icon + val componentItem = + runCatching { + itemStackParser.parse(StringReader(giveSyntaxItem)).createStack(1, false) + }.getOrNull() + if (componentItem != null) + itemStack = componentItem + } + } + } + return itemStack + } + } - fun render(context: DrawContext) { - context.drawGuiTexture( - 0, - 0, - 0, - dimensions.width, - dimensions.height, - Identifier.of("firmament:inventory_button_background") - ) - context.drawItem(getItem(), 1, 1) - } + fun render(context: DrawContext) { + context.drawGuiTexture( + 0, + 0, + 0, + dimensions.width, + dimensions.height, + Identifier.of("firmament:inventory_button_background") + ) + context.drawItem(getItem(), 1, 1) + } - fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank() + fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank() - fun getPosition(guiRect: Rectangle): Point { - return Point( - (if (anchorRight) guiRect.maxX else guiRect.minX) + x, - (if (anchorBottom) guiRect.maxY else guiRect.minY) + y, - ) - } + fun getPosition(guiRect: Rectangle): Point { + return Point( + (if (anchorRight) guiRect.maxX else guiRect.minX) + x, + (if (anchorBottom) guiRect.maxY else guiRect.minY) + y, + ) + } - fun getBounds(guiRect: Rectangle): Rectangle { - return Rectangle(getPosition(guiRect), dimensions) - } + fun getBounds(guiRect: Rectangle): Rectangle { + return Rectangle(getPosition(guiRect), dimensions) + } - fun getItem(): ItemStack { - return getItemForName(icon ?: "") - } + fun getItem(): ItemStack { + return getItemForName(icon ?: "") + } } diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt index c4ea519..74a986a 100644 --- a/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt @@ -1,7 +1,9 @@ package moe.nea.firmament.features.inventory.buttons import io.github.notenoughupdates.moulconfig.common.IItemStack +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent import io.github.notenoughupdates.moulconfig.platform.ModernItemStack +import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext import io.github.notenoughupdates.moulconfig.xml.Bind import me.shedaniel.math.Point import me.shedaniel.math.Rectangle @@ -57,7 +59,10 @@ class InventoryButtonEditor( } override fun resize(client: MinecraftClient, width: Int, height: Int) { - lastGuiRect.move(MC.window.scaledWidth / 2 - lastGuiRect.width / 2, MC.window.scaledHeight / 2 - lastGuiRect.height / 2) + lastGuiRect.move( + MC.window.scaledWidth / 2 - lastGuiRect.width / 2, + MC.window.scaledHeight / 2 - lastGuiRect.height / 2 + ) super.resize(client, width, height) } @@ -89,14 +94,20 @@ class InventoryButtonEditor( val movedButtons = mutableListOf<InventoryButton>() for (button in buttons) { if ((!button.anchorBottom && !button.anchorRight && button.x > 0 && button.y > 0)) { - MC.sendChat(tr("firmament.inventory-buttons.button-moved", - "One of your imported buttons intersects with the inventory and has been moved to the top left.")) - movedButtons.add(button.copy( - x = 0, - y = -InventoryButton.dimensions.width, - anchorRight = false, - anchorBottom = false - )) + MC.sendChat( + tr( + "firmament.inventory-buttons.button-moved", + "One of your imported buttons intersects with the inventory and has been moved to the top left." + ) + ) + movedButtons.add( + button.copy( + x = 0, + y = -InventoryButton.dimensions.width, + anchorRight = false, + anchorBottom = false + ) + ) } else { newButtons.add(button) } @@ -105,9 +116,11 @@ class InventoryButtonEditor( val zeroRect = Rectangle(0, 0, 1, 1) for (movedButton in movedButtons) { fun getPosition(button: InventoryButton, index: Int) = - button.copy(x = (index % 10) * InventoryButton.dimensions.width, - y = (index / 10) * -InventoryButton.dimensions.height, - anchorRight = false, anchorBottom = false) + button.copy( + x = (index % 10) * InventoryButton.dimensions.width, + y = (index / 10) * -InventoryButton.dimensions.height, + anchorRight = false, anchorBottom = false + ) while (true) { val newPos = getPosition(movedButton, i++) val newBounds = newPos.getBounds(zeroRect) @@ -131,7 +144,12 @@ class InventoryButtonEditor( super.render(context, mouseX, mouseY, delta) context.matrices.push() context.matrices.translate(0f, 0f, -10f) - context.fill(lastGuiRect.minX, lastGuiRect.minY, lastGuiRect.maxX, lastGuiRect.maxY, -1) + PanelComponent.DefaultBackgroundRenderer.VANILLA + .render( + ModernRenderContext(context), + lastGuiRect.minX, lastGuiRect.minY, + lastGuiRect.width, lastGuiRect.height, + ) context.matrices.pop() for (button in buttons) { val buttonPosition = button.getBounds(lastGuiRect) @@ -193,14 +211,6 @@ class InventoryButtonEditor( ) fun getCoordsForMouse(mx: Int, my: Int): AnchoredCoords? { - if (lastGuiRect.contains(mx, my) || lastGuiRect.contains( - Point( - mx + InventoryButton.dimensions.width, - my + InventoryButton.dimensions.height, - ) - ) - ) return null - val anchorRight = mx > lastGuiRect.maxX val anchorBottom = my > lastGuiRect.maxY var offsetX = mx - if (anchorRight) lastGuiRect.maxX else lastGuiRect.minX @@ -209,7 +219,10 @@ class InventoryButtonEditor( offsetX = MathHelper.floor(offsetX / 20F) * 20 offsetY = MathHelper.floor(offsetY / 20F) * 20 } - return AnchoredCoords(anchorRight, anchorBottom, offsetX, offsetY) + val rect = InventoryButton(offsetX, offsetY, anchorRight, anchorBottom).getBounds(lastGuiRect) + if (rect.intersects(lastGuiRect)) return null + val anchoredCoords = AnchoredCoords(anchorRight, anchorBottom, offsetX, offsetY) + return anchoredCoords } override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt index 92640c8..361e8d0 100644 --- a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt @@ -5,16 +5,22 @@ package moe.nea.firmament.features.inventory.buttons import me.shedaniel.math.Rectangle import kotlinx.serialization.Serializable import kotlinx.serialization.serializer +import kotlin.time.Duration.Companion.seconds +import net.minecraft.client.MinecraftClient +import net.minecraft.text.Text import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.HandledScreenClickEvent import moe.nea.firmament.events.HandledScreenForegroundEvent import moe.nea.firmament.events.HandledScreenPushREIEvent import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.FirmHoverComponent import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.util.MC import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.data.DataHolder import moe.nea.firmament.util.accessors.getRectangle +import moe.nea.firmament.util.gold object InventoryButtons : FirmamentFeature { override val identifier: String @@ -24,6 +30,7 @@ object InventoryButtons : FirmamentFeature { val _openEditor by button("open-editor") { openEditor() } + val hoverText by toggle("hover-text") { true } } object DConfig : DataHolder<Data>(serializer(), identifier, ::Data) @@ -60,16 +67,36 @@ object InventoryButtons : FirmamentFeature { } } + var lastHoveredComponent: InventoryButton? = null + var lastMouseMove = TimeMark.farPast() + @Subscribe fun onRenderForeground(it: HandledScreenForegroundEvent) { val bounds = it.screen.getRectangle() + + var hoveredComponent: InventoryButton? = null for (button in getValidButtons()) { val buttonBounds = button.getBounds(bounds) it.context.matrices.push() it.context.matrices.translate(buttonBounds.minX.toFloat(), buttonBounds.minY.toFloat(), 0F) button.render(it.context) it.context.matrices.pop() + + if (buttonBounds.contains(it.mouseX, it.mouseY) && TConfig.hoverText && hoveredComponent == null) { + hoveredComponent = button + if (lastMouseMove.passedTime() > 0.6.seconds && lastHoveredComponent === button) { + it.context.drawTooltip( + MC.font, + listOf(Text.literal(button.command).gold()), + buttonBounds.minX - 15, + buttonBounds.maxY + 20, + ) + } + } } + if (hoveredComponent !== lastHoveredComponent) + lastMouseMove = TimeMark.now() + lastHoveredComponent = hoveredComponent lastRectangle = bounds } diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt index 2101915..ec62aa6 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt @@ -27,6 +27,7 @@ object StorageOverlay : FirmamentFeature { object TConfig : ManagedConfig(identifier, Category.INVENTORY) { val alwaysReplace by toggle("always-replace") { true } + val outlineActiveStoragePage by toggle("outline-active-page") { false } val columns by integer("rows", 1, 10) { 3 } val height by integer("height", 80, 3000) { 3 * 18 * 6 } val scrollSpeed by integer("scroll-speed", 1, 50) { 10 } @@ -101,7 +102,8 @@ object StorageOverlay : FirmamentFeature { screen.customGui = StorageOverlayCustom( currentHandler ?: return, screen, - storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return)) + storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return) + ) } fun rememberContent(handler: StorageBackingHandle?) { diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt index 22f4dab..f4dc458 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -47,7 +47,8 @@ class StorageOverlayScreen : Screen(Text.literal("")) { val PLAYER_Y_INSET = 3 val SLOT_SIZE = 18 val PADDING = 10 - val PAGE_WIDTH = SLOT_SIZE * 9 + val PAGE_SLOTS_WIDTH = SLOT_SIZE * 9 + val PAGE_WIDTH = PAGE_SLOTS_WIDTH + 4 val HOTBAR_X = 12 val HOTBAR_Y = 67 val MAIN_INVENTORY_Y = 9 @@ -68,7 +69,8 @@ class StorageOverlayScreen : Screen(Text.literal("")) { val x = width / 2 - overviewWidth / 2 val overviewHeight = minOf( height - PLAYER_HEIGHT - minOf(80, height / 10), - StorageOverlay.TConfig.height) + StorageOverlay.TConfig.height + ) val innerScrollPanelHeight = overviewHeight - PADDING * 2 val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2 val playerX = width / 2 - PLAYER_WIDTH / 2 @@ -100,6 +102,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { coerceScroll(StorageOverlay.adjustScrollSpeed(verticalAmount).toFloat()) return true } + fun coerceScroll(offset: Float) { scroll = (scroll + offset) .coerceAtMost(getMaxScroll()) @@ -159,11 +162,16 @@ class StorageOverlayScreen : Screen(Text.literal("")) { val guiContext = GuiContext(EmptyComponent()) private val knobStub = EmptyComponent() - val editButton = FirmButtonComponent(TextComponent(tr("firmament.storage-overlay.edit-pages", "Edit Pages").string), action = ::editPages) + val editButton = FirmButtonComponent( + TextComponent(tr("firmament.storage-overlay.edit-pages", "Edit Pages").string), + action = ::editPages + ) val searchText = Property.of("") // TODO: sync with REI - val searchField = TextFieldComponent(searchText, 100, GetSetter.constant(true), - tr("firmament.storage-overlay.search.suggestion", "Search...").string, - IMinecraft.instance.defaultFontRenderer) + val searchField = TextFieldComponent( + searchText, 100, GetSetter.constant(true), + tr("firmament.storage-overlay.search.suggestion", "Search...").string, + IMinecraft.instance.defaultFontRenderer + ) val controlComponent = PanelComponent( ColumnComponent( searchField, @@ -186,25 +194,31 @@ class StorageOverlayScreen : Screen(Text.literal("")) { controllerBackground, measurements.controlX, measurements.controlY, - CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT) + CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT + ) context.drawMCComponentInPlace( controlComponent, measurements.controlX, measurements.controlY, CONTROL_WIDTH, CONTROL_HEIGHT, - mouseX, mouseY) + mouseX, mouseY + ) } fun drawBackgrounds(context: DrawContext) { - context.drawGuiTexture(upperBackgroundSprite, - measurements.x, - measurements.y, - measurements.overviewWidth, - measurements.overviewHeight) - context.drawGuiTexture(playerInventorySprite, - measurements.playerX, - measurements.playerY, - PLAYER_WIDTH, - PLAYER_HEIGHT) + context.drawGuiTexture( + upperBackgroundSprite, + measurements.x, + measurements.y, + measurements.overviewWidth, + measurements.overviewHeight + ) + context.drawGuiTexture( + playerInventorySprite, + measurements.playerX, + measurements.playerY, + PLAYER_WIDTH, + PLAYER_HEIGHT + ) } fun getPlayerInventorySlotPosition(int: Int): Pair<Int, Int> { @@ -227,17 +241,21 @@ class StorageOverlayScreen : Screen(Text.literal("")) { } fun getScrollBarRect(): Rectangle { - return Rectangle(measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING, - measurements.y + PADDING, - SCROLL_BAR_WIDTH, - measurements.innerScrollPanelHeight) + return Rectangle( + measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING, + measurements.y + PADDING, + SCROLL_BAR_WIDTH, + measurements.innerScrollPanelHeight + ) } fun getScrollPanelInner(): Rectangle { - return Rectangle(measurements.x + PADDING, - measurements.y + PADDING, - measurements.innerScrollPanelWidth, - measurements.innerScrollPanelHeight) + return Rectangle( + measurements.x + PADDING, + measurements.y + PADDING, + measurements.innerScrollPanelWidth, + measurements.innerScrollPanelHeight + ) } fun createScissors(context: DrawContext) { @@ -257,12 +275,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) { createScissors(context) val data = StorageOverlay.Data.data ?: StorageData() layoutedForEach(data) { rect, page, inventory -> - drawPage(context, - rect.x, - rect.y, - page, inventory, - if (excluding == page) slots else null, - slotOffset + drawPage( + context, + rect.x, + rect.y, + page, inventory, + if (excluding == page) slots else null, + slotOffset ) } context.disableScissor() @@ -282,11 +301,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) { knobGrabbed = false return true } - if (clickMCComponentInPlace(controlComponent, - measurements.controlX, measurements.controlY, - CONTROL_WIDTH, CONTROL_HEIGHT, - mouseX.toInt(), mouseY.toInt(), - MouseEvent.Click(button, false)) + if (clickMCComponentInPlace( + controlComponent, + measurements.controlX, measurements.controlY, + CONTROL_WIDTH, CONTROL_HEIGHT, + mouseX.toInt(), mouseY.toInt(), + MouseEvent.Click(button, false) + ) ) return true return super.mouseReleased(mouseX, mouseY, button) } @@ -322,11 +343,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) { knobGrabbed = true return true } - if (clickMCComponentInPlace(controlComponent, - measurements.controlX, measurements.controlY, - CONTROL_WIDTH, CONTROL_HEIGHT, - mouseX.toInt(), mouseY.toInt(), - MouseEvent.Click(button, true)) + if (clickMCComponentInPlace( + controlComponent, + measurements.controlX, measurements.controlY, + CONTROL_WIDTH, CONTROL_HEIGHT, + mouseX.toInt(), mouseY.toInt(), + MouseEvent.Click(button, true) + ) ) return true return false } @@ -420,7 +443,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { val filter = getFilteredPages() for ((page, inventory) in data.storageInventories.entries) { if (page !in filter) continue - val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } + val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 6 + textRenderer.fontHeight } ?: 18 maxHeight = maxOf(maxHeight, currentHeight) val rect = Rectangle( @@ -452,22 +475,35 @@ class StorageOverlayScreen : Screen(Text.literal("")) { val inv = inventory.inventory if (inv == null) { context.drawGuiTexture(upperBackgroundSprite, x, y, PAGE_WIDTH, 18) - context.drawText(textRenderer, - Text.literal("TODO: open this page"), - x + 4, - y + 4, - -1, - true) + context.drawText( + textRenderer, + Text.literal("TODO: open this page"), + x + 4, + y + 4, + -1, + true + ) return 18 } assertTrueOr(slots == null || slots.size == inv.stacks.size) { return 0 } val name = page.defaultName() - context.drawText(textRenderer, Text.literal(name), x + 4, y + 2, - if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true) - context.drawGuiTexture(slotRowSprite, x, y + 4 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE) + val pageHeight = inv.rows * SLOT_SIZE + 8 + textRenderer.fontHeight + if (slots != null && StorageOverlay.TConfig.outlineActiveStoragePage) + context.drawBorder(x, y + 3 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE + 4, 0xFFFF00FF.toInt()) + context.drawText( + textRenderer, Text.literal(name), x + 6, y + 3, + if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true + ) + context.drawGuiTexture( + slotRowSprite, + x + 2, + y + 5 + textRenderer.fontHeight, + PAGE_SLOTS_WIDTH, + inv.rows * SLOT_SIZE + ) inv.stacks.forEachIndexed { index, stack -> - val slotX = (index % 9) * SLOT_SIZE + x + 1 - val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1 + val slotX = (index % 9) * SLOT_SIZE + x + 3 + val slotY = (index / 9) * SLOT_SIZE + y + 5 + textRenderer.fontHeight + 1 val fakeSlot = FakeSlot(stack, slotX, slotY) if (slots == null) { SlotRenderEvents.Before.publish(SlotRenderEvents.Before(context, fakeSlot)) @@ -480,22 +516,29 @@ class StorageOverlayScreen : Screen(Text.literal("")) { slot.y = slotY - slotOffset.y } } - return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight + return pageHeight + 6 } fun getBounds(): List<Rectangle> { return listOf( - Rectangle(measurements.x, - measurements.y, - measurements.overviewWidth, - measurements.overviewHeight), - Rectangle(measurements.playerX, - measurements.playerY, - PLAYER_WIDTH, - PLAYER_HEIGHT), - Rectangle(measurements.controlX, - measurements.controlY, - CONTROL_WIDTH, - CONTROL_HEIGHT)) + Rectangle( + measurements.x, + measurements.y, + measurements.overviewWidth, + measurements.overviewHeight + ), + Rectangle( + measurements.playerX, + measurements.playerY, + PLAYER_WIDTH, + PLAYER_HEIGHT + ), + Rectangle( + measurements.controlX, + measurements.controlY, + CONTROL_WIDTH, + CONTROL_HEIGHT + ) + ) } } diff --git a/src/main/kotlin/features/macros/ComboProcessor.kt b/src/main/kotlin/features/macros/ComboProcessor.kt index 55b3f6e..5c5ac0e 100644 --- a/src/main/kotlin/features/macros/ComboProcessor.kt +++ b/src/main/kotlin/features/macros/ComboProcessor.kt @@ -10,6 +10,7 @@ import moe.nea.firmament.events.WorldKeyboardEvent import moe.nea.firmament.keybindings.SavedKeyBinding import moe.nea.firmament.util.MC import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.tr object ComboProcessor { @@ -22,18 +23,13 @@ object ComboProcessor { var isInputting = false var lastInput = TimeMark.farPast() val breadCrumbs = mutableListOf<SavedKeyBinding>() - // TODO: keep breadcrumbs - init { val f = SavedKeyBinding(InputUtil.GLFW_KEY_F) val one = SavedKeyBinding(InputUtil.GLFW_KEY_1) val two = SavedKeyBinding(InputUtil.GLFW_KEY_2) setActions( - listOf( - ComboKeyAction(CommandAction("wardrobe"), listOf(f, one)), - ComboKeyAction(CommandAction("equipment"), listOf(f, two)), - ) + MacroData.DConfig.data.comboActions ) } @@ -68,10 +64,24 @@ object ComboProcessor { 0F ) val breadCrumbText = breadCrumbs.joinToString(" > ") - event.context.drawText(MC.font, breadCrumbText, 0, 0, -1, true) + event.context.drawText( + MC.font, + tr("firmament.combo.active", "Current Combo: ").append(breadCrumbText), + 0, + 0, + -1, + true + ) event.context.matrices.translate(0F, MC.font.fontHeight + 2F, 0F) for ((key, value) in activeTrie.nodes) { - event.context.drawText(MC.font, Text.literal("$breadCrumbText > $key: ").append(value.label), 0, 0, -1, true) + event.context.drawText( + MC.font, + Text.literal("$breadCrumbText > $key: ").append(value.label), + 0, + 0, + -1, + true + ) event.context.matrices.translate(0F, MC.font.fontHeight + 1F, 0F) } event.context.matrices.pop() diff --git a/src/main/kotlin/features/macros/HotkeyAction.kt b/src/main/kotlin/features/macros/HotkeyAction.kt index 51c1baa..011f797 100644 --- a/src/main/kotlin/features/macros/HotkeyAction.kt +++ b/src/main/kotlin/features/macros/HotkeyAction.kt @@ -1,14 +1,19 @@ package moe.nea.firmament.features.macros +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import net.minecraft.text.Text import moe.nea.firmament.util.MC -interface HotkeyAction { +@Serializable +sealed interface HotkeyAction { // TODO: execute val label: Text fun execute() } +@Serializable +@SerialName("command") data class CommandAction(val command: String) : HotkeyAction { override val label: Text get() = Text.literal("/$command") diff --git a/src/main/kotlin/features/macros/KeyComboTrie.kt b/src/main/kotlin/features/macros/KeyComboTrie.kt index 5c14bcd..57ff289 100644 --- a/src/main/kotlin/features/macros/KeyComboTrie.kt +++ b/src/main/kotlin/features/macros/KeyComboTrie.kt @@ -1,7 +1,9 @@ package moe.nea.firmament.features.macros +import kotlinx.serialization.Serializable import net.minecraft.text.Text import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.util.ErrorUtil sealed interface KeyComboTrie { val label: Text @@ -13,19 +15,27 @@ sealed interface KeyComboTrie { val root = Branch(mutableMapOf()) for (combo in combos) { var p = root - require(combo.keys.isNotEmpty()) + if (combo.keys.isEmpty()) { + ErrorUtil.softUserError("Key Combo for ${combo.action.label.string} is empty") + continue + } for ((index, key) in combo.keys.withIndex()) { val m = (p.nodes as MutableMap) if (index == combo.keys.lastIndex) { - if (key in m) - error("Overlapping actions found for ${combo.keys} (another action ${m[key]} already exists).") + if (key in m) { + ErrorUtil.softUserError("Overlapping actions found for ${combo.keys.joinToString(" > ")} (another action ${m[key]} already exists).") + break + } m[key] = Leaf(combo.action) } else { val c = m.getOrPut(key) { Branch(mutableMapOf()) } - if (c !is Branch) - error("Overlapping actions found for ${combo.keys} (final node exists at index $index) through another action already") - p = c + if (c !is Branch) { + ErrorUtil.softUserError("Overlapping actions found for ${combo.keys} (final node exists at index $index) through another action already") + break + } else { + p = c + } } } } @@ -35,6 +45,7 @@ sealed interface KeyComboTrie { } +@Serializable data class ComboKeyAction( val action: HotkeyAction, val keys: List<SavedKeyBinding>, diff --git a/src/main/kotlin/features/macros/MacroData.kt b/src/main/kotlin/features/macros/MacroData.kt new file mode 100644 index 0000000..78a5948 --- /dev/null +++ b/src/main/kotlin/features/macros/MacroData.kt @@ -0,0 +1,11 @@ +package moe.nea.firmament.features.macros + +import kotlinx.serialization.Serializable +import moe.nea.firmament.util.data.DataHolder + +@Serializable +data class MacroData( + var comboActions: List<ComboKeyAction> = listOf(), +){ + object DConfig : DataHolder<MacroData>(kotlinx.serialization.serializer(), "macros", ::MacroData) +} diff --git a/src/main/kotlin/features/macros/MacroUI.kt b/src/main/kotlin/features/macros/MacroUI.kt new file mode 100644 index 0000000..17fdd0a --- /dev/null +++ b/src/main/kotlin/features/macros/MacroUI.kt @@ -0,0 +1,161 @@ +package moe.nea.firmament.features.macros + +import io.github.notenoughupdates.moulconfig.gui.CloseEventListener +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.xml.Bind +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.gui.config.AllConfigsGui.toObservableList +import moe.nea.firmament.gui.config.KeyBindingStateManager +import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil + +class MacroUI { + + + companion object { + @Subscribe + fun onCommands(event: CommandEvent.SubCommand) { + // TODO: add button in config + event.subcommand("macros") { + thenExecute { + ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen("config/macros/index", MacroUI(), null)) + } + } + } + + } + + @field:Bind("combos") + val combos = Combos() + + class Combos { + @field:Bind("actions") + val actions: ObservableList<ActionEditor> = ObservableList( + MacroData.DConfig.data.comboActions.mapTo(mutableListOf()) { + ActionEditor(it, this) + } + ) + + var dontSave = false + + @Bind + fun beforeClose(): CloseEventListener.CloseAction { + if (!dontSave) + save() + return CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE + } + + @Bind + fun addCommand() { + actions.add( + ActionEditor( + ComboKeyAction( + CommandAction("ac Hello from a Firmament Hotkey"), + listOf() + ), + this + ) + ) + } + + @Bind + fun discard() { + dontSave = true + MC.screen?.close() + } + + @Bind + fun saveAndClose() { + save() + MC.screen?.close() + } + + @Bind + fun save() { + MacroData.DConfig.data.comboActions = actions.map { it.asSaveable() } + MacroData.DConfig.markDirty() + ComboProcessor.setActions(MacroData.DConfig.data.comboActions) // TODO: automatically reload those from the config on startup + } + } + + class KeyBindingEditor(var binding: SavedKeyBinding, val parent: ActionEditor) { + val sm = KeyBindingStateManager( + { binding }, + { binding = it }, + ::blur, + ::requestFocus + ) + + @field:Bind + val button = sm.createButton() + + init { + sm.updateLabel() + } + + fun blur() { + button.blur() + } + + @Bind + fun delete() { + parent.combo.removeIf { it === this } + parent.combo.update() + } + + fun requestFocus() { + button.requestFocus() + } + } + + class ActionEditor(val action: ComboKeyAction, val parent: MacroUI.Combos) { + fun asSaveable(): ComboKeyAction { + return ComboKeyAction( + CommandAction(command), + combo.map { it.binding } + ) + } + + @field:Bind("command") + var command: String = (action.action as CommandAction).command + + @field:Bind("combo") + val combo = action.keys.map { KeyBindingEditor(it, this) }.toObservableList() + + @Bind + fun formattedCombo() = + combo.joinToString(" > ") { it.binding.toString() } + + @Bind + fun addStep() { + combo.add(KeyBindingEditor(SavedKeyBinding.unbound(), this)) + } + + @Bind + fun back() { + MC.screen?.close() + } + + @Bind + fun delete() { + parent.actions.removeIf { it === this } + parent.actions.update() + } + @Bind + fun edit() { + MC.screen = MoulConfigUtils.loadScreen("config/macros/editor", this, MC.screen) + } + } +} + +private fun <T> ObservableList<T>.setAll(ts: Collection<T>) { + val observer = this.observer + this.clear() + this.addAll(ts) + this.observer = observer + this.update() +} diff --git a/src/main/kotlin/gui/config/KeyBindingHandler.kt b/src/main/kotlin/gui/config/KeyBindingHandler.kt index d7d0b47..14a4b32 100644 --- a/src/main/kotlin/gui/config/KeyBindingHandler.kt +++ b/src/main/kotlin/gui/config/KeyBindingHandler.kt @@ -40,34 +40,7 @@ class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : { button.blur() }, { button.requestFocus() } ) - button = object : FirmButtonComponent( - TextComponent( - IMinecraft.instance.defaultFontRenderer, - { sm.label.string }, - 130, - TextComponent.TextAlignment.LEFT, - false, - false - ), action = { - sm.onClick() - }) { - override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { - if (event is KeyboardEvent.KeyPressed) { - return sm.keyboardEvent(event.keycode, event.pressed) - } - return super.keyboardEvent(event, context) - } - - override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> { - if (sm.editing) return activeBg - return super.getBackground(context) - } - - - override fun onLostFocus() { - sm.onLostFocus() - } - } + button = sm.createButton() sm.updateLabel() return button } diff --git a/src/main/kotlin/gui/config/KeyBindingStateManager.kt b/src/main/kotlin/gui/config/KeyBindingStateManager.kt index cc8178d..1528ac4 100644 --- a/src/main/kotlin/gui/config/KeyBindingStateManager.kt +++ b/src/main/kotlin/gui/config/KeyBindingStateManager.kt @@ -1,8 +1,15 @@ package moe.nea.firmament.gui.config +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import org.lwjgl.glfw.GLFW import net.minecraft.text.Text import net.minecraft.util.Formatting +import moe.nea.firmament.gui.FirmButtonComponent import moe.nea.firmament.keybindings.SavedKeyBinding class KeyBindingStateManager( @@ -51,9 +58,11 @@ class KeyBindingStateManager( ) { lastPressed = ch } else { - setValue(SavedKeyBinding( - ch, modifiers - )) + setValue( + SavedKeyBinding( + ch, modifiers + ) + ) editing = false blur() lastPressed = 0 @@ -104,5 +113,34 @@ class KeyBindingStateManager( label = stroke } + fun createButton(): FirmButtonComponent { + return object : FirmButtonComponent( + TextComponent( + IMinecraft.instance.defaultFontRenderer, + { this@KeyBindingStateManager.label.string }, + 130, + TextComponent.TextAlignment.LEFT, + false, + false + ), action = { + this@KeyBindingStateManager.onClick() + }) { + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + if (event is KeyboardEvent.KeyPressed) { + return this@KeyBindingStateManager.keyboardEvent(event.keycode, event.pressed) + } + return super.keyboardEvent(event, context) + } + + override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> { + if (this@KeyBindingStateManager.editing) return activeBg + return super.getBackground(context) + } + + override fun onLostFocus() { + this@KeyBindingStateManager.onLostFocus() + } + } + } } diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt index 7ddda9e..5a41f43 100644 --- a/src/main/kotlin/gui/config/ManagedConfig.kt +++ b/src/main/kotlin/gui/config/ManagedConfig.kt @@ -39,6 +39,7 @@ abstract class ManagedConfig( CHAT, INVENTORY, MINING, + GARDEN, EVENTS, INTEGRATIONS, META, diff --git a/src/main/kotlin/gui/entity/ModifyEquipment.kt b/src/main/kotlin/gui/entity/ModifyEquipment.kt index 712f5ca..3ce63f5 100644 --- a/src/main/kotlin/gui/entity/ModifyEquipment.kt +++ b/src/main/kotlin/gui/entity/ModifyEquipment.kt @@ -11,7 +11,7 @@ import net.minecraft.item.Items 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 +import moe.nea.firmament.util.mc.arbitraryUUID object ModifyEquipment : EntityModifier { val names = mapOf( @@ -36,7 +36,7 @@ object ModifyEquipment : EntityModifier { 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) } + "SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(arbitraryUUID, data) } "LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data) "LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data) "LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data) diff --git a/src/main/kotlin/keybindings/SavedKeyBinding.kt b/src/main/kotlin/keybindings/SavedKeyBinding.kt index 5429844..3ae0b89 100644 --- a/src/main/kotlin/keybindings/SavedKeyBinding.kt +++ b/src/main/kotlin/keybindings/SavedKeyBinding.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.keybindings import org.lwjgl.glfw.GLFW @@ -11,100 +9,106 @@ import moe.nea.firmament.util.MC @Serializable data class SavedKeyBinding( - val keyCode: Int, - val shift: Boolean = false, - val ctrl: Boolean = false, - val alt: Boolean = false, + val keyCode: Int, + val shift: Boolean = false, + val ctrl: Boolean = false, + val alt: Boolean = false, ) : IKeyBinding { - val isBound: Boolean get() = keyCode != GLFW.GLFW_KEY_UNKNOWN - - constructor(keyCode: Int, mods: Triple<Boolean, Boolean, Boolean>) : this( - keyCode, - mods.first && keyCode != GLFW.GLFW_KEY_LEFT_SHIFT && keyCode != GLFW.GLFW_KEY_RIGHT_SHIFT, - mods.second && keyCode != GLFW.GLFW_KEY_LEFT_CONTROL && keyCode != GLFW.GLFW_KEY_RIGHT_CONTROL, - mods.third && keyCode != GLFW.GLFW_KEY_LEFT_ALT && keyCode != GLFW.GLFW_KEY_RIGHT_ALT, - ) - - constructor(keyCode: Int, mods: Int) : this(keyCode, getMods(mods)) - - companion object { - fun getMods(modifiers: Int): Triple<Boolean, Boolean, Boolean> { - return Triple( - modifiers and GLFW.GLFW_MOD_SHIFT != 0, - modifiers and GLFW.GLFW_MOD_CONTROL != 0, - modifiers and GLFW.GLFW_MOD_ALT != 0, - ) - } - - fun getModInt(): Int { - val h = MC.window.handle - val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { - InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) - } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) - val shift = isShiftDown() - val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) - var mods = 0 - if (ctrl) mods = mods or GLFW.GLFW_MOD_CONTROL - if (shift) mods = mods or GLFW.GLFW_MOD_SHIFT - if (alt) mods = mods or GLFW.GLFW_MOD_ALT - return mods - } - - private val h get() = MC.window.handle - fun isShiftDown() = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SHIFT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SHIFT) - - } - - fun isPressed(atLeast: Boolean = false): Boolean { - if (!isBound) return false - val h = MC.window.handle - if (!InputUtil.isKeyPressed(h, keyCode)) return false - - val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { - InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) - } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) - val shift = isShiftDown() - val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) - if (atLeast) - return (ctrl >= this.ctrl) && - (alt >= this.alt) && - (shift >= this.shift) - - return (ctrl == this.ctrl) && - (alt == this.alt) && - (shift == this.shift) - } - - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false - return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) - } + val isBound: Boolean get() = keyCode != GLFW.GLFW_KEY_UNKNOWN + + constructor(keyCode: Int, mods: Triple<Boolean, Boolean, Boolean>) : this( + keyCode, + mods.first && keyCode != GLFW.GLFW_KEY_LEFT_SHIFT && keyCode != GLFW.GLFW_KEY_RIGHT_SHIFT, + mods.second && keyCode != GLFW.GLFW_KEY_LEFT_CONTROL && keyCode != GLFW.GLFW_KEY_RIGHT_CONTROL, + mods.third && keyCode != GLFW.GLFW_KEY_LEFT_ALT && keyCode != GLFW.GLFW_KEY_RIGHT_ALT, + ) + + constructor(keyCode: Int, mods: Int) : this(keyCode, getMods(mods)) + + companion object { + fun getMods(modifiers: Int): Triple<Boolean, Boolean, Boolean> { + return Triple( + modifiers and GLFW.GLFW_MOD_SHIFT != 0, + modifiers and GLFW.GLFW_MOD_CONTROL != 0, + modifiers and GLFW.GLFW_MOD_ALT != 0, + ) + } + + fun getModInt(): Int { + val h = MC.window.handle + val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { + InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) + } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) + val shift = isShiftDown() + val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) + var mods = 0 + if (ctrl) mods = mods or GLFW.GLFW_MOD_CONTROL + if (shift) mods = mods or GLFW.GLFW_MOD_SHIFT + if (alt) mods = mods or GLFW.GLFW_MOD_ALT + return mods + } + + private val h get() = MC.window.handle + fun isShiftDown() = shiftKeys.any { InputUtil.isKeyPressed(h, it) } + + fun unbound(): SavedKeyBinding = + SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) + + val controlKeys = if (MinecraftClient.IS_SYSTEM_MAC) { + listOf(GLFW.GLFW_KEY_LEFT_SUPER, GLFW.GLFW_KEY_RIGHT_SUPER) + } else { + listOf(GLFW.GLFW_KEY_LEFT_CONTROL, GLFW.GLFW_KEY_RIGHT_CONTROL) + } + val shiftKeys = listOf(GLFW.GLFW_KEY_LEFT_SHIFT, GLFW.GLFW_KEY_RIGHT_SHIFT) + + val altKeys = listOf(GLFW.GLFW_KEY_LEFT_ALT, GLFW.GLFW_KEY_RIGHT_ALT) + } + + fun isPressed(atLeast: Boolean = false): Boolean { + if (!isBound) return false + val h = MC.window.handle + if (!InputUtil.isKeyPressed(h, keyCode)) return false + + // These are modifiers, so if the searched keyCode is a modifier key, then that key does not count as the modifier + val ctrl = keyCode !in controlKeys && controlKeys.any { InputUtil.isKeyPressed(h, it) } + val shift = keyCode !in shiftKeys && isShiftDown() + val alt = keyCode !in altKeys && altKeys.any { InputUtil.isKeyPressed(h, it) } + if (atLeast) + return (ctrl >= this.ctrl) && + (alt >= this.alt) && + (shift >= this.shift) + + return (ctrl == this.ctrl) && + (alt == this.alt) && + (shift == this.shift) + } + + override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false + return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) + } override fun toString(): String { return format().string } - fun format(): Text { - val stroke = Text.literal("") - if (ctrl) { - stroke.append("CTRL + ") - } - if (alt) { - stroke.append("ALT + ") - } - if (shift) { - stroke.append("SHIFT + ") // TODO: translations? - } - - stroke.append(InputUtil.Type.KEYSYM.createFromCode(keyCode).localizedText) - return stroke - } + fun format(): Text { + val stroke = Text.literal("") + if (ctrl) { + stroke.append("CTRL + ") + } + if (alt) { + stroke.append("ALT + ") + } + if (shift) { + stroke.append("SHIFT + ") // TODO: translations? + } + + stroke.append(InputUtil.Type.KEYSYM.createFromCode(keyCode).localizedText) + return stroke + } } diff --git a/src/main/kotlin/repo/HypixelStaticData.kt b/src/main/kotlin/repo/HypixelStaticData.kt index 181aa70..3c992f5 100644 --- a/src/main/kotlin/repo/HypixelStaticData.kt +++ b/src/main/kotlin/repo/HypixelStaticData.kt @@ -3,21 +3,17 @@ package moe.nea.firmament.repo import io.ktor.client.call.body import io.ktor.client.request.get import org.apache.logging.log4j.LogManager -import org.lwjgl.glfw.GLFW import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.time.Duration.Companion.minutes import moe.nea.firmament.Firmament import moe.nea.firmament.apis.CollectionResponse import moe.nea.firmament.apis.CollectionSkillData -import moe.nea.firmament.keybindings.IKeyBinding import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.async.waitForInput object HypixelStaticData { private val logger = LogManager.getLogger("Firmament.HypixelStaticData") @@ -25,6 +21,12 @@ object HypixelStaticData { private val hypixelApiBaseUrl = "https://api.hypixel.net" var lowestBin: Map<SkyblockId, Double> = mapOf() private set + var avg1dlowestBin: Map<SkyblockId, Double> = mapOf() + private set + var avg3dlowestBin: Map<SkyblockId, Double> = mapOf() + private set + var avg7dlowestBin: Map<SkyblockId, Double> = mapOf() + private set var bazaarData: Map<SkyblockId, BazaarData> = mapOf() private set var collectionData: Map<String, CollectionSkillData> = mapOf() @@ -90,6 +92,12 @@ object HypixelStaticData { private suspend fun fetchPricesFromMoulberry() { lowestBin = Firmament.httpClient.get("$moulberryBaseUrl/lowestbin.json") .body<Map<SkyblockId, Double>>() + avg1dlowestBin = Firmament.httpClient.get("$moulberryBaseUrl/auction_averages_lbin/1day.json") + .body<Map<SkyblockId, Double>>() + avg3dlowestBin = Firmament.httpClient.get("$moulberryBaseUrl/auction_averages_lbin/3day.json") + .body<Map<SkyblockId, Double>>() + avg7dlowestBin = Firmament.httpClient.get("$moulberryBaseUrl/auction_averages_lbin/7day.json") + .body<Map<SkyblockId, Double>>() } private suspend fun fetchBazaarPrices() { diff --git a/src/main/kotlin/util/ErrorUtil.kt b/src/main/kotlin/util/ErrorUtil.kt index af36d81..2f0acd6 100644 --- a/src/main/kotlin/util/ErrorUtil.kt +++ b/src/main/kotlin/util/ErrorUtil.kt @@ -75,4 +75,9 @@ object ErrorUtil { return nullable } + fun softUserError(string: String) { + if (TestUtil.isInTest) + error(string) + MC.sendChat(tr("firmament.usererror", "Firmament encountered a user caused error: $string")) + } } diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt index d8a3ab1..e85b119 100644 --- a/src/main/kotlin/util/MC.kt +++ b/src/main/kotlin/util/MC.kt @@ -1,6 +1,7 @@ package moe.nea.firmament.util import io.github.moulberry.repo.data.Coordinate +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper import java.util.concurrent.ConcurrentLinkedQueue import kotlin.jvm.optionals.getOrNull import net.minecraft.client.MinecraftClient @@ -126,6 +127,8 @@ object MC { } private set + val currentMoulConfigContext + get() = (screen as? GuiComponentWrapper)?.context fun openUrl(uri: String) { Util.getOperatingSystem().open(uri) diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt index 5613393..6260679 100644 --- a/src/main/kotlin/util/SkyblockId.kt +++ b/src/main/kotlin/util/SkyblockId.kt @@ -6,7 +6,8 @@ import com.mojang.serialization.Codec import io.github.moulberry.repo.data.NEUIngredient import io.github.moulberry.repo.data.NEUItem import io.github.moulberry.repo.data.Rarity -import java.util.* +import java.util.Optional +import java.util.UUID import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import kotlinx.serialization.json.Json @@ -28,8 +29,8 @@ import moe.nea.firmament.util.json.DashlessUUIDSerializer /** * A SkyBlock item id, as used by the NEU repo. - * This is not exactly the format used by HyPixel, but is mostly the same. - * Usually this id splits an id used by HyPixel into more sub items. For example `PET` becomes `$PET_ID;$PET_RARITY`, + * This is not exactly the format used by Hypixel, but is mostly the same. + * Usually this id splits an id used by Hypixel into more sub items. For example `PET` becomes `$PET_ID;$PET_RARITY`, * with those values extracted from other metadata. */ @JvmInline @@ -53,7 +54,7 @@ value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> { } /** - * A bazaar stock item id, as returned by the HyPixel bazaar api endpoint. + * A bazaar stock item id, as returned by the Hypixel bazaar api endpoint. * These are not equivalent to the in-game ids, or the NEU repo ids, and in fact, do not refer to items, but instead * to bazaar stocks. The main difference from [SkyblockId]s is concerning enchanted books. There are probably more, * but for now this holds. @@ -220,14 +221,28 @@ val ItemStack.skyBlockId: SkyblockId? when { potionName != null -> SkyblockId("POTION_${potionName.uppercase()};$potionLevel") potionData != null -> SkyblockId("POTION_${potionData.uppercase()};$potionLevel") - potionType != null -> SkyblockId("POTION${potionType.uppercase()}") + potionType != null -> SkyblockId("POTION_${potionType.uppercase()}") else -> SkyblockId("WATER_BOTTLE") } } - // TODO: PARTY_HAT_CRAB{,_ANIMATED,_SLOTH} + "PARTY_HAT_SLOTH", "PARTY_HAT_CRAB", "PARTY_HAT_CRAB_ANIMATED" -> { + val partyHatEmoji = extraAttributes.getString("party_hat_emoji").getOrNull() + val partyHatYear = extraAttributes.getInt("party_hat_year").getOrNull() + val partyHatColor = extraAttributes.getString("party_hat_color").getOrNull() + when { + partyHatEmoji != null -> SkyblockId("PARTY_HAT_SLOTH_${partyHatEmoji.uppercase()}") + partyHatYear == 2022 -> SkyblockId("PARTY_HAT_CRAB_${partyHatColor?.uppercase()}_ANIMATED") + else -> SkyblockId("PARTY_HAT_CRAB_${partyHatColor?.uppercase()}") + } + } + + "BALLOON_HAT_2024" -> { + SkyblockId("BALLOON_HAT_2024_${extraAttributes.getString("party_hat_color").getOrNull()?.uppercase()}") + } + else -> { - SkyblockId(id) + SkyblockId(id.replace(":", "-")) } } } diff --git a/src/main/kotlin/util/TestUtil.kt b/src/main/kotlin/util/TestUtil.kt index 45e3dde..da8ba38 100644 --- a/src/main/kotlin/util/TestUtil.kt +++ b/src/main/kotlin/util/TestUtil.kt @@ -2,6 +2,7 @@ package moe.nea.firmament.util object TestUtil { inline fun <T> unlessTesting(block: () -> T): T? = if (isInTest) null else block() + @JvmField val isInTest = Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") || it.className.startsWith("io.kotest.") diff --git a/src/main/kotlin/util/mc/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt index 0405b65..1b7dcba 100644 --- a/src/main/kotlin/util/mc/SkullItemData.kt +++ b/src/main/kotlin/util/mc/SkullItemData.kt @@ -10,7 +10,6 @@ import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers -import kotlinx.serialization.encodeToString import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.ProfileComponent import net.minecraft.item.ItemStack @@ -51,7 +50,7 @@ fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) } -val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") +val arbitraryUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD) .also { it.setSkullOwner(uuid, url) } diff --git a/src/main/resources/assets/firmament/gui/config/macros/combos.xml b/src/main/resources/assets/firmament/gui/config/macros/combos.xml new file mode 100644 index 0000000..b2865ab --- /dev/null +++ b/src/main/resources/assets/firmament/gui/config/macros/combos.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:firm="http://firmament.nea.moe/moulconfig" +> + <Panel background="TRANSPARENT" insets="10"> + <Column> + <Meta beforeClose="@beforeClose"/> + <ScrollPanel width="380" height="300"> + <Align horizontal="CENTER"> + <Array data="@actions"> + <!-- evenBackground="#8B8B8B" oddBackground="#C6C6C6" --> + <Panel background="TRANSPARENT" insets="3"> + <Panel background="VANILLA" insets="6"> + <Column> + <Row> + <Text text="@command" width="280"/> + </Row> + <Row> + <Text text="@formattedCombo" width="250"/> + <Align horizontal="RIGHT"> + <Row> + <firm:Button onClick="@edit"> + <Text text="Edit"/> + </firm:Button> + <Spacer width="12"/> + <firm:Button onClick="@delete"> + <Text text="Delete"/> + </firm:Button> + </Row> + </Align> + </Row> + </Column> + </Panel> + + </Panel> + </Array> + </Align> + </ScrollPanel> + <Align horizontal="RIGHT"> + <Row> + <firm:Button onClick="@discard"> + <Text text="Discard Changes"/> + </firm:Button> + <firm:Button onClick="@saveAndClose"> + <Text text="Save & Close"/> + </firm:Button> + <firm:Button onClick="@save"> + <Text text="Save"/> + </firm:Button> + <firm:Button onClick="@addCommand"> + <Text text="Add Combo Command"/> + </firm:Button> + </Row> + </Align> + </Column> + </Panel> +</Root> diff --git a/src/main/resources/assets/firmament/gui/config/macros/editor.xml b/src/main/resources/assets/firmament/gui/config/macros/editor.xml new file mode 100644 index 0000000..50a1d99 --- /dev/null +++ b/src/main/resources/assets/firmament/gui/config/macros/editor.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:firm="http://firmament.nea.moe/moulconfig" +> + <Center> + <Panel background="VANILLA" insets="10"> + <Column> + <Row> + <firm:Button onClick="@back"> + <Text text="←"/> + </firm:Button> + <Text text="Editing command macro"/> + </Row> + <Row> + <Text text="Command: /"/> + <Align horizontal="RIGHT"> + <TextField value="@command" width="200"/> + </Align> + </Row> + <Row> + <Text text="Key Combo:"/> + <Align horizontal="RIGHT"> + <firm:Button onClick="@addStep"> + <Text text="+"/> + </firm:Button> + </Align> + </Row> + <Array data="@combo"> + <Row> + <firm:Fixed width="160"> + <Indirect value="@button"/> + </firm:Fixed> + <Align horizontal="RIGHT"> + <firm:Button onClick="@delete"> + <Text text="Delete"/> + </firm:Button> + </Align> + </Row> + </Array> + </Column> + </Panel> + </Center> +</Root> diff --git a/src/main/resources/assets/firmament/gui/config/macros/index.xml b/src/main/resources/assets/firmament/gui/config/macros/index.xml new file mode 100644 index 0000000..3fa9f99 --- /dev/null +++ b/src/main/resources/assets/firmament/gui/config/macros/index.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<Root xmlns="http://notenoughupdates.org/moulconfig" + xmlns:firm="http://firmament.nea.moe/moulconfig"> + <Center> + <Tabs> + <Tab> + <Tab.Header> + <Text text="Combo Macros"/> + </Tab.Header> + <Tab.Body> + <Fragment value="firmament:gui/config/macros/combos.xml" bind="@combos"/> + </Tab.Body> + </Tab> + </Tabs> + </Center> +</Root> |