aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob <55346310+Kathund@users.noreply.github.com>2025-08-12 07:27:04 +0800
committerGitHub <noreply@github.com>2025-08-12 01:27:04 +0200
commit98a080099683f9354ec4e02e47ff39b30caa8590 (patch)
treef6f2a2a4deaae6375ecd70c8f4276295f8dd1af3
parent92fb76bb1811dde3bf8e9b1bee9789c8e12cc79c (diff)
downloadFirmament-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.kt146
-rw-r--r--src/main/kotlin/util/render/CustomRenderLayers.kt1
-rw-r--r--src/main/kotlin/util/render/RenderInWorldContext.kt115
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt1
-rw-r--r--translations/en_us.json7
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",