diff options
author | Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> | 2024-04-03 20:50:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-03 20:50:31 +0200 |
commit | 2f85351bacddb9ab3704a53c778d558a755bcc06 (patch) | |
tree | f479045f271f04a66a1ba69a61cb1b9893261eb5 /src/main/java/at/hannibal2/skyhanni/utils | |
parent | 76be6ad6de39c7078550394e8ec24a494ddb3bcc (diff) | |
download | skyhanni-2f85351bacddb9ab3704a53c778d558a755bcc06.tar.gz skyhanni-2f85351bacddb9ab3704a53c778d558a755bcc06.tar.bz2 skyhanni-2f85351bacddb9ab3704a53c778d558a755bcc06.zip |
Backend: Mob Detection (#712)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Co-authored-by: Cal <cwolfson58@gmail.com>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils')
5 files changed, 204 insertions, 1 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt index 21f731fc2..d37ddad09 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt @@ -139,4 +139,28 @@ object CollectionUtils { fun <K, V : Comparable<V>> Map<K, V>.sortedDesc(): Map<K, V> { return toList().sorted().reversed().toMap() } + + inline fun <reified T> ConcurrentLinkedQueue<T>.drainForEach(action: (T) -> Unit) { + while (true) { + val value = this.poll() ?: break + action(value) + } + } + + fun <T> Sequence<T>.takeWhileInclusive(predicate: (T) -> Boolean) = sequence { + with(iterator()) { + while (hasNext()) { + val next = next() + yield(next) + if (!predicate(next)) break + } + } + } + + /** Updates a value if it is present in the set (equals), useful if the newValue is not reference equal with the value in the set */ + inline fun <reified T> MutableSet<T>.refreshReference(newValue: T) = if (this.contains(newValue)) { + this.remove(newValue) + this.add(newValue) + true + } else false } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt index b2ffc2826..c9b90d738 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt @@ -1,10 +1,14 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.events.SkyHanniRenderEntityEvent +import at.hannibal2.skyhanni.data.mob.MobFilter.isRealPlayer import at.hannibal2.skyhanni.utils.ItemUtils.getSkullTexture import at.hannibal2.skyhanni.utils.LocationUtils.canBeSeen import at.hannibal2.skyhanni.utils.LocationUtils.distanceTo +import at.hannibal2.skyhanni.utils.LocationUtils.distanceToIgnoreY import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth +import at.hannibal2.skyhanni.utils.LorenzUtils.derpy +import at.hannibal2.skyhanni.utils.StringUtils.removeColor import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft import net.minecraft.client.entity.EntityOtherPlayerMP @@ -147,6 +151,9 @@ object EntityUtils { inline fun <reified T : Entity> getEntitiesNearby(location: LorenzVec, radius: Double): Sequence<T> = getEntities<T>().filter { it.distanceTo(location) < radius } + inline fun <reified T : Entity> getEntitiesNearbyIgnoreY(location: LorenzVec, radius: Double): Sequence<T> = + getEntities<T>().filter { it.distanceToIgnoreY(location) < radius } + fun EntityLivingBase.isAtFullHealth() = baseMaxHealth == health.toInt() fun EntityArmorStand.hasSkullTexture(skin: String): Boolean { @@ -154,7 +161,7 @@ object EntityUtils { return inventory.any { it != null && it.getSkullTexture() == skin } } - fun EntityPlayer.isNPC() = uniqueID == null || uniqueID.version() != 4 + fun EntityPlayer.isNPC() = !isRealPlayer() fun EntityLivingBase.hasPotionEffect(potion: Potion) = getActivePotionEffect(potion) != null @@ -212,6 +219,12 @@ object EntityUtils { event.cancel() } } + + fun EntityLivingBase.isCorrupted() = baseMaxHealth == health.toInt().derpy() * 3 || isRunicAndCorrupt() + fun EntityLivingBase.isRunic() = baseMaxHealth == health.toInt().derpy() * 4 || isRunicAndCorrupt() + fun EntityLivingBase.isRunicAndCorrupt() = baseMaxHealth == health.toInt().derpy() * 3 * 4 + + fun Entity.cleanName() = this.name.removeColor() } private fun Event.cancel() { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LocationUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LocationUtils.kt index 29cc1d569..ca651ea08 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LocationUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LocationUtils.kt @@ -3,6 +3,9 @@ package at.hannibal2.skyhanni.utils import net.minecraft.client.Minecraft import net.minecraft.entity.Entity import net.minecraft.util.AxisAlignedBB +import net.minecraft.util.BlockPos +import kotlin.math.max +import kotlin.math.min object LocationUtils { @@ -22,6 +25,9 @@ object LocationUtils { fun Entity.distanceToPlayer() = getLorenzVec().distanceToPlayer() fun Entity.distanceTo(location: LorenzVec) = getLorenzVec().distance(location) + fun Entity.distanceTo(other: Entity) = getLorenzVec().distance(other.getLorenzVec()) + + fun Entity.distanceToIgnoreY(location: LorenzVec) = getLorenzVec().distanceIgnoreY(location) fun playerEyeLocation(): LorenzVec { val player = Minecraft.getMinecraft().thePlayer @@ -41,4 +47,53 @@ object LocationUtils { val inFov = true // TODO add Frustum "Frustum().isBoundingBoxInFrustum(entity.entityBoundingBox)" return noBlocks && notTooFar && inFov } + + fun AxisAlignedBB.minBox() = LorenzVec(minX, minY, minZ) + + fun AxisAlignedBB.maxBox() = LorenzVec(maxX, maxY, maxZ) + + fun AxisAlignedBB.rayIntersects(origin: LorenzVec, direction: LorenzVec): Boolean { + // Reference for Algorithm https://tavianator.com/2011/ray_box.html + val rayDirectionInverse = direction.inverse() + val t1 = (this.minBox().subtract(origin)).multiply(rayDirectionInverse) + val t2 = (this.maxBox().subtract(origin)).multiply(rayDirectionInverse) + + val tmin = max(t1.minOfEachElement(t2).max(), Double.NEGATIVE_INFINITY) + val tmax = min(t1.maxOfEachElement(t2).min(), Double.POSITIVE_INFINITY) + return tmax >= tmin && tmax >= 0.0 + } + + fun AxisAlignedBB.union(aabbs: List<AxisAlignedBB>?): AxisAlignedBB? { + if (aabbs.isNullOrEmpty()) { + return null + } + + var minX = this.minX + var minY = this.minY + var minZ = this.minZ + var maxX = this.maxX + var maxY = this.maxY + var maxZ = this.maxZ + + aabbs.forEach { aabb -> + if (aabb.minX < minX) minX = aabb.minX + if (aabb.minY < minY) minY = aabb.minY + if (aabb.minZ < minZ) minZ = aabb.minZ + if (aabb.maxX > maxX) maxX = aabb.maxX + if (aabb.maxY > maxY) maxY = aabb.maxY + if (aabb.maxZ > maxZ) maxZ = aabb.maxZ + } + + val combinedMin = BlockPos(minX, minY, minZ) + val combinedMax = BlockPos(maxX, maxY, maxZ) + + return AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ) + } + + fun AxisAlignedBB.getEdgeLengths() = this.maxBox().subtract(this.minBox()) + + fun AxisAlignedBB.getCenter() = this.getEdgeLengths().multiply(0.5).add(this.minBox()) + + fun AxisAlignedBB.getTopCenter() = this.getCenter().add(y = (maxY - minY) / 2) } + diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt index 77021e977..534b54172 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt @@ -7,7 +7,11 @@ import net.minecraft.util.AxisAlignedBB import net.minecraft.util.BlockPos import net.minecraft.util.Rotations import net.minecraft.util.Vec3 +import kotlin.math.abs +import kotlin.math.acos import kotlin.math.cos +import kotlin.math.max +import kotlin.math.min import kotlin.math.pow import kotlin.math.round import kotlin.math.sin @@ -18,6 +22,7 @@ data class LorenzVec( val y: Double, val z: Double, ) { + constructor() : this(0.0, 0.0, 0.0) constructor(x: Int, y: Int, z: Int) : this(x.toDouble(), y.toDouble(), z.toDouble()) @@ -35,6 +40,8 @@ data class LorenzVec( fun distance(x: Double, y: Double, z: Double): Double = distance(LorenzVec(x, y, z)) + fun distanceChebyshevIgnoreY(other: LorenzVec) = max(abs(this.x - other.x), abs(this.z - other.z)) + fun distanceSq(other: LorenzVec): Double { val dx = (other.x - x) val dy = (other.y - y) @@ -62,12 +69,31 @@ data class LorenzVec( fun divide(d : Double) = multiply(1.0/d) + fun multiply(v: LorenzVec) = LorenzVec(x multiplyZeroSave v.x, y multiplyZeroSave v.y, z multiplyZeroSave v.z) + + fun dotProduct(other: LorenzVec): Double = + x multiplyZeroSave other.x + y multiplyZeroSave other.y + z multiplyZeroSave other.z + + fun angleAsCos(other: LorenzVec) = this.normalize().dotProduct(other.normalize()) + + fun angleInRad(other: LorenzVec) = acos(this.angleAsCos(other)) + + fun angleInDeg(other: LorenzVec) = Math.toDegrees(this.angleInRad(other)) + fun add(other: LorenzVec) = LorenzVec(x + other.x, y + other.y, z + other.z) fun subtract(other: LorenzVec) = LorenzVec(x - other.x, y - other.y, z - other.z) fun normalize() = length().let { LorenzVec(x / it, y / it, z / it) } + fun inverse() = LorenzVec(1.0 / x, 1.0 / y, 1.0 / z) + + fun min() = min(x, min(y, z)) + fun max() = max(x, max(y, z)) + + fun minOfEachElement(other: LorenzVec) = LorenzVec(min(x, other.x), min(y, other.y), min(z, other.z)) + fun maxOfEachElement(other: LorenzVec) = LorenzVec(max(x, other.x), max(y, other.y), max(z, other.z)) + fun printWithAccuracy(accuracy: Int, splitChar: String = " "): String { return if (accuracy == 0) { val x = round(x).toInt() @@ -152,6 +178,10 @@ data class LorenzVec( return LorenzVec(x, y, z) } + fun rotateXY(theta: Double) = LorenzVec(x * cos(theta) - y * sin(theta), x * sin(theta) + y * cos(theta), z) + fun rotateXZ(theta: Double) = LorenzVec(x * cos(theta) + z * sin(theta), y, -x * sin(theta) + z * cos(theta)) + fun rotateYZ(theta: Double) = LorenzVec(x, y * cos(theta) - z * sin(theta), y * sin(theta) + z * cos(theta)) + companion object { fun getFromYawPitch(yaw: Double, pitch: Double): LorenzVec { @@ -183,6 +213,7 @@ fun BlockPos.toLorenzVec(): LorenzVec = LorenzVec(x, y, z) fun Entity.getLorenzVec(): LorenzVec = LorenzVec(posX, posY, posZ) fun Entity.getPrevLorenzVec(): LorenzVec = LorenzVec(prevPosX, prevPosY, prevPosZ) +fun Entity.getMotionLorenzVec(): LorenzVec = LorenzVec(motionX, motionY, motionZ) fun Vec3.toLorenzVec(): LorenzVec = LorenzVec(xCoord, yCoord, zCoord) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/MobUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/MobUtils.kt new file mode 100644 index 000000000..e1b165d25 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/MobUtils.kt @@ -0,0 +1,80 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.data.mob.Mob +import at.hannibal2.skyhanni.data.mob.MobData +import at.hannibal2.skyhanni.utils.EntityUtils.cleanName +import at.hannibal2.skyhanni.utils.LocationUtils.distanceTo +import at.hannibal2.skyhanni.utils.LocationUtils.rayIntersects +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.item.EntityArmorStand +import net.minecraft.entity.player.EntityPlayer + +object MobUtils { + private const val defaultArmorStandName = "Armor Stand" + + // The corresponding ArmorStand for a mob has always the ID + 1 (with some exceptions) + fun getArmorStand(entity: Entity, offset: Int = 1) = getNextEntity(entity, offset) as? EntityArmorStand + + fun getNextEntity(entity: Entity, offset: Int) = EntityUtils.getEntityByID(entity.entityId + offset) + + fun getArmorStandByRangeAll(entity: Entity, range: Double) = + EntityUtils.getEntitiesNearby<EntityArmorStand>(entity.getLorenzVec(), range) + + fun getClosedArmorStand(entity: Entity, range: Double) = + getArmorStandByRangeAll(entity, range).sortedBy { it.distanceTo(entity) }.firstOrNull() + + fun getClosedArmorStandWithName(entity: Entity, range: Double, name: String) = + getArmorStandByRangeAll(entity, range).filter { it.cleanName().startsWith(name) } + .sortedBy { it.distanceTo(entity) }.firstOrNull() + + fun EntityArmorStand.isDefaultValue() = this.name == defaultArmorStandName + + fun EntityArmorStand?.takeNonDefault() = this?.takeIf { !it.isDefaultValue() } + + class OwnerShip(val ownerName: String) { + val ownerPlayer = MobData.players.firstOrNull { it.name == ownerName } + override fun equals(other: Any?): Boolean { + if (other is EntityPlayer) return ownerPlayer == other || ownerName == other.name + if (other is String) return ownerName == other + return false + } + + override fun hashCode(): Int { + return ownerName.hashCode() + } + } + + fun rayTraceForMob(entity: Entity, distance: Double, partialTicks: Float, offset: LorenzVec = LorenzVec()) = + rayTraceForMob(entity, partialTicks, offset)?.takeIf { + it.baseEntity.distanceTo(entity.getLorenzVec()) <= distance + } + + fun rayTraceForMobs( + entity: Entity, + distance: Double, + partialTicks: Float, + offset: LorenzVec = LorenzVec() + ) = + rayTraceForMobs(entity, partialTicks, offset)?.filter { + it.baseEntity.distanceTo(entity.getLorenzVec()) <= distance + }.takeIf { it?.isNotEmpty() ?: false } + + fun rayTraceForMob(entity: Entity, partialTicks: Float, offset: LorenzVec = LorenzVec()) = + rayTraceForMobs(entity, partialTicks, offset)?.firstOrNull() + + fun rayTraceForMobs(entity: Entity, partialTicks: Float, offset: LorenzVec = LorenzVec()): List<Mob>? { + val pos = entity.getPositionEyes(partialTicks).toLorenzVec().add(offset) + val look = entity.getLook(partialTicks).toLorenzVec().normalize() + val possibleEntities = MobData.entityToMob.filterKeys { + it !is EntityArmorStand && it.entityBoundingBox.rayIntersects( + pos, look + ) + }.values + if (possibleEntities.isEmpty()) return null + return possibleEntities.distinct().sortedBy { it.baseEntity.distanceTo(pos) }.drop(1) // drop to remove player + } + + val EntityLivingBase.mob get() = MobData.entityToMob[this] + +} |