diff options
author | Jacob <55346310+Kathund@users.noreply.github.com> | 2025-08-12 07:27:04 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-08-12 01:27:04 +0200 |
commit | 98a080099683f9354ec4e02e47ff39b30caa8590 (patch) | |
tree | f6f2a2a4deaae6375ecd70c8f4276295f8dd1af3 | |
parent | 92fb76bb1811dde3bf8e9b1bee9789c8e12cc79c (diff) | |
download | Firmament-98a080099683f9354ec4e02e47ff39b30caa8590.tar.gz Firmament-98a080099683f9354ec4e02e47ff39b30caa8590.tar.bz2 Firmament-98a080099683f9354ec4e02e47ff39b30caa8590.zip |
feat: block zapper overlay (#208)
Co-authored-by: Linnea Gräf <nea@nea.moe>
-rw-r--r-- | src/main/kotlin/features/items/BlockZapperOverlay.kt | 146 | ||||
-rw-r--r-- | src/main/kotlin/util/render/CustomRenderLayers.kt | 1 | ||||
-rw-r--r-- | src/main/kotlin/util/render/RenderInWorldContext.kt | 115 | ||||
-rw-r--r-- | src/main/kotlin/util/skyblock/SkyBlockItems.kt | 1 | ||||
-rw-r--r-- | translations/en_us.json | 7 |
5 files changed, 235 insertions, 35 deletions
diff --git a/src/main/kotlin/features/items/BlockZapperOverlay.kt b/src/main/kotlin/features/items/BlockZapperOverlay.kt new file mode 100644 index 0000000..c207d67 --- /dev/null +++ b/src/main/kotlin/features/items/BlockZapperOverlay.kt @@ -0,0 +1,146 @@ +package moe.nea.firmament.features.items + +import io.github.notenoughupdates.moulconfig.ChromaColour +import java.util.LinkedList +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.block.Blocks +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.BlockPos +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ClientStartedEvent +import moe.nea.firmament.events.WorldKeyboardEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.render.RenderInWorldContext +import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.skyblock.SkyBlockItems + +object BlockZapperOverlay : FirmamentFeature { + override val identifier: String + get() = "block-zapper-overlay" + + object TConfig : ManagedConfig(identifier, Category.ITEMS) { + var blockZapperOverlay by toggle("block-zapper-overlay") { false } + val color by colour("color") { ChromaColour.fromStaticRGB(160, 0, 0, 60) } + var undoKey by keyBindingWithDefaultUnbound("undo-key") + } + + @Subscribe + fun onInit(event: ClientStartedEvent) { + } + + override val config: ManagedConfig + get() = TConfig + + val bannedZapper: List<Block> = listOf<Block>( + Blocks.WHEAT, + Blocks.CARROTS, + Blocks.POTATOES, + Blocks.PUMPKIN, + Blocks.PUMPKIN_STEM, + Blocks.MELON, + Blocks.MELON_STEM, + Blocks.CACTUS, + Blocks.SUGAR_CANE, + Blocks.NETHER_WART, + Blocks.TALL_GRASS, + Blocks.SUNFLOWER, + Blocks.FARMLAND, + Blocks.BREWING_STAND, + Blocks.SNOW, + Blocks.RED_MUSHROOM, + Blocks.BROWN_MUSHROOM, + ) + + private val zapperOffsets: List<BlockPos> = listOf( + BlockPos(0, 0, -1), + BlockPos(0, 0, 1), + BlockPos(-1, 0, 0), + BlockPos(1, 0, 0), + BlockPos(0, 1, 0), + BlockPos(0, -1, 0) + ) + + // Skidded from NEU + // Credit: https://github.com/NotEnoughUpdates/NotEnoughUpdates/blob/9b1fcfebc646e9fb69f99006327faa3e734e5f51/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java#L1281-L1355 (Modified) + @Subscribe + fun renderBlockZapperOverlay(event: WorldRenderLastEvent) { + if (!TConfig.blockZapperOverlay) return + val player = MC.player ?: return + val world = player.world ?: return + val heldItem = MC.stackInHand + if (heldItem.skyBlockId != SkyBlockItems.BLOCK_ZAPPER) return + val hitResult = MC.instance.crosshairTarget ?: return + + val zapperBlocks: HashSet<BlockPos> = HashSet() + val returnablePositions = LinkedList<BlockPos>() + + if (hitResult is BlockHitResult && hitResult.type == HitResult.Type.BLOCK) { + var pos: BlockPos = hitResult.blockPos + val firstBlockState: BlockState = world.getBlockState(pos) + val block = firstBlockState.block + + val initialAboveBlock = world.getBlockState(pos.up()).block + if (!bannedZapper.contains(initialAboveBlock) && !bannedZapper.contains(block)) { + var i = 0 + while (i < 164) { + zapperBlocks.add(pos) + returnablePositions.remove(pos) + + val availableNeighbors: MutableList<BlockPos> = ArrayList() + + for (offset in zapperOffsets) { + val newPos = pos.add(offset) + + if (zapperBlocks.contains(newPos)) continue + + val state: BlockState? = world.getBlockState(newPos) + if (state != null && state.block === block) { + val above = newPos.up() + val aboveBlock = world.getBlockState(above).block + if (!bannedZapper.contains(aboveBlock)) { + availableNeighbors.add(newPos) + } + } + } + + if (availableNeighbors.size >= 2) { + returnablePositions.add(pos) + pos = availableNeighbors[0] + } else if (availableNeighbors.size == 1) { + pos = availableNeighbors[0] + } else if (returnablePositions.isEmpty()) { + break + } else { + i-- + pos = returnablePositions.last() + } + + i++ + } + } + + RenderInWorldContext.renderInWorld(event) { + if (MC.player?.isSneaking ?: false) { + zapperBlocks.forEach { + block(it, TConfig.color.getEffectiveColourRGB()) + } + } else { + sharedVoxelSurface(zapperBlocks, TConfig.color.getEffectiveColourRGB()) + } + } + } + } + + @Subscribe + fun onWorldKeyboard(it: WorldKeyboardEvent) { + if (!TConfig.undoKey.isBound) return + if (!it.matches(TConfig.undoKey)) return + if (MC.stackInHand.skyBlockId != SkyBlockItems.BLOCK_ZAPPER) return + MC.sendCommand("undozap") + } +} diff --git a/src/main/kotlin/util/render/CustomRenderLayers.kt b/src/main/kotlin/util/render/CustomRenderLayers.kt index f713a81..2da1de7 100644 --- a/src/main/kotlin/util/render/CustomRenderLayers.kt +++ b/src/main/kotlin/util/render/CustomRenderLayers.kt @@ -88,6 +88,7 @@ object CustomRenderLayers { val COLORED_QUADS = RenderLayer.of( "firmament_quads", RenderLayer.DEFAULT_BUFFER_SIZE, + false, true, CustomRenderPipelines.COLORED_OMNIPRESENT_QUADS, RenderLayer.MultiPhaseParameters.builder() .lightmap(RenderPhase.DISABLE_LIGHTMAP) diff --git a/src/main/kotlin/util/render/RenderInWorldContext.kt b/src/main/kotlin/util/render/RenderInWorldContext.kt index 4963920..c30ee19 100644 --- a/src/main/kotlin/util/render/RenderInWorldContext.kt +++ b/src/main/kotlin/util/render/RenderInWorldContext.kt @@ -11,7 +11,6 @@ import net.minecraft.client.render.RenderLayer import net.minecraft.client.render.RenderTickCounter import net.minecraft.client.render.VertexConsumer import net.minecraft.client.render.VertexConsumerProvider -import net.minecraft.client.render.VertexFormats import net.minecraft.client.texture.Sprite import net.minecraft.client.util.math.MatrixStack import net.minecraft.text.Text @@ -20,7 +19,6 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import moe.nea.firmament.events.WorldRenderLastEvent import moe.nea.firmament.util.FirmFormatters -import moe.nea.firmament.util.IntUtil.toRGBA import moe.nea.firmament.util.MC @RenderContextDSL @@ -49,6 +47,38 @@ class RenderInWorldContext private constructor( matrixStack.pop() } + fun sharedVoxelSurface(blocks: Set<BlockPos>, color: Int) { + val m = BlockPos.Mutable() + val l = vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS) + blocks.forEach { + matrixStack.push() + matrixStack.translate(it.x.toFloat(), it.y.toFloat(), it.z.toFloat()) + val p = matrixStack.peek().positionMatrix + m.set(it) + if (m.setX(it.x + 1) !in blocks) { + buildFaceXP(p, l, color) + } + if (m.setX(it.x - 1) !in blocks) { + buildFaceXN(p, l, color) + } + m.set(it) + if (m.setY(it.y + 1) !in blocks) { + buildFaceYP(p, l, color) + } + if (m.setY(it.y - 1) !in blocks) { + buildFaceYN(p, l, color) + } + m.set(it) + if (m.setZ(it.z + 1) !in blocks) { + buildFaceZP(p, l, color) + } + if (m.setZ(it.z - 1) !in blocks) { + buildFaceZN(p, l, color) + } + matrixStack.pop() + } + } + enum class VerticalAlign { TOP, BOTTOM, CENTER; @@ -205,41 +235,56 @@ class RenderInWorldContext private constructor( } } - private fun buildCube(matrix: Matrix4f, buf: VertexConsumer, colorInt: Int) { - val (r, g, b, a) = colorInt.toRGBA() - - // Y- - buf.vertex(matrix, 0F, 0F, 0F).color(r, g, b, a) - buf.vertex(matrix, 0F, 0F, 1F).color(r, g, b, a) - buf.vertex(matrix, 1F, 0F, 1F).color(r, g, b, a) - buf.vertex(matrix, 1F, 0F, 0F).color(r, g, b, a) - // Y+ - buf.vertex(matrix, 0F, 1F, 0F).color(r, g, b, a) - buf.vertex(matrix, 1F, 1F, 0F).color(r, g, b, a) - buf.vertex(matrix, 1F, 1F, 1F).color(r, g, b, a) - buf.vertex(matrix, 0F, 1F, 1F).color(r, g, b, a) - // X- - buf.vertex(matrix, 0F, 0F, 0F).color(r, g, b, a) - buf.vertex(matrix, 0F, 0F, 1F).color(r, g, b, a) - buf.vertex(matrix, 0F, 1F, 1F).color(r, g, b, a) - buf.vertex(matrix, 0F, 1F, 0F).color(r, g, b, a) - // X+ - buf.vertex(matrix, 1F, 0F, 0F).color(r, g, b, a) - buf.vertex(matrix, 1F, 1F, 0F).color(r, g, b, a) - buf.vertex(matrix, 1F, 1F, 1F).color(r, g, b, a) - buf.vertex(matrix, 1F, 0F, 1F).color(r, g, b, a) - // Z- - buf.vertex(matrix, 0F, 0F, 0F).color(r, g, b, a) - buf.vertex(matrix, 1F, 0F, 0F).color(r, g, b, a) - buf.vertex(matrix, 1F, 1F, 0F).color(r, g, b, a) - buf.vertex(matrix, 0F, 1F, 0F).color(r, g, b, a) - // Z+ - buf.vertex(matrix, 0F, 0F, 1F).color(r, g, b, a) - buf.vertex(matrix, 0F, 1F, 1F).color(r, g, b, a) - buf.vertex(matrix, 1F, 1F, 1F).color(r, g, b, a) - buf.vertex(matrix, 1F, 0F, 1F).color(r, g, b, a) + private fun buildFaceZP(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) { + buf.vertex(matrix, 0F, 0F, 1F).color(rgba) + buf.vertex(matrix, 0F, 1F, 1F).color(rgba) + buf.vertex(matrix, 1F, 1F, 1F).color(rgba) + buf.vertex(matrix, 1F, 0F, 1F).color(rgba) + } + + private fun buildFaceZN(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) { + buf.vertex(matrix, 0F, 0F, 0F).color(rgba) + buf.vertex(matrix, 1F, 0F, 0F).color(rgba) + buf.vertex(matrix, 1F, 1F, 0F).color(rgba) + buf.vertex(matrix, 0F, 1F, 0F).color(rgba) + } + + private fun buildFaceXP(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) { + buf.vertex(matrix, 1F, 0F, 0F).color(rgba) + buf.vertex(matrix, 1F, 1F, 0F).color(rgba) + buf.vertex(matrix, 1F, 1F, 1F).color(rgba) + buf.vertex(matrix, 1F, 0F, 1F).color(rgba) + } + + private fun buildFaceXN(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) { + buf.vertex(matrix, 0F, 0F, 0F).color(rgba) + buf.vertex(matrix, 0F, 0F, 1F).color(rgba) + buf.vertex(matrix, 0F, 1F, 1F).color(rgba) + buf.vertex(matrix, 0F, 1F, 0F).color(rgba) } + private fun buildFaceYN(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) { + buf.vertex(matrix, 0F, 0F, 0F).color(rgba) + buf.vertex(matrix, 0F, 0F, 1F).color(rgba) + buf.vertex(matrix, 1F, 0F, 1F).color(rgba) + buf.vertex(matrix, 1F, 0F, 0F).color(rgba) + } + + private fun buildFaceYP(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) { + buf.vertex(matrix, 0F, 1F, 0F).color(rgba) + buf.vertex(matrix, 1F, 1F, 0F).color(rgba) + buf.vertex(matrix, 1F, 1F, 1F).color(rgba) + buf.vertex(matrix, 0F, 1F, 1F).color(rgba) + } + + private fun buildCube(matrix4f: Matrix4f, buf: VertexConsumer, rgba: Int) { + buildFaceXP(matrix4f, buf, rgba) + buildFaceXN(matrix4f, buf, rgba) + buildFaceYP(matrix4f, buf, rgba) + buildFaceYN(matrix4f, buf, rgba) + buildFaceZP(matrix4f, buf, rgba) + buildFaceZN(matrix4f, buf, rgba) + } fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) { // TODO: there should be *no more global state*. the only thing we should be doing is render layers. that includes settings like culling, blending, shader color, and depth testing diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt index d552fd7..32c4aab 100644 --- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt +++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt @@ -19,5 +19,6 @@ object SkyBlockItems { val BONE_BOOMERANG = SkyblockId("BONE_BOOMERANG") val STARRED_BONE_BOOMERANG = SkyblockId("STARRED_BONE_BOOMERANG") val TRIBAL_SPEAR = SkyblockId("TRIBAL_SPEAR") + val BLOCK_ZAPPER = SkyblockId("BLOCK_ZAPPER") val HUNTING_TOOLKIT = SkyblockId("HUNTING_TOOLKIT") } diff --git a/translations/en_us.json b/translations/en_us.json index c18c2f7..b902206 100644 --- a/translations/en_us.json +++ b/translations/en_us.json @@ -24,6 +24,13 @@ "firmament.config.auto-completions.warp-complete.description": "Auto complete warp destinations in chat. This may include warps you have not yet unlocked.", "firmament.config.auto-completions.warp-is": "Redirect /warp is to /warp island", "firmament.config.auto-completions.warp-is.description": "Redirects /warp is to /warp island, since hypixel does not recognize /warp is as a warp destination.", + "firmament.config.block-zapper-overlay": "Block Zapper Overlay", + "firmament.config.block-zapper-overlay.block-zapper-overlay": "Block Zapper Overlay", + "firmament.config.block-zapper-overlay.block-zapper-overlay.description": "Shows what blocks will be zapped", + "firmament.config.block-zapper-overlay.color": "Colour", + "firmament.config.block-zapper-overlay.color.description": "The color that the blocks will be highlighted in", + "firmament.config.block-zapper-overlay.undo-key": "Undo Keybind", + "firmament.config.block-zapper-overlay.undo-key.description": "Keybind to undo your zap", "firmament.config.bonemerang-overlay": "Bonemerang Overlay", "firmament.config.bonemerang-overlay.bonemerang-overlay": "Bonemerang Overlay", "firmament.config.bonemerang-overlay.bonemerang-overlay-hud": "Bonemerang Overlay Hud", |