diff --git a/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt b/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt index aef28e9bf..11f248bd7 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt @@ -7,7 +7,7 @@ import com.lambda.interaction.rotation.Rotation.Companion.rotation import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.threading.runSafe import com.lambda.util.math.VecUtils.distSq -import com.lambda.util.world.WorldUtils.getFastEntities +import com.lambda.util.world.entitySearch import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.LivingEntity import net.minecraft.entity.mob.MobEntity @@ -31,16 +31,6 @@ abstract class Targeting( override val invisible by c.setting("Invisible", true) { vis() } override val dead by c.setting("Dead", false) { vis() } - fun getEntities(): List = - mutableListOf().apply { - runSafe { - getFastEntities( - player.pos, targetingRange, this@apply, - predicate = { entity -> validate(player, entity) } - ) - } - } - open fun validate(player: ClientPlayerEntity, entity: LivingEntity) = when { !players && entity.isPlayer -> false !animals && entity is PassiveEntity -> false @@ -65,24 +55,15 @@ abstract class Targeting( } fun getTarget(): LivingEntity? = runSafe { - var best: LivingEntity? = null - var bestFactor = Double.MAX_VALUE - - val comparator = { entity: LivingEntity, _: Int -> - val factor = priority.factor(this, entity) - if (factor < bestFactor) { - best = entity - bestFactor = factor - } - } - val predicate = { entity: LivingEntity -> validate(player, entity) } - getFastEntities(player.pos, targetingRange, null, comparator, predicate) - - return@runSafe best + return@runSafe entitySearch(targetingRange) { + predicate(it) + }.minBy { + priority.factor(this, it) + } } } @@ -97,4 +78,4 @@ abstract class Targeting( HEALTH({ it.health.toDouble() }), FOV({ player.rotation dist player.eyePos.rotationTo(it.pos) }) } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/core/annotations/InternalApi.kt b/common/src/main/kotlin/com/lambda/core/annotations/InternalApi.kt new file mode 100644 index 000000000..6a023ee78 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/core/annotations/InternalApi.kt @@ -0,0 +1,9 @@ +package com.lambda.core.annotations + +import kotlin.annotation.AnnotationTarget.* + +@RequiresOptIn( + message = "Only use if you know what you are doing, no support will be provided whatsoever, use at your own risk", +) +@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS) +internal annotation class InternalApi diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index b7401ca1b..57dac5710 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -4,11 +4,8 @@ import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.RotationSettings import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.concurrentListener -import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.util.world.WorldUtils.getClosestEntity -import net.minecraft.entity.LivingEntity import net.minecraft.util.Hand object CrystalAura : Module( @@ -56,9 +53,5 @@ object CrystalAura : Module( /*listener { event -> event.lookAtEntity(rotation, interac, getClosestEntity(player.eyePos, placeRange) ?: return@listener) }*/ - - listener { - getClosestEntity(player.eyePos, 64.0) - } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt new file mode 100644 index 000000000..7eeee03ff --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt @@ -0,0 +1,45 @@ +package com.lambda.module.modules.debug + +import com.lambda.event.events.RenderEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.graphics.renderer.esp.builders.build +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.world.blockSearch +import net.minecraft.block.Blocks +import net.minecraft.util.math.Vec3i +import java.awt.Color + +object BlockTest : Module( + name = "BlockTest", + description = "BlockTest", + defaultTags = setOf(ModuleTag.DEBUG), +) { + private val rangeX by setting("Range X", 5, 1..7, 1, "Range X") + private val rangeY by setting("Range Y", 5, 1..7, 1, "Range Y") + private val rangeZ by setting("Range Z", 5, 1..7, 1, "Range Z") + private val stepX by setting("Step X", 1, 1..7, 1, "Step X") + private val stepY by setting("Step Y", 1, 1..7, 1, "Step Y") + private val stepZ by setting("Step Z", 1, 1..7, 1, "Step Z") + + private val range: Vec3i + get() = Vec3i(rangeX, rangeY, rangeZ) + + private val step: Vec3i + get() = Vec3i(stepX, stepY, stepZ) + + private val filledColor = Color(100, 150, 255, 128) + private val outlineColor = Color(100, 150, 255, 51) + + init { + listener { + blockSearch(range, step) { _, state -> + state.isOf(Blocks.DIAMOND_BLOCK) + }.forEach { (pos, state) -> + state.getOutlineShape(world, pos).boundingBoxes.forEach { box -> + it.renderer.build(box.offset(pos), filledColor, outlineColor) + } + } + } + } +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/EntityTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/EntityTest.kt deleted file mode 100644 index 9db4a1aba..000000000 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/EntityTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.lambda.module.modules.debug - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listener -import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.util.world.WorldUtils.getClosestEntity -import net.minecraft.entity.Entity - -object EntityTest : Module( - name = "EntityTest", - description = "Test entity", - defaultTags = setOf(ModuleTag.DEBUG) -) { - init { - listener { - repeat(10000) { - getClosestEntity(player.eyePos, 7.0) - } - } - } -} diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index e014b2321..c1b6be5ec 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -7,7 +7,7 @@ import com.lambda.graphics.renderer.esp.builders.build import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.ColorUtils.setAlpha -import com.lambda.util.world.WorldUtils.getClosestEntity +import com.lambda.util.world.entitySearch import net.minecraft.entity.LivingEntity import net.minecraft.util.math.Box import java.awt.Color @@ -29,12 +29,14 @@ object RenderTest : Module( init { listener { - val entity = getClosestEntity(player.pos, 8.0) ?: return@listener - it.renderer.build(entity.dynamicBox, filledColor, outlineColor) + entitySearch(8.0) + .forEach { entity -> + it.renderer.build(entity.dynamicBox, filledColor, outlineColor) + } } listener { it.renderer.build(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt index f67665104..29f45947f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt @@ -5,7 +5,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.util.world.WorldUtils.getEntities +import com.lambda.util.world.entitySearch import net.minecraft.entity.passive.AbstractHorseEntity import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket @@ -15,38 +15,23 @@ object EntityControl : Module( description = "Control mountable entities", defaultTags = setOf(ModuleTag.MOVEMENT) ) { - private val page by setting("Page", Page.GENERAL) - - /* General */ - private val forceMount by setting("Force Mount", true, description = "Attempts to force mount chested entities.", visibility = { page == Page.GENERAL }).apply { + private val forceMount by setting("Force Mount", true, description = "Attempts to force mount chested entities.").apply { onValueChange { _, _ -> - horses.forEach { horse -> horse.updateSaddle() } + resetMounts() } } - - /* Movement */ - private val speed by setting("Entity Speed", 2.0, 0.1..10.0, 0.1, description = "Speed for entities.", visibility = { page == Page.MOVEMENT }) - - private enum class Page { - GENERAL, MOVEMENT - } - - private val horses = mutableListOf() + private val modified = mutableSetOf() init { listener { - if (forceMount) { - getEntities(player.pos, 8.0, horses, { horse, _ -> horse.setHorseFlag(4, true) }) - } - } - - /*listener { - if (!player.isRiding) return@listener + if (!forceMount) return@listener - // We can do this because the player movement depends on the entity movement - player.vehicle?.motionX = speed - player.vehicle?.motionZ = speed - }*/ + entitySearch(8.0) + .forEach { + it.setHorseFlag(4, true) + modified.add(it) + } + } listener { event -> if (!forceMount) return@listener @@ -60,7 +45,11 @@ object EntityControl : Module( } onDisable { - horses.clear() + resetMounts() } } + + private fun resetMounts() { + modified.forEach { it.updateSaddle() } + } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 318cc46ce..0ba412a2f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -13,11 +13,9 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Nameable import com.lambda.util.player.MovementUtils.addSpeed -import com.lambda.util.player.MovementUtils.buildMovementInput import com.lambda.util.player.MovementUtils.calcMoveYaw import com.lambda.util.player.MovementUtils.handledByBaritone import com.lambda.util.player.MovementUtils.isInputting -import com.lambda.util.player.MovementUtils.mergeFrom import com.lambda.util.player.MovementUtils.motionY import com.lambda.util.player.MovementUtils.moveDelta import com.lambda.util.player.MovementUtils.newMovementInput @@ -25,7 +23,7 @@ import com.lambda.util.player.MovementUtils.roundedForward import com.lambda.util.player.MovementUtils.roundedStrafing import com.lambda.util.player.MovementUtils.setSpeed import com.lambda.util.extension.contains -import com.lambda.util.world.WorldUtils.getFastEntities +import com.lambda.util.world.entitySearch import net.minecraft.entity.LivingEntity import net.minecraft.entity.decoration.ArmorStandEntity import net.minecraft.entity.vehicle.BoatEntity @@ -134,22 +132,14 @@ object Speed : Module( var boostAmount = 0.0 - getFastEntities( - player.pos, 3.0, - predicate = { player.boundingBox.expand(1.0) in it.boundingBox && it !is ArmorStandEntity }, - iterator = { e, _ -> - val colliding = player.boundingBox in e.boundingBox - val multiplier = if (colliding) grimCollideMultiplier else 1.0 - boostAmount += 0.08 * grimEntityBoost * multiplier - } - ) + boostAmount += entitySearch(3.0) { + player.boundingBox.expand(1.0) in it.boundingBox && it !is ArmorStandEntity + }.sumOf { 0.08 * grimEntityBoost } if (grimBoatBoost > 0.0) { - getFastEntities( - player.pos, 4.0, - predicate = { player.boundingBox in it.boundingBox.expand(0.01) }, - iterator = { _, _ -> boostAmount += grimBoatBoost } - ) + boostAmount += entitySearch(4.0) { + player.boundingBox in it.boundingBox.expand(0.01) + }.sumOf { grimBoatBoost } } addSpeed(boostAmount) diff --git a/common/src/main/kotlin/com/lambda/util/collections/Extensions.kt b/common/src/main/kotlin/com/lambda/util/collections/Extensions.kt index 32753525a..01fae338e 100644 --- a/common/src/main/kotlin/com/lambda/util/collections/Extensions.kt +++ b/common/src/main/kotlin/com/lambda/util/collections/Extensions.kt @@ -1,5 +1,7 @@ package com.lambda.util.collections +import kotlin.reflect.KClass + /** * Filters elements of the iterable by their runtime type and a predicate, and adds the matching elements to the specified mutable collection. * @@ -20,16 +22,47 @@ inline fun > Iterable<*>.filterPointer( predicate: (R) -> Boolean, ) { var index = 0 - for (element in this) { - when { - element is R && predicate(element) && destination != null -> { - iterator(element, index++) - destination.add(element) - } - element is R && predicate(element) && destination == null -> { - iterator(element, index++) - } + forEach { element -> + val fulfilled = predicate(element as R) + + if (fulfilled && destination != null) { + destination.add(element) + iterator(element, index) + } + + index++ + } +} + +/** + * Filters elements of the iterable by their runtime type and a predicate, and adds the matching elements to the specified mutable collection. + * + * This function allows filtering elements of an iterable based on their runtime type and a provided predicate function. + * The elements that match both the type constraint and the predicate are added to the destination mutable collection. + * Because we do not want additional overhead, this function acts as pointer receiver to a collection. + * The predicate function determines whether an element should be included based on its type and any additional criteria. + * + * @param R The target type to filter elements to. + * @param C The type of the destination mutable collection. + * @param destination The mutable collection to which the filtered elements will be added. + * @param iterator The iterator function that processes the filtered elements and their index. + * @param predicate The predicate function that determines whether an element should be included based on its type and other criteria. + */ +inline fun > Iterable<*>.filterPointer( + kClass: KClass, + destination: C?, + iterator: (R) -> Unit, + predicate: (R) -> Boolean, +) { + forEach { element -> + // Cannot be replaced with reified type due to type erasure + (element as? R) ?: return@forEach + val fulfilled = kClass.isInstance(element) && predicate(element) + + if (fulfilled && destination != null) { + destination.add(element) + iterator(element) } } } diff --git a/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt b/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt index ac559c39c..24fc433e0 100644 --- a/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt +++ b/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt @@ -1,11 +1,15 @@ +@file:OptIn(InternalApi::class) + package com.lambda.util.combat import com.lambda.context.SafeContext +import com.lambda.core.annotations.InternalApi import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.fluidState import com.lambda.util.math.VecUtils.minus import com.lambda.util.math.VecUtils.times -import com.lambda.util.world.WorldUtils.getFastEntities +import com.lambda.util.world.WorldUtils.internalGetFastEntities +import com.lambda.util.world.toFastVec import net.minecraft.enchantment.ProtectionEnchantment import net.minecraft.entity.LivingEntity import net.minecraft.util.math.BlockPos @@ -51,7 +55,7 @@ object Explosion { */ fun SafeContext.explosionVelocity(explosion: Explosion): Map { val ref = ArrayList() - getFastEntities(explosion.position, explosion.power * 2.0, ref) + internalGetFastEntities(explosion.position.toFastVec(), explosion.power * 2.0, ref) return ref.associateWith { entity -> explosionVelocity(entity, explosion) } } diff --git a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt index 21d3626fc..bc22e2911 100644 --- a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt @@ -1,6 +1,11 @@ package com.lambda.util.math import com.lambda.util.math.MathUtils.sq +import com.lambda.util.world.FastVector +import com.lambda.util.world.x +import com.lambda.util.world.y +import com.lambda.util.world.z +import net.minecraft.entity.Entity import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3i @@ -11,6 +16,9 @@ object VecUtils { val Vec3d.blockPos: BlockPos get() = BlockPos(x.roundToInt(), y.roundToInt(), z.roundToInt()) + val Vec3i.asVec3d: Vec3d + get() = Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) + infix fun Vec3d.dist(other: Vec3d): Double = this.distanceTo(other) infix fun Vec3d.distSq(other: Vec3d): Double = this.squaredDistanceTo(other) @@ -21,6 +29,10 @@ object VecUtils { infix fun Vec3i.distSq(other: Vec3i): Int = (this.x - other.x).sq + (this.y - other.y).sq + (this.z - other.z).sq + infix fun Entity.distSq(other: Vec3d): Double = this.pos distSq other + + infix fun Entity.distSq(other: Vec3i): Int = this.blockPos distSq other + infix operator fun Vec3d.plus(other: Vec3d): Vec3d = this.add(other) infix operator fun Vec3d.minus(other: Vec3d): Vec3d = this.subtract(other) diff --git a/common/src/main/kotlin/com/lambda/util/primitives/extension/World.kt b/common/src/main/kotlin/com/lambda/util/primitives/extension/World.kt new file mode 100644 index 000000000..fa4a053b5 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/primitives/extension/World.kt @@ -0,0 +1,36 @@ +package com.lambda.util.primitives.extension + +import com.lambda.util.world.FastVector +import com.lambda.util.world.x +import com.lambda.util.world.y +import com.lambda.util.world.z +import net.minecraft.block.BlockState +import net.minecraft.block.Blocks +import net.minecraft.fluid.FluidState +import net.minecraft.fluid.Fluids +import net.minecraft.world.World + +fun World.getBlockState(x: Int, y: Int, z: Int): BlockState { + if (isOutOfHeightLimit(y)) return Blocks.VOID_AIR.defaultState + + val chunk = getChunk(x shr 4, z shr 4) + val sectionIndex = getSectionIndex(y) + if (sectionIndex < 0) return Blocks.VOID_AIR.defaultState + + val section = chunk.getSection(sectionIndex) + return section.getBlockState(x and 0xF, y and 0xF, z and 0xF) +} + +fun World.getFluidState(x: Int, y: Int, z: Int): FluidState { + if (isOutOfHeightLimit(y)) return Fluids.EMPTY.defaultState + + val chunk = getChunk(x shr 4, z shr 4) + val sectionIndex = getSectionIndex(y) + if (sectionIndex < 0) return Fluids.EMPTY.defaultState + + val section = chunk.getSection(sectionIndex) + return section.getFluidState(x and 0xF, y and 0xF, z and 0xF) +} + +fun World.getBlockState(vec: FastVector): BlockState = getBlockState(vec.x, vec.y, vec.z) +fun World.getFluidState(vec: FastVector): FluidState = getFluidState(vec.x, vec.y, vec.z) diff --git a/common/src/main/kotlin/com/lambda/util/world/BlockDsl.kt b/common/src/main/kotlin/com/lambda/util/world/BlockDsl.kt new file mode 100644 index 000000000..c14fb2fae --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/world/BlockDsl.kt @@ -0,0 +1,87 @@ +@file:OptIn(InternalApi::class) + +package com.lambda.util.world + +import com.lambda.context.SafeContext +import com.lambda.core.annotations.InternalApi +import com.lambda.util.world.WorldUtils.MAGICVECTOR +import com.lambda.util.world.WorldUtils.internalSearchBlocks +import net.minecraft.block.BlockState +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraft.util.math.Vec3i + +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +@DslMarker +annotation class BlockDslMarker + +/** + * The [BlockDsl] class provides a DSL for performing block search operations + * within a specified range in a Minecraft world. + * It allows for filtering blocks around a given position. + * + * @param safeContext The context in which the block search is performed, providing safe access to world data. + * @param pos The position from which to start the block search. + * + * ### Usage Example: + * + * ```kotlin + * val blocks = blockSearch(range = Vec3i(10, 10, 10)) { + * it.isOf(Blocks.DIAMOND_BLOCK) // Filter out blocks that are not diamond blocks + * } + * + * blocks.forEach { (pos, state) -> + * println("Found diamond block at: $pos") + * } + * ``` + */ +@BlockDslMarker +class BlockDsl( + private val safeContext: SafeContext, + pos: BlockPos, + private val range: Vec3i, + private val step: Vec3i, + private val predicate: (BlockPos, BlockState) -> Boolean +) { + private val fastVector = pos.toFastVec() + private val receiver: MutableMap = mutableMapOf() + + fun build(): Map { + safeContext.internalSearchBlocks(fastVector, range.toFastVec(), step.toFastVec(), receiver, { pos, state -> predicate(pos.toBlockPos(), state) }, { _, _ -> }) + return receiver.mapKeys { it.key.toBlockPos() } + } +} + +/** + * Searches for blocks around the player's position and applies the specified block operations. + * + * @param pos The position around which to search for blocks. Defaults to the player's current position. + * @param range The `x`, `y`, `z` range around the position to search for blocks. + * @param step The `x`, `y`, `z` step intervals at which to check for blocks. + * @param predicate The predicate to filter blocks. + * @return A map of block positions and their states matching the predicate within the specified range. + */ +fun SafeContext.blockSearch( + range: Vec3i, + step: Vec3i, + pos: BlockPos = player.blockPos, + predicate: (BlockPos, BlockState) -> Boolean +): Map = + BlockDsl(this, pos, range, step, predicate).build() + +/** + * Searches for blocks around the player's position and applies the specified block operations. + * + * @param pos The position around which to search for blocks. Defaults to the player's current position. + * @param range The range around the position to search for blocks. + * @param step The step intervals at which to check for blocks. + * @param predicate The predicate to filter blocks. + * @return A map of block positions and their states matching the predicate within the specified range. + */ +fun SafeContext.blockSearch( + range: Int, + step: Int, + pos: BlockPos = player.blockPos, + predicate: (BlockPos, BlockState) -> Boolean +): Map = + blockSearch(Vec3i(range, range, range), Vec3i(step, step, step), pos, predicate) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/world/EntityDsl.kt b/common/src/main/kotlin/com/lambda/util/world/EntityDsl.kt new file mode 100644 index 000000000..4af32e9da --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/world/EntityDsl.kt @@ -0,0 +1,99 @@ +@file:OptIn(InternalApi::class) + +package com.lambda.util.world + +import com.lambda.context.SafeContext +import com.lambda.core.annotations.InternalApi +import com.lambda.util.world.WorldUtils.internalGetEntities +import com.lambda.util.world.WorldUtils.internalGetFastEntities +import net.minecraft.entity.Entity +import net.minecraft.util.math.BlockPos +import kotlin.reflect.KClass + +@DslMarker +annotation class EntityDslMarker + +/** + * The [EntityDsl] class provides a DSL for performing entity search operations + * within a specified range in a Minecraft world. It allows for filtering entities + * of a specific type [T] around a given position. + * + * @param safeContext The context in which the entity search is performed, providing safe access to world data. + * @param kClass The class of the entity type [T]. + * @param pos The position from which to start the entity search. + * @param range The range around the position to search for entities. + * @param predicate The predicate to filter entities. + * + * ### Usage Example: + * + * ```kotlin + * val entities = entitySearch(range = 10.0) { + * it.isAlive // Filter out dead entities + * } + * + * val closestEntityToFOV = entities.minBy { + * player.rotation dist player.eyePos.rotationTo(it.pos) + * } + * ``` + */ +@EntityDslMarker +class EntityDsl( + private val safeContext: SafeContext, + private val kClass: KClass, + pos: BlockPos, + private val range: Double, + private val predicate: (T) -> Boolean +) { + private val fastVector = pos.toFastVec() + private val receiver: MutableList = mutableListOf() + + /** + * Retrieves a list of entities of type [T] within the specified range. + * This method is safe to use with mutable lists and should be used when the optimized method is not suitable. + */ + @EntityDslMarker + fun build(): List { + safeContext.internalGetEntities(kClass, fastVector, range, receiver, predicate) + return receiver + } + + /** + * Retrieves a list of entities of type [T] within the specified range. + * This method is optimized for performance and should be used when possible. + */ + @EntityDslMarker + fun buildFast(): List { + safeContext.internalGetFastEntities(kClass, fastVector, range, receiver, predicate) + return receiver + } +} + +/** + * Initiates an entity search operation in the world at the specified position using an [EntityDsl]. + * + * @param pos The position to start the search from. Defaults to the player's current position. + * @param range The range around the position to search for entities. + * @param predicate The predicate to filter entities. + * @return A list of entities matching the predicate within the specified range. + */ +@EntityDslMarker +inline fun SafeContext.entitySearch( + range: Double, + pos: BlockPos = player.blockPos, + noinline predicate: (T) -> Boolean = { true } +): List = EntityDsl(this, T::class, pos, range, predicate).build() + +/** + * Initiates an optimized entity search operation in the world at the specified position using an [EntityDsl]. + * + * @param pos The position to start the search from. Defaults to the player's current position. + * @param range The range around the position to search for entities. + * @param predicate The predicate to filter entities. + * @return A list of entities matching the predicate within the specified range. + */ +@EntityDslMarker +inline fun SafeContext.fastEntitySearch( + range: Double, + pos: BlockPos = player.blockPos, + noinline predicate: (T) -> Boolean +): List = EntityDsl(this, T::class, pos, range, predicate).buildFast() diff --git a/common/src/main/kotlin/com/lambda/util/world/FluidDsl.kt b/common/src/main/kotlin/com/lambda/util/world/FluidDsl.kt new file mode 100644 index 000000000..29dfa2f5e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/world/FluidDsl.kt @@ -0,0 +1,92 @@ +@file:OptIn(InternalApi::class) + +package com.lambda.util.world + +import com.lambda.context.SafeContext +import com.lambda.core.annotations.InternalApi +import com.lambda.util.world.WorldUtils.MAGICVECTOR +import com.lambda.util.world.WorldUtils.internalSearchFluids +import net.minecraft.fluid.Fluid +import net.minecraft.fluid.FluidState +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraft.util.math.Vec3i +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +@DslMarker +annotation class FluidDslMarker + +/** + * The [FluidDsl] class provides a DSL for performing fluid search operations + * within a specified range in a Minecraft world. It allows for filtering fluids + * around a given position. + * + * @param safeContext The context in which the fluid search is performed, providing safe access to world data. + * @param kClass The class of the fluid type [T]. + * @param pos The position from which to start the fluid search. + * @param range The range around the position to search for fluids. + * @param step The step intervals at which to check for fluids. + * @param predicate The predicate to filter fluids. + * + * ### Usage Example: + * + * ```kotlin + * val fluids = fluidSearch(range = 8) { // Search for fluids within an 8 block radius + * it.isOf(Fluids.LAVA) // Filter out fluids that are not lava + * } + * + * fluids.forEach { (pos, state) -> + * println("Found still lava at $pos with state $state") + * } + * ``` + */ +@FluidDslMarker +class FluidDsl( + private val safeContext: SafeContext, + private val kClass: KClass, + pos: BlockPos, + private val range: Vec3i, + private val step: Vec3i, + private val predicate: (BlockPos, FluidState) -> Boolean +) { + private val fastVector = pos.toFastVec() + private val receiver: MutableMap = mutableMapOf() + + fun build(): Map { + safeContext.internalSearchFluids(kClass, fastVector, range.toFastVec(), step.toFastVec(), receiver, { pos, state -> predicate(pos.toBlockPos(), state) }, { _, _ -> }) + return receiver.mapKeys { it.key.toBlockPos() } + } +} + +/** + * Searches for fluids around the player's position and applies the specified fluid operations. + * + * @param pos The position around which to search for fluids. Defaults to the player's current position. + * @param range The `x`, `y`, `z` range around the position to search for fluids. + * @param step The `x`, `y`, `z` step intervals at which to check for fluids. + * @param predicate The predicate to filter fluids. + * @return A map of fluid positions and their states matching the predicate within the specified range. + */ +inline fun SafeContext.fluidSearch( + range: Vec3i, + step: Vec3i, + pos: BlockPos = player.blockPos, + noinline predicate: (BlockPos, FluidState) -> Boolean +): Map = FluidDsl(this, T::class, pos, range, step, predicate).build() + +/** + * Searches for fluids around the player's position and applies the specified fluid operations. + * + * @param pos The position around which to search for fluids. Defaults to the player's current position. + * @param range The range around the position to search for fluids. + * @param step The step intervals at which to check for fluids. + * @param predicate The predicate to filter fluids. + * @return A map of fluid positions and their states matching the predicate within the specified range. + */ +inline fun SafeContext.fluidSearch( + range: Int, + step: Int, + pos: BlockPos = player.blockPos, + noinline predicate: (BlockPos, FluidState) -> Boolean +): Map = fluidSearch(Vec3i(range, range, range), Vec3i(step, step, step), pos, predicate) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/world/Position.kt b/common/src/main/kotlin/com/lambda/util/world/Position.kt new file mode 100644 index 000000000..794ba630c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/world/Position.kt @@ -0,0 +1,233 @@ +package com.lambda.util.world + +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraft.util.math.Vec3i +import kotlin.math.sqrt + +/** + * Represents a position in the world encoded as a long. + * + * [ X (26 bits) | Z (26 bits) | Y (12 bits) ] + * + * The position is encoded as a 64-bit long where the X and Z coordinates are stored in the 26 most significant bits, + * and the Y coordinate is stored in the 12 least significant bits. + * This encoding allows for a maximum world size of ±33,554,432 blocks + * in the X and Z directions and ±2,048 blocks in the Y direction, which is more than needed. + */ +typealias FastVector = Long + +internal const val X_BITS = 26 +internal const val Z_BITS = 26 +internal const val Y_BITS = 12 + +internal const val X_SHIFT = Y_BITS + Z_BITS +internal const val Z_SHIFT = Y_BITS + +internal const val X_MASK = (1L shl X_BITS) - 1L +internal const val Z_MASK = (1L shl Z_BITS) - 1L +internal const val Y_MASK = (1L shl Y_BITS) - 1L + +internal const val MIN_X = -(1L shl X_BITS - 1) +internal const val MIN_Z = -(1L shl Z_BITS - 1) +internal const val MIN_Y = -(1L shl Y_BITS - 1) +internal const val MAX_X = (1L shl X_BITS - 1) - 1L +internal const val MAX_Z = (1L shl Z_BITS - 1) - 1L +internal const val MAX_Y = (1L shl Y_BITS - 1) - 1L + +/** + * Creates a new position from the given coordinates. + */ +fun fastVectorOf(x: Long, y: Long, z: Long): FastVector { + require(x in MIN_X..MAX_X) { "X coordinate out of bounds for $X_BITS bits: $x" } + require(y in MIN_Y..MAX_Y) { "Y coordinate out of bounds for $Y_BITS bits: $y" } + require(z in MIN_Z..MAX_Z) { "Z coordinate out of bounds for $Z_BITS bits: $z" } + + return ((x and X_MASK) shl X_SHIFT) or ((z and Z_MASK) shl Z_SHIFT) or (y and Y_MASK) +} + +/** + * Creates a new position from the given coordinates. + */ +fun fastVectorOf(x: Int, y: Int, z: Int): FastVector = fastVectorOf(x.toLong(), y.toLong(), z.toLong()) + +/** + * Gets the X coordinate from the position. + */ +val FastVector.x: Int + get() { + val x = (this shr X_SHIFT and X_MASK).toInt() + return if (x and (1 shl X_BITS - 1) != 0) x - (1 shl X_BITS) else x + } + +/** + * Gets the Z coordinate from the position. + */ +val FastVector.z: Int + get() { + val z = (this shr Z_SHIFT and Z_MASK).toInt() + return if (z and (1 shl Z_BITS - 1) != 0) z - (1 shl Z_BITS) else z + } + +/** + * Gets the Y coordinate from the position. + */ +val FastVector.y: Int + get() { + val y = (this and Y_MASK).toInt() + return if (y and (1 shl Y_BITS - 1) != 0) y - (1 shl Y_BITS) else y + } + +/** + * Sets the X coordinate of the position. + */ +infix fun FastVector.setX(x: Int): FastVector = bitSetTo(x.toLong(), X_SHIFT, X_BITS) + +/** + * Sets the Y coordinate of the position. + */ +infix fun FastVector.setY(y: Int): FastVector = bitSetTo(y.toLong(), 0, Y_BITS) + +/** + * Sets the Z coordinate of the position. + */ +infix fun FastVector.setZ(z: Int): FastVector = bitSetTo(z.toLong(), Z_SHIFT, Z_BITS) + +/** + * Adds the given value to the X coordinate. + */ +infix fun FastVector.addX(value: Int): FastVector = setX(x + value) + +/** + * Adds the given value to the Y coordinate. + */ +infix fun FastVector.addY(value: Int): FastVector = setY(y + value) + +/** + * Adds the given value to the Z coordinate. + */ +infix fun FastVector.addZ(value: Int): FastVector = setZ(z + value) + +/** + * Adds the given vector to the position. + */ +infix fun FastVector.plus(vec: FastVector): FastVector = fastVectorOf(x + vec.x, y + vec.y, z + vec.z) + +/** + * Adds the given vector to the position. + * @return The new position. + */ +infix fun FastVector.plus(vec: Vec3i): FastVector = fastVectorOf(x + vec.x, y + vec.y, z + vec.z) + +/** + * Adds the given vector to the position. + * @return The new position. + */ +infix fun FastVector.plus(vec: Vec3d): FastVector = fastVectorOf(x + vec.x.toLong(), y + vec.y.toLong(), z + vec.z.toLong()) + +/** + * Subtracts the given vector from the position. + */ +infix fun FastVector.minus(vec: FastVector): FastVector = fastVectorOf(x - vec.x, y - vec.y, z - vec.z) + +/** + * Subtracts the given vector from the position. + * @return The new position. + */ +infix fun FastVector.minus(vec: Vec3i): FastVector = fastVectorOf(x - vec.x, y - vec.y, z - vec.z) + +/** + * Subtracts the given vector from the position. + * @return The new position. + */ +infix fun FastVector.minus(vec: Vec3d): FastVector = fastVectorOf(x - vec.x.toLong(), y - vec.y.toLong(), z - vec.z.toLong()) + +/** + * Multiplies the position by the given scalar. + */ +infix fun FastVector.times(scalar: Int): FastVector = fastVectorOf(x * scalar, y * scalar, z * scalar) + +/** + * Multiplies the position by the given scalar. + */ +infix fun FastVector.times(scalar: Double): FastVector = fastVectorOf((x * scalar).toLong(), (y * scalar).toLong(), (z * scalar).toLong()) + +/** + * Divides the position by the given scalar. + */ +infix fun FastVector.div(scalar: Int): FastVector = fastVectorOf(x / scalar, y / scalar, z / scalar) + +/** + * Divides the position by the given scalar. + */ +infix fun FastVector.div(scalar: Double): FastVector = fastVectorOf((x / scalar).toLong(), (y / scalar).toLong(), (z / scalar).toLong()) + +/** + * Modulo the position by the given scalar. + */ +infix fun FastVector.mod(scalar: Int): FastVector = fastVectorOf(x % scalar, y % scalar, z % scalar) + +/** + * Modulo the position by the given scalar. + */ +infix fun FastVector.mod(scalar: Double): FastVector = fastVectorOf((x % scalar).toLong(), (y % scalar).toLong(), (z % scalar).toLong()) + +/** + * Returns the squared distance between this position and the other. + */ +infix fun FastVector.distSq(other: FastVector): Double { + val dx = x - other.x + val dy = y - other.y + val dz = z - other.z + return (dx * dx + dy * dy + dz * dz).toDouble() +} + +/** + * Returns the squared distance between this position and the Vec3i. + */ +infix fun FastVector.distSq(other: Vec3i): Double { + val dx = x - other.x + val dy = y - other.y + val dz = z - other.z + return (dx * dx + dy * dy + dz * dz).toDouble() +} + +/** + * Returns the squared distance between this position and the Vec3d. + */ +infix fun FastVector.distSq(other: Vec3d): Double { + val dx = x - other.x.toLong() + val dy = y - other.y.toLong() + val dz = z - other.z.toLong() + return (dx * dx + dy * dy + dz * dz).toDouble() +} + +/** + * Converts a [Vec3i] to a [FastVector]. + * @return The encoded position. + */ +fun Vec3i.toFastVec(): FastVector = fastVectorOf(x.toLong(), y.toLong(), z.toLong()) + +/** + * Converts a [Vec3d] to a [FastVector]. + * @return The encoded position. + */ +fun Vec3d.toFastVec(): FastVector = fastVectorOf(x.toLong(), y.toLong(), z.toLong()) + +/** + * Converts the [FastVector] into a [Vec3d]. + */ +fun FastVector.toVec3d(): Vec3d = Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) + +/** + * Converts the [FastVector] into a [BlockPos]. + */ +fun FastVector.toBlockPos(): BlockPos = BlockPos(x, y, z) + +/** + * Sets n bits to a value at a given position. + */ +internal fun Long.bitSetTo(value: Long, position: Int, length: Int): Long { + val mask = (1L shl length) - 1L + return this and (mask shl position).inv() or (value and mask shl position) +} diff --git a/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index 4cd0e1c6a..542a89111 100644 --- a/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -1,26 +1,24 @@ package com.lambda.util.world import com.lambda.context.SafeContext -import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.BlockUtils.blockState +import com.lambda.core.annotations.InternalApi import com.lambda.util.collections.filterPointer -import net.minecraft.block.Block +import com.lambda.util.primitives.extension.getBlockState +import com.lambda.util.primitives.extension.getFluidState import net.minecraft.block.BlockState import net.minecraft.entity.Entity import net.minecraft.fluid.Fluid import net.minecraft.fluid.FluidState -import net.minecraft.util.math.BlockPos import net.minecraft.util.math.ChunkSectionPos -import net.minecraft.util.math.Vec3d -import net.minecraft.util.math.Vec3i import kotlin.math.ceil +import kotlin.reflect.KClass /** * Utility functions for working with the Minecraft world. * * This object employs a pass-by-reference model, allowing functions to modify * data structures passed to them rather than creating new ones. This approach - * offers 2 main benefits: + * offers two main benefits: * * - **Performance**: Pass-by-reference avoids unnecessary memory allocations * and reallocations that can occur when creating new data structures. @@ -31,52 +29,24 @@ import kotlin.math.ceil * temporary objects. * * When you create a new object, the JVM allocates memory for it on the heap. - * When it is no longer needed, the garbage collector frees up the memory. + * When it is no longer necessary, the garbage collector frees up the memory. * This process **IS** expensive, especially if you are creating and discarding many objects - * in a short period of time, for example, when retrieving all the 200 pigs in your farm every game tick. + * + * Please note that the author of this code currently does not have any certifications in the field of computer science. + * Simply plain old experience and knowledge. * * @see IBM - Pass By Reference * @see Florida State University - Pass By Reference vs. Pass By Value * @see IBM - Garbage Collection Impacts on Java Performance * @see Medium - GC and Its Effect on Java Performance - * - * Many functions uses a branching approach to avoid trolling the speculative execution of the CPU. - * @see Branch Predictor - * @see Speculative Execution */ object WorldUtils { /** - * Gets the closest entity of type [T] within a specified range. - * - * Because we don't want to troll the CPU speculative execution, we only use the [getFastEntities] function. - * This should not be an issue as the performance of this function is optimized for small distances. - * - * @param pos The position to search from. - * @param range The maximum distance to search for entities. - * @param predicate Predicate to filter entities. - * @return The first entity of type [T] that is closest to the position within the specified range. + * A magic vector that can be used to represent a single block. + * It is the same as `fastVectorOf(1, 1, 1)`. */ - inline fun SafeContext.getClosestEntity( - pos: Vec3d, - range: Double, - predicate: (T) -> Boolean = { true }, - type: Class = T::class.java, - ): T? { - var closest: T? = null - var closestDistance = Double.MAX_VALUE - - val comparator = { entity: T, _: Int -> - val distance = pos.squaredDistanceTo(entity.pos) - if (distance < closestDistance) { - closest = entity - closestDistance = distance - } - } - - getFastEntities(pos, range, null, comparator, predicate) - - return closest - } + @InternalApi + const val MAGICVECTOR = 274945015809L /** * Gets all entities of type [T] within a specified distance from a position. @@ -84,40 +54,45 @@ object WorldUtils { * This function retrieves entities of type [T] within a specified distance from a given position. It efficiently * queries nearby chunks based on the distance and returns a list of matching entities, excluding the player entity. * - * - * Getting all Zombie entities within a certain distance: - * ``` - * val nearbyZombies = getFastEntities(playerPos, 20.0) - * ``` - * - * Getting all hostile entities within a certain distance: + * Examples: + * - Getting all hostile entities within a certain distance: * ``` - * val hostileEntities = getFastEntities(playerPos, 30.0) + * val hostileEntities = mutableListOf() + * getFastEntities(player.pos, 30.0, hostileEntities) * ``` - * This fetches all hostile entities (e.g., Monsters) within a 30-block radius from the player's position. * - * Please note that this implementation is optimized for performance at small distances. For larger distances, it is - * recommended to use the [getEntities] function instead. - * With the time complexity, we can determine that after 64 blocks, the performance of this function will degrade. + * Please note that this implementation is optimized for performance at small distances. + * For larger distances, it is recommended to use the [internalGetEntities] function instead. + * With the time complexity, we can determine that the performance of this function will degrade after 64 blocks. * * @param pos The position to search from. * @param distance The maximum distance to search for entities. * @param pointer The mutable list to store the entities in. - * @param iterator Iterator to perform operations on each entity. + * @param iterator Iterator to perform operations on each entity. The second parameter is the index of the iteration. * @param predicate Predicate to filter entities. */ - inline fun SafeContext.getFastEntities( - pos: Vec3d, + @InternalApi + inline fun SafeContext.internalGetFastEntities( + pos: FastVector, + distance: Double, + pointer: MutableList? = null, + predicate: (T) -> Boolean = { true }, + iterator: (T) -> Unit = { _ -> }, + ) = internalGetFastEntities(T::class, pos, distance, pointer, predicate, iterator) + + @InternalApi + inline fun SafeContext.internalGetFastEntities( + kClass: KClass, + pos: FastVector, distance: Double, pointer: MutableList? = null, - iterator: (T, Int) -> Unit = { _, _ -> }, predicate: (T) -> Boolean = { true }, - type: Class = T::class.java, + iterator: (T) -> Unit = { _ -> }, ) { - val chunks = ceil(distance / 16).toInt() - val sectionX = pos.x.toInt() shr 4 - val sectionY = pos.y.toInt() shr 4 - val sectionZ = pos.z.toInt() shr 4 + val chunks = ceil(distance / 16.0).toInt() + val sectionX = pos.x shr 4 + val sectionY = pos.y shr 4 + val sectionZ = pos.z shr 4 // Here we iterate over all sections within the specified distance and add all entities of type [T] to the list. // We do not have to worry about performance here, as the number of sections is very limited. @@ -130,9 +105,9 @@ object WorldUtils { .cache .findTrackingSection(ChunkSectionPos.asLong(x, y, z)) ?: continue - section.collection.filterPointer(pointer, iterator) { entity -> + section.collection.filterPointer(kClass, pointer, iterator) { entity -> entity != player && - entity.squaredDistanceTo(pos) <= distance * distance && + pos distSq entity.pos <= distance * distance && predicate(entity) } } @@ -144,48 +119,39 @@ object WorldUtils { * Gets all entities of type [T] within a specified distance from a position. * * This function retrieves entities of type [T] within a specified distance from a given position. Unlike - * [getFastEntities], it traverses all entities in the world to find matches, while also excluding the player entity. + * [internalGetFastEntities], it traverses all entities in the world to find matches, while also excluding the player entity. * + * @param pos The block position to search from. + * @param distance The maximum distance to search for entities. * @param pointer The mutable list to store the entities in. - * @param iterator Iterator to perform operations on each entity. + * @param iterator Iterator to perform operations on each entity. The second parameter is the index of the iteration. * @param predicate Predicate to filter entities. */ - inline fun SafeContext.getEntities( - pos: Vec3d, + @InternalApi + inline fun SafeContext.internalGetEntities( + pos: FastVector, + distance: Double, + pointer: MutableList? = null, + predicate: (T) -> Boolean = { true }, + iterator: (T) -> Unit = { _ -> }, + ) = internalGetEntities(T::class, pos, distance, pointer, predicate, iterator) + + @InternalApi + inline fun SafeContext.internalGetEntities( + kClass: KClass, + pos: FastVector, distance: Double, pointer: MutableList? = null, - iterator: (T, Int) -> Unit = { _, _ -> }, predicate: (T) -> Boolean = { true }, - type: Class = T::class.java, + iterator: (T) -> Unit = { _ -> }, ) { - world.entities.filterPointer(pointer, iterator) { entity -> + world.entities.filterPointer(kClass, pointer, iterator) { entity -> entity != player && - entity.squaredDistanceTo(pos) <= distance * distance && + pos distSq entity.pos <= distance * distance && predicate(entity) } } - /** - * Returns all the blocks and positions within the range where the predicate is true. - * - * @param pos The position to search from. - * @param rangeX The maximum distance to search for entities in the x-axis. - * @param rangeY The maximum distance to search for entities in the y-axis. - * @param rangeZ The maximum distance to search for entities in the z-axis. - * @param pointer The mutable map to store the positions to blocks in. - * @param predicate Predicate to filter the blocks. - * @param iterator Iterator to perform operations on each block. - */ - inline fun SafeContext.searchBlocks( - pos: Vec3i, - rangeX: Int, - rangeY: Int, - rangeZ: Int, - pointer: MutableMap? = null, - predicate: (BlockState, BlockPos) -> Boolean = { _, _ -> true }, - iterator: (BlockState, BlockPos, Int) -> Unit = { _, _, _ -> }, - ) = searchBlocks(pos, Vec3i(rangeX, rangeY, rangeZ), pointer, predicate, iterator) - /** * Returns all the blocks and positions within the range where the predicate is true. * @@ -195,23 +161,22 @@ object WorldUtils { * @param iterator Iterator to perform operations on each block. * @param predicate Predicate to filter the blocks. */ - inline fun SafeContext.searchBlocks( - pos: Vec3i, - range: Vec3i, - pointer: MutableMap? = null, - predicate: (BlockState, BlockPos) -> Boolean = { _, _ -> true }, - iterator: (BlockState, BlockPos, Int) -> Unit = { _, _, _ -> }, + @InternalApi + inline fun SafeContext.internalSearchBlocks( + pos: FastVector, + range: FastVector = MAGICVECTOR times 7, + step: FastVector = MAGICVECTOR, + pointer: MutableMap? = null, + predicate: (FastVector, BlockState) -> Boolean = { _, _ -> true }, + iterator: (FastVector, BlockState) -> Unit = { _, _ -> }, ) { - iteratePositions(pos, range) { blockPos, index -> - val state = blockPos.blockState(world) - when { - predicate(state, blockPos) && pointer != null -> { - pointer[blockPos] = state.block - iterator(state, blockPos, index) - } + internalIteratePositions(pos, range, step) { position -> + world.getBlockState(position).let { state -> + val fulfilled = predicate(position, state) - predicate(state, blockPos) && pointer == null -> { - iterator(state, blockPos, index) + if (fulfilled && pointer != null) { + pointer[position] = state + iterator(position, state) } } } @@ -226,23 +191,34 @@ object WorldUtils { * @param iterator Iterator to perform operations on each fluid. * @param predicate Predicate to filter the fluids. */ - inline fun SafeContext.searchFluids( - pos: Vec3i, - range: Vec3i, - pointer: MutableMap? = null, - iterator: (FluidState, BlockPos, Int) -> Unit = { _, _, _ -> }, - predicate: (FluidState, BlockPos) -> Boolean = { _, _ -> true }, + @InternalApi + inline fun SafeContext.internalSearchFluids( + pos: FastVector, + range: FastVector = MAGICVECTOR times 7, + step: FastVector = MAGICVECTOR, + pointer: MutableMap? = null, + predicate: (FastVector, FluidState) -> Boolean = { _, _ -> true }, + iterator: (FastVector, FluidState) -> Unit = { _, _ -> }, + ) = internalSearchFluids(T::class, pos, range, step, pointer, predicate, iterator) + + @InternalApi + inline fun SafeContext.internalSearchFluids( + kClass: KClass, + pos: FastVector, + range: FastVector = MAGICVECTOR times 7, + step: FastVector = MAGICVECTOR, + pointer: MutableMap? = null, + predicate: (FastVector, FluidState) -> Boolean = { _, _ -> true }, + iterator: (FastVector, FluidState) -> Unit = { _, _ -> }, ) { - iteratePositions(pos, range) { blockPos, index -> - val state = world.getFluidState(blockPos) - when { - predicate(state, blockPos) && pointer != null -> { - iterator(state, blockPos, index) - pointer[blockPos] = state.fluid as T - } + internalIteratePositions(pos, range, step) { position -> + world.getFluidState(position.x, position.y, position.z).let { state -> + val fulfilled = kClass.isInstance(state.fluid) && predicate(position, state) + + if (fulfilled && pointer != null) { + pointer[position] = state.fluid as T - predicate(state, blockPos) && pointer == null -> { - iterator(state, blockPos, index) + iterator(position, state) } } } @@ -252,16 +228,25 @@ object WorldUtils { * Iterates over all positions within the specified range. * @param pos The position to start from. * @param range The maximum distance to search for entities in each axis. + * @param step The step to increment the position by. * @param iterator Iterator to perform operations on each position. */ - inline fun iteratePositions( - pos: Vec3i, - range: Vec3i, - iterator: (BlockPos, Int) -> Unit, + @InternalApi + inline fun internalIteratePositions( + pos: FastVector, + range: FastVector, + step: FastVector, + iterator: (FastVector) -> Unit = { _ -> }, ) { - BlockPos.iterateOutwards(pos.blockPos, range.x, range.y, range.z) - .forEachIndexed { index, blockPos -> - iterator(blockPos, index) + for (x in -range.x..range.x step step.x) { + for (y in -range.y..range.y step step.y) { + for (z in -range.z..range.z step step.z) { + iterator( + pos plus fastVectorOf(x, y, z), + ) + } } + } } } +