diff --git a/common/src/main/kotlin/com/lambda/module/modules/EntityTest.kt b/common/src/main/kotlin/com/lambda/module/modules/EntityTest.kt new file mode 100644 index 000000000..df224646a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/EntityTest.kt @@ -0,0 +1,21 @@ +package com.lambda.module.modules + +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.module.Module +import com.lambda.util.world.EntityUtils.getClosestEntity +import net.minecraft.entity.Entity + +object EntityTest : Module( + name = "EntityTest", + description = "Test entity", + defaultTags = setOf() +) { + init { + listener { + repeat(10000) { + getClosestEntity(player.eyePos, 7.0) + } + } + } +} 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 new file mode 100644 index 000000000..3a123265a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -0,0 +1,70 @@ +package com.lambda.module.modules.combat + +import com.lambda.config.InteractionSettings +import com.lambda.config.RotationSettings +import com.lambda.event.events.RotationEvent +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.Communication.info +import com.lambda.util.combat.Explosion.velocity +import com.lambda.util.world.EntityUtils.getClosestEntity +import com.lambda.util.world.EntityUtils.getFastEntities +import net.minecraft.entity.Entity +import net.minecraft.entity.LivingEntity +import net.minecraft.util.Hand +import net.minecraft.world.explosion.Explosion + +object CrystalAura : Module( + name = "CrystalAura", + description = "Automatically attacks entities with crystals", + defaultTags = setOf(ModuleTag.COMBAT), +) { + private val page by setting("Page", Page.General) + + /* Rotation */ + private val rotation = RotationSettings(this) { page == Page.Targeting } + + /* Placing */ + private val swap by setting("Swap", Hand.MAIN_HAND, "Automatically swap to crystals") { page == Page.Placing } + private val multiPlace by setting("Multi Place", true, "Place multiple crystals") { page == Page.Placing } + private val placeDelay by setting("Place Delay", 0, 0..20, 1, "Delay between crystal placements", unit = "ticks", visibility = { page == Page.Placing }) + private val placeRange by setting("Place Range", 5.0, 0.1..7.0, 0.1, "Range to place crystals from the player eyes", visibility = { page == Page.Placing }) + private val placeRangeWalls by setting("Place Range Walls", 3.5, 0.1..7.0, 0.1, "Range to place crystals through walls", visibility = { page == Page.Placing }) + private val placeMinHealth by setting("Place Min Health", 10.0, 0.0..20.0, 0.5, "Minimum health to place a crystal", visibility = { page == Page.Placing }) + private val placeMaxSelfDamage by setting("Place Max Self Damage", 8.0, 0.0..20.0, 0.5, "Maximum self damage to place a crystal", visibility = { page == Page.Placing }) + private val placeMinDamage by setting("Place Min Damage", 6.0, 0.0..20.0, 0.5, "Minimum damage to place a crystal", visibility = { page == Page.Placing }) + + /* Exploding */ + private val explode by setting("Explode", true, "Explode crystals") { page == Page.Exploding } + private val explodeDelay by setting("Explode Delay", 0, 0..20, 1, "Delay between crystal explosions", unit = "ticks", visibility = { page == Page.Exploding }) + private val explodeRange by setting("Explode Range", 5.0, 0.1..7.0, 0.1, "Range to explode crystals", visibility = { page == Page.Exploding }) + private val explodeRangeWalls by setting("Explode Range Walls", 3.5, 0.1..7.0, 0.1, "Range to explode crystals through walls", visibility = { page == Page.Exploding }) + private val preventDeath by setting("Prevent Death", true, "Prevent death from crystal explosions", visibility = { page == Page.Exploding }) + private val explodeMinDamage by setting("Explode Min Damage", 6.0, 0.0..20.0, 0.5, "Minimum damage to explode a crystal", visibility = { page == Page.Exploding }) + private val noWeakness by setting("No Weakness", true, "Switch to a weapon when you have a weakness effect", visibility = { page == Page.Exploding }) + + /* Rendering */ + + + /* Interaction */ + private val interac = InteractionSettings(this) // Canadian interbank meme + + private enum class Page { + General, Targeting, Placing, Exploding, Rendering + } + + init { + concurrentListener {} + + /*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/util/collections/Extensions.kt b/common/src/main/kotlin/com/lambda/util/collections/Extensions.kt new file mode 100644 index 000000000..4d938d1c7 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/collections/Extensions.kt @@ -0,0 +1,21 @@ +package com.lambda.util.collections + +/** + * 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 predicate The predicate function that determines whether an element should be included based on its type and other criteria. + */ +inline fun > Iterable<*>.filterIsInstanceTo( + destination: C, + predicate: (R) -> Boolean +) { + for (element in this) if (element is R && predicate(element)) destination.add(element) +} diff --git a/common/src/main/kotlin/com/lambda/util/combat/Damage.kt b/common/src/main/kotlin/com/lambda/util/combat/Damage.kt new file mode 100644 index 000000000..e3c31c1c1 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/combat/Damage.kt @@ -0,0 +1,33 @@ +package com.lambda.util.combat + +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.damage.DamageSource +import net.minecraft.entity.effect.StatusEffects +import net.minecraft.registry.tag.DamageTypeTags +import kotlin.math.max +import kotlin.math.min + +object Damage { + /** + * @param entity The entity to calculate the damage for + * @param damage The damage to apply + * @return The damage dealt by the explosion + */ + fun mask(entity: LivingEntity, damage: Double, source: DamageSource): Double { + val resistanceAmplifier = entity.getStatusEffect(StatusEffects.RESISTANCE)?.amplifier ?: -1 + + if (source.isIn(DamageTypeTags.BYPASSES_EFFECTS)) return damage + + if (entity.hasStatusEffect(StatusEffects.RESISTANCE) && !source.isIn(DamageTypeTags.BYPASSES_RESISTANCE)) + return (damage - max(damage * (25 - (resistanceAmplifier + 1) * 5) / 25.0, 0.0)).coerceAtLeast(0.0) + + if (source.isIn(DamageTypeTags.BYPASSES_ENCHANTMENTS)) return damage + + val protectionAmount = EnchantmentHelper.getProtectionAmount(entity.armorItems, source) + + if (protectionAmount > 0) return damage * (1.0 - min(protectionAmount, 20) / 25.0) + + return damage + } +} diff --git a/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt b/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt new file mode 100644 index 000000000..4f1f3950d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/combat/Explosion.kt @@ -0,0 +1,131 @@ +package com.lambda.util.combat + +import com.lambda.context.SafeContext +import com.lambda.util.math.VecUtils.minus +import com.lambda.util.math.VecUtils.times +import com.lambda.util.world.EntityUtils.getFastEntities +import net.minecraft.enchantment.ProtectionEnchantment +import net.minecraft.entity.LivingEntity +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraft.world.explosion.Explosion +import kotlin.math.max + +object Explosion { + /** + * Calculates the damage dealt by an explosion to a living entity. + * @param source The source of the explosion. + * @param entity The entity to calculate the damage for. + * @return The damage dealt by the explosion. + */ + fun SafeContext.damage(source: Explosion, entity: LivingEntity) = + damage(source.position, entity, source.power.toDouble()) + + /** + * Calculates the damage dealt by an explosion to a living entity. + * @param position The position of the explosion. + * @param entity The entity to calculate the damage for. + * @param power The strength of the explosion above 0. + * @return The damage dealt by the explosion. + */ + fun SafeContext.damage(position: Vec3d, entity: LivingEntity, power: Double): Double { + val distance = entity.pos.distanceTo(position) + + val impact = (1.0 - distance / (power * 2.0)) * + Explosion.getExposure(position, entity) * + 0.4 + + val damage = world.difficulty.id * 3 * + power * + (impact * impact + impact) + 1 + + return Damage.mask(entity, damage, Explosion.createDamageSource(world, null)) + } + + /** + * Calculates the velocity of entities in the explosion. + * @param explosion The explosion to calculate the velocity for. + * @return The velocity of the entities. + */ + fun SafeContext.velocity(explosion: Explosion) = + getFastEntities(explosion.position, explosion.power * 2.0) + .associateWith { entity -> velocity(entity, explosion) } + + /** + * Calculates the velocity of a living entity affected by an explosion. + * @param entity The entity to calculate the velocity for. + * @param explosion The explosion to calculate the velocity for. + * @return The velocity of the entity. + */ + fun SafeContext.velocity(entity: LivingEntity, explosion: Explosion) = + velocity(entity, explosion.position, explosion.power.toDouble()) + + /** + * Calculates the velocity of a living entity affected by an explosion. + * @param entity The entity to calculate the velocity for. + * @param position The position of the explosion. + * @param power The strength of the explosion. + * @return The velocity of the entity. + */ + fun SafeContext.velocity(entity: LivingEntity, position: Vec3d, power: Double): Vec3d { + val distance = entity.pos.distanceTo(position) + + val size = power * 2.0 + val vel = ProtectionEnchantment.transformExplosionKnockback( + entity, + (1.0 - distance / size) * Explosion.getExposure(position, entity) + ) + + val diff = entity.eyePos - position + return diff.normalize() * vel + } + + fun SafeContext.destruction(source: Explosion): List { + val affected = mutableListOf() + + repeat(16) { x -> + repeat(16) { y -> + repeat(16) { z -> + if (x == 0 || x == 15 || y == 0 || y == 15 || z == 0 || z == 15) { + val vec = Vec3d(x / 30.0 - 1.0, y / 30.0 - 1.0, z / 30.0 - 1.0) + val len = vec.length() + + val dx = vec.x / len + val dy = vec.y / len + val dz = vec.z / len + + var explosionX = source.position.x + var explosionY = source.position.y + var explosionZ = source.position.z + + var intensity = source.power * (0.7 + world.random.nextDouble() * 0.6) + + while (intensity > 0) { + val blockPos = BlockPos.ofFloored(explosionX, explosionY, explosionZ) + val block = world.getBlockState(blockPos) + val fluid = world.getFluidState(blockPos) + if (!world.isInBuildLimit(blockPos)) { + break + } + + val resistance = max(block.block.blastResistance, fluid.blastResistance) + intensity -= (resistance + 0.3) * 0.3 + + if (intensity > 0 && source.behavior.canDestroyBlock(source, world, blockPos, block, intensity.toFloat())) { + affected.add(Vec3d(explosionX, explosionY, explosionZ)) + } + + explosionX += dx * 0.3 + explosionY += dy * 0.3 + explosionZ += dz * 0.3 + + intensity -= 0.225 + } + } + } + } + } + + return affected + } +} diff --git a/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt b/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt index 3072ec978..1cbd18e6a 100644 --- a/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt @@ -1,42 +1,70 @@ package com.lambda.util.world import com.lambda.context.SafeContext +import com.lambda.util.collections.filterIsInstanceTo import net.minecraft.entity.Entity import net.minecraft.util.math.ChunkSectionPos import net.minecraft.util.math.Vec3d import kotlin.math.ceil -/** - * Utility class for working with entities in a Minecraft environment. - */ object EntityUtils { - - // TODO: Tick cache implementation - /** * Gets the closest entity of type [T] within a specified range. * * @param pos The position to search from. * @param range The maximum distance to search for entities. * @param predicate Optional predicate to filter entities. - * @return The closest entity of type [T] within the specified range, or null if none is found. + * @return The first entity of type [T] that is closest to the position within the specified range. */ inline fun SafeContext.getClosestEntity( - pos: Vec3d, - range: Double, + pos: Vec3d = player.pos, + range: Double = 6.0, noinline predicate: (T) -> Boolean = { true }, ): T? { - return getFastEntities(pos, range, predicate).firstOrNull { it.pos.squaredDistanceTo(pos) <= range * range } + // Speculative execution trolling + val entities = + if (range > 64) getEntities(predicate) + // I have an idea for optimization. + // + // Since the search operates linearly, eventually it will reach the midpoint. + // Calculate the distance between the first and last entities. + // Obtain the delta value. + // Theoretically, the closest entity should be within a cubic space of delta^3 blocks. + // If there are no entities within this delta box, examine the outer box. (Although this is unlikely given the fact that the closest entity is within the delta box.) + // The performance improvement is relative to the initial state. + else getFastEntities(pos, range, predicate) + + return entities.minByOrNull { it.squaredDistanceTo(pos) } } /** * 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. 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: + * ``` + * val hostileEntities = getFastEntities(playerPos, 30.0) + * ``` + * 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. + * * @param pos The position to search from. * @param distance The maximum distance to search for entities. - * @param predicate Optional predicate to filter entities. - * @return A list of entities of type [T] within the specified distance from the position. + * @param predicate Optional predicate to filter entities. It allows custom filtering based on entity properties. + * @return A list of entities of type [T] within the specified distance from the position, excluding the player. + * */ inline fun SafeContext.getFastEntities( pos: Vec3d, @@ -48,7 +76,7 @@ object EntityUtils { val sectionY = pos.y.toInt() shr 4 val sectionZ = pos.z.toInt() shr 4 - val entities = mutableListOf() + val entities = ArrayList() // 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. @@ -57,11 +85,32 @@ object EntityUtils { for (y in sectionY - chunks..sectionY + chunks) { for (z in sectionZ - chunks..sectionZ + chunks) { val section = world.entityManager.cache.findTrackingSection(ChunkSectionPos.asLong(x, y, z)) ?: continue - entities.addAll(section.collection.getAllOfType(T::class.java).filter(predicate)) + section.collection.filterIsInstanceTo(entities) { entity -> + entity != player && entity.squaredDistanceTo(pos) <= distance * distance && predicate(entity) + } } } } return entities } + + /** + * 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. + * + * @param predicate Optional predicate to filter entities. + * @return A list of entities of type [T] within the specified distance from the position without the player. + */ + inline fun SafeContext.getEntities(noinline predicate: (T) -> Boolean = { true }): List { + val entities = ArrayList() + + world.entities.filterIsInstanceTo(entities) { entity -> + entity != player && predicate(entity) + } + + return entities + } } diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index f0e1dbcf9..e95982c8a 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -37,3 +37,4 @@ accessible method net/minecraft/text/Style (Lnet/minecraft/text/TextColor # Other accessible field net/minecraft/client/world/ClientEntityManager cache Lnet/minecraft/world/entity/SectionedEntityCache; accessible field net/minecraft/world/entity/EntityTrackingSection collection Lnet/minecraft/util/collection/TypeFilterableList; +accessible field net/minecraft/world/explosion/Explosion behavior Lnet/minecraft/world/explosion/ExplosionBehavior;