aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils
diff options
context:
space:
mode:
authorThunderblade73 <85900443+Thunderblade73@users.noreply.github.com>2024-04-03 20:50:31 +0200
committerGitHub <noreply@github.com>2024-04-03 20:50:31 +0200
commit2f85351bacddb9ab3704a53c778d558a755bcc06 (patch)
treef479045f271f04a66a1ba69a61cb1b9893261eb5 /src/main/java/at/hannibal2/skyhanni/utils
parent76be6ad6de39c7078550394e8ec24a494ddb3bcc (diff)
downloadskyhanni-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')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt24
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt15
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LocationUtils.kt55
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt31
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/MobUtils.kt80
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]
+
+}