summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/data')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/ClickType.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/mob/Mob.kt177
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/mob/MobData.kt140
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/mob/MobDebug.kt97
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/mob/MobDetection.kt361
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/mob/MobFactories.kt133
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/mob/MobFilter.kt565
7 files changed, 1474 insertions, 1 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/data/ClickType.kt b/src/main/java/at/hannibal2/skyhanni/data/ClickType.kt
index 06c37d039..b008978f0 100644
--- a/src/main/java/at/hannibal2/skyhanni/data/ClickType.kt
+++ b/src/main/java/at/hannibal2/skyhanni/data/ClickType.kt
@@ -2,4 +2,4 @@ package at.hannibal2.skyhanni.data
enum class ClickType {
LEFT_CLICK, RIGHT_CLICK
-} \ No newline at end of file
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/mob/Mob.kt b/src/main/java/at/hannibal2/skyhanni/data/mob/Mob.kt
new file mode 100644
index 000000000..4eab5d881
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/mob/Mob.kt
@@ -0,0 +1,177 @@
+package at.hannibal2.skyhanni.data.mob
+
+import at.hannibal2.skyhanni.data.mob.Mob.Type
+import at.hannibal2.skyhanni.data.mob.MobFilter.summonOwnerPattern
+import at.hannibal2.skyhanni.events.MobEvent
+import at.hannibal2.skyhanni.utils.CollectionUtils.toSingletonListOrEmpty
+import at.hannibal2.skyhanni.utils.EntityUtils.canBeSeen
+import at.hannibal2.skyhanni.utils.EntityUtils.cleanName
+import at.hannibal2.skyhanni.utils.EntityUtils.isCorrupted
+import at.hannibal2.skyhanni.utils.EntityUtils.isRunic
+import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer
+import at.hannibal2.skyhanni.utils.LocationUtils.union
+import at.hannibal2.skyhanni.utils.MobUtils
+import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.entity.item.EntityArmorStand
+import net.minecraft.entity.monster.EntityZombie
+import net.minecraft.util.AxisAlignedBB
+
+/**
+ * Represents a Mob in Hypixel Skyblock.
+ *
+ * @property baseEntity The main entity representing the Mob.
+ *
+ * Avoid caching, as it may change without notice.
+ * @property mobType The type of the Mob.
+ * @property armorStand The armor stand entity associated with the Mob, if it has one.
+ *
+ * Avoid caching, as it may change without notice.
+ * @property name The name of the Mob.
+ * @property extraEntities Additional entities associated with the Mob.
+ *
+ * Avoid caching, as they may change without notice.
+ * @property owner Valid for: [Type.SUMMON], [Type.SLAYER]
+ *
+ * The owner of the Mob.
+ * @property hasStar Valid for: [Type.DUNGEON]
+ *
+ * Indicates whether the Mob has a star.
+ * @property attribute Valid for: [Type.DUNGEON]
+ *
+ * The attribute of the Mob.
+ * @property levelOrTier Valid for: [Type.BASIC], [Type.SLAYER]
+ *
+ * The level or tier of the Mob.
+ * @property hologram1 Valid for: [Type.BASIC], [Type.SLAYER]
+ *
+ * Gives back the first additional armor stand.
+ *
+ * (should be called in the [MobEvent.Spawn] since it is a lazy)
+ * @property hologram2 Valid for: [Type.BASIC], [Type.SLAYER]
+ *
+ * Gives back the second additional armor stand.
+ *
+ * (should be called in the [MobEvent.Spawn] since it is a lazy)
+ */
+class Mob(
+ var baseEntity: EntityLivingBase,
+ val mobType: Type,
+ var armorStand: EntityArmorStand? = null,
+ val name: String = "",
+ additionalEntities: List<EntityLivingBase>? = null,
+ ownerName: String? = null,
+ val hasStar: Boolean = false,
+ val attribute: MobFilter.DungeonAttribute? = null,
+ val levelOrTier: Int = -1,
+) {
+
+ val owner: MobUtils.OwnerShip?
+
+ val hologram1Delegate = lazy { MobUtils.getArmorStand(armorStand ?: baseEntity, 1) }
+ val hologram2Delegate = lazy { MobUtils.getArmorStand(armorStand ?: baseEntity, 2) }
+
+ val hologram1 by hologram1Delegate
+ val hologram2 by hologram2Delegate
+
+ private val extraEntitiesList = additionalEntities?.toMutableList() ?: mutableListOf()
+ private var relativeBoundingBox: AxisAlignedBB?
+
+ val extraEntities: List<EntityLivingBase> = extraEntitiesList
+
+ enum class Type {
+ DISPLAY_NPC, SUMMON, BASIC, DUNGEON, BOSS, SLAYER, PLAYER, PROJECTILE, SPECIAL;
+
+ fun isSkyblockMob() = when (this) {
+ BASIC, DUNGEON, BOSS, SLAYER -> true
+ else -> false
+ }
+ }
+
+ val isCorrupted get() = baseEntity.isCorrupted() // Can change
+ val isRunic = baseEntity.isRunic() // Does not Change
+
+ fun isInRender() = baseEntity.distanceToPlayer() < MobData.ENTITY_RENDER_RANGE_IN_BLOCKS
+
+ fun canBeSeen() = baseEntity.canBeSeen()
+
+ fun isInvisible() = if (baseEntity !is EntityZombie) baseEntity.isInvisible else false
+
+ val boundingBox: AxisAlignedBB
+ get() = relativeBoundingBox?.offset(baseEntity.posX, baseEntity.posY, baseEntity.posZ)
+ ?: baseEntity.entityBoundingBox
+
+ init {
+ removeExtraEntitiesFromChecking()
+ relativeBoundingBox =
+ if (extraEntities.isNotEmpty()) makeRelativeBoundingBox() else null // Inlined updateBoundingBox()
+
+ owner = (ownerName ?: if (mobType == Type.SLAYER) hologram2?.let {
+ summonOwnerPattern.matchMatcher(it.cleanName()) { this.group("name") }
+ } else null)?.let { MobUtils.OwnerShip(it) }
+ }
+
+ private fun removeExtraEntitiesFromChecking() =
+ extraEntities.count { MobData.retries[it.entityId] != null }.also {
+ MobData.externRemoveOfRetryAmount += it
+ }
+
+ fun updateBoundingBox() {
+ relativeBoundingBox = if (extraEntities.isNotEmpty()) makeRelativeBoundingBox() else null
+ }
+
+ private fun makeRelativeBoundingBox() =
+ (baseEntity.entityBoundingBox.union(extraEntities.filter { it !is EntityArmorStand }
+ .mapNotNull { it.entityBoundingBox }))?.offset(-baseEntity.posX, -baseEntity.posY, -baseEntity.posZ)
+
+ fun fullEntityList() =
+ baseEntity.toSingletonListOrEmpty() +
+ armorStand.toSingletonListOrEmpty() +
+ extraEntities
+
+ fun makeEntityToMobAssociation() =
+ fullEntityList().associateWith { this }
+
+ internal fun internalAddEntity(entity: EntityLivingBase) {
+ if (baseEntity.entityId > entity.entityId) {
+ extraEntitiesList.add(0, baseEntity)
+ baseEntity = entity
+ } else {
+ extraEntitiesList.add(extraEntitiesList.lastIndex + 1, entity)
+ }
+ updateBoundingBox()
+ MobData.entityToMob[entity] = this
+ }
+
+ internal fun internalAddEntity(entities: Collection<EntityLivingBase>) {
+ val list = entities.drop(1).toMutableList().apply { add(baseEntity) }
+ extraEntitiesList.addAll(0, list)
+ baseEntity = entities.first()
+ updateBoundingBox()
+ removeExtraEntitiesFromChecking()
+ MobData.entityToMob.putAll(entities.associateWith { this })
+ }
+
+ internal fun internalUpdateOfEntity(entity: EntityLivingBase) = when (entity.entityId) {
+ baseEntity.entityId -> baseEntity = entity
+ armorStand?.entityId ?: Int.MIN_VALUE -> armorStand = entity as EntityArmorStand
+ else -> {
+ extraEntitiesList.remove(entity)
+ extraEntitiesList.add(entity)
+ Unit // To make return type of this branch Unit
+ }
+ }
+
+ override fun hashCode() = baseEntity.hashCode()
+
+ override fun toString(): String = "$name - ${baseEntity.entityId}"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Mob
+
+ return baseEntity == other.baseEntity
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/mob/MobData.kt b/src/main/java/at/hannibal2/skyhanni/data/mob/MobData.kt
new file mode 100644
index 000000000..b8da58d41
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/mob/MobData.kt
@@ -0,0 +1,140 @@
+package at.hannibal2.skyhanni.data.mob
+
+import at.hannibal2.skyhanni.events.MobEvent
+import at.hannibal2.skyhanni.utils.LocationUtils
+import at.hannibal2.skyhanni.utils.getLorenzVec
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.entity.item.EntityArmorStand
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.TreeMap
+import at.hannibal2.skyhanni.data.mob.Mob.Type as MobType
+
+class MobData {
+
+ class MobSet : HashSet<Mob>() {
+ val entityList get() = this.flatMap { listOf(it.baseEntity) + (it.extraEntities) }
+ }
+
+ companion object {
+
+ val players = MobSet()
+ val displayNPCs = MobSet()
+ val skyblockMobs = MobSet()
+ val summoningMobs = MobSet()
+ val special = MobSet()
+ val currentMobs = MobSet()
+
+ val entityToMob = mutableMapOf<EntityLivingBase, Mob>()
+
+ internal val currentEntityLiving = mutableSetOf<EntityLivingBase>()
+ internal val previousEntityLiving = mutableSetOf<EntityLivingBase>()
+
+ internal val retries = TreeMap<Int, RetryEntityInstancing>()
+
+ const val ENTITY_RENDER_RANGE_IN_BLOCKS = 80.0 // Entity DeRender after ~5 Chunks
+ const val DETECTION_RANGE = 22.0
+ const val DISPLAY_NPC_DETECTION_RANGE = 24.0 // 24.0
+
+ var externRemoveOfRetryAmount = 0
+ }
+
+ internal enum class Result {
+ Found, NotYetFound, Illegal, SomethingWentWrong
+ }
+
+ internal class MobResult(val result: Result, val mob: Mob?) {
+ operator fun component1() = result
+ operator fun component2() = mob
+
+ companion object {
+ val illegal = MobResult(Result.Illegal, null)
+ val notYetFound = MobResult(Result.NotYetFound, null)
+ val somethingWentWrong = MobResult(Result.SomethingWentWrong, null)
+ fun found(mob: Mob) = MobResult(Result.Found, mob)
+
+ fun EntityArmorStand?.makeMobResult(mob: (EntityArmorStand) -> Mob?) =
+ this?.let { armor ->
+ mob.invoke(armor)?.let { found(it) } ?: somethingWentWrong
+ } ?: notYetFound
+ }
+ }
+
+ internal class RetryEntityInstancing(
+ var entity: EntityLivingBase,
+ var times: Int,
+ val roughType: MobType
+ ) {
+ override fun hashCode() = entity.entityId
+ override fun equals(other: Any?) = (other as? RetryEntityInstancing).hashCode() == this.hashCode()
+ fun toKeyValuePair() = entity.entityId to this
+
+ fun outsideRange() =
+ entity.getLorenzVec().distanceChebyshevIgnoreY(LocationUtils.playerLocation()) > when (roughType) {
+ MobType.DISPLAY_NPC -> DISPLAY_NPC_DETECTION_RANGE
+ MobType.PLAYER -> Double.POSITIVE_INFINITY
+ else -> DETECTION_RANGE
+ }
+ }
+
+ @SubscribeEvent
+ fun onMobEventSpawn(event: MobEvent.Spawn) {
+ entityToMob.putAll(event.mob.makeEntityToMobAssociation())
+ currentMobs.add(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onMobEventDeSpawn(event: MobEvent.DeSpawn) {
+ event.mob.fullEntityList().forEach { entityToMob.remove(it) }
+ currentMobs.remove(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onSkyblockMobSpawnEvent(event: MobEvent.Spawn.SkyblockMob) {
+ skyblockMobs.add(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onSkyblockMobDeSpawnEvent(event: MobEvent.DeSpawn.SkyblockMob) {
+ skyblockMobs.remove(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onSummonSpawnEvent(event: MobEvent.Spawn.Summon) {
+ summoningMobs.add(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onSummonDeSpawnEvent(event: MobEvent.DeSpawn.Summon) {
+ summoningMobs.remove(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onSpecialSpawnEvent(event: MobEvent.Spawn.Special) {
+ special.add(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onSpecialDeSpawnEvent(event: MobEvent.DeSpawn.Special) {
+ special.remove(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onDisplayNPCSpawnEvent(event: MobEvent.Spawn.DisplayNPC) {
+ displayNPCs.add(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onDisplayNPCDeSpawnEvent(event: MobEvent.DeSpawn.DisplayNPC) {
+ displayNPCs.remove(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onRealPlayerSpawnEvent(event: MobEvent.Spawn.Player) {
+ players.add(event.mob)
+ }
+
+ @SubscribeEvent
+ fun onRealPlayerDeSpawnEvent(event: MobEvent.DeSpawn.Player) {
+ players.remove(event.mob)
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/mob/MobDebug.kt b/src/main/java/at/hannibal2/skyhanni/data/mob/MobDebug.kt
new file mode 100644
index 000000000..63a39d502
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/mob/MobDebug.kt
@@ -0,0 +1,97 @@
+package at.hannibal2.skyhanni.data.mob
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.config.features.dev.DebugMobConfig.HowToShow
+import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent
+import at.hannibal2.skyhanni.events.MobEvent
+import at.hannibal2.skyhanni.test.command.CopyNearbyEntitiesCommand.getMobInfo
+import at.hannibal2.skyhanni.utils.LocationUtils.getTopCenter
+import at.hannibal2.skyhanni.utils.LorenzColor
+import at.hannibal2.skyhanni.utils.LorenzDebug
+import at.hannibal2.skyhanni.utils.MobUtils
+import at.hannibal2.skyhanni.utils.RenderUtils.drawFilledBoundingBox_nea
+import at.hannibal2.skyhanni.utils.RenderUtils.drawString
+import at.hannibal2.skyhanni.utils.RenderUtils.expandBlock
+import net.minecraft.client.Minecraft
+import net.minecraft.client.entity.EntityPlayerSP
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class MobDebug {
+
+ private val config get() = SkyHanniMod.feature.dev.mobDebug.mobDetection
+
+ private var lastRayHit: Mob? = null
+
+ private fun HowToShow.isHighlight() =
+ this == HowToShow.ONLY_HIGHLIGHT || this == HowToShow.NAME_AND_HIGHLIGHT
+
+ private fun HowToShow.isName() =
+ this == HowToShow.ONLY_NAME || this == HowToShow.NAME_AND_HIGHLIGHT
+
+ private fun Mob.isNotInvisible() = !this.isInvisible() || (config.showInvisible && this == lastRayHit)
+
+ private fun MobData.MobSet.highlight(event: LorenzRenderWorldEvent, color: (Mob) -> (LorenzColor)) =
+ this.filter { it.isNotInvisible() }.forEach {
+ event.drawFilledBoundingBox_nea(it.boundingBox.expandBlock(), color.invoke(it).toColor(), 0.3f)
+ }
+
+ private fun MobData.MobSet.showName(event: LorenzRenderWorldEvent) =
+ this.filter { it.canBeSeen() && it.isNotInvisible() }.map { it.boundingBox.getTopCenter() to it.name }.forEach {
+ event.drawString(
+ it.first.add(y = 0.5), "ยง5" + it.second, seeThroughBlocks = true
+ )
+ }
+
+ @SubscribeEvent
+ fun onWorldRenderDebug(event: LorenzRenderWorldEvent) {
+ if (config.showRayHit || config.showInvisible) {
+ lastRayHit = MobUtils.rayTraceForMobs(Minecraft.getMinecraft().thePlayer, event.partialTicks)
+ ?.firstOrNull { it.canBeSeen() && (config.showInvisible || it.isInvisible()) }
+ }
+
+ if (config.skyblockMob.isHighlight()) {
+ MobData.skyblockMobs.highlight(event) { if (it.mobType == Mob.Type.BOSS) LorenzColor.DARK_GREEN else LorenzColor.GREEN }
+ }
+ if (config.displayNPC.isHighlight()) {
+ MobData.displayNPCs.highlight(event) { LorenzColor.RED }
+ }
+ if (config.realPlayerHighlight) {
+ MobData.players.highlight(event) { if (it.baseEntity is EntityPlayerSP) LorenzColor.CHROMA else LorenzColor.BLUE }
+ }
+ if (config.summon.isHighlight()) {
+ MobData.summoningMobs.highlight(event) { LorenzColor.YELLOW }
+ }
+ if (config.special.isHighlight()) {
+ MobData.special.highlight(event) { LorenzColor.AQUA }
+ }
+ if (config.skyblockMob.isName()) {
+ MobData.skyblockMobs.showName(event)
+ }
+ if (config.displayNPC.isName()) {
+ MobData.displayNPCs.showName(event)
+ }
+ if (config.summon.isName()) {
+ MobData.summoningMobs.showName(event)
+ }
+ if (config.special.isName()) {
+ MobData.special.showName(event)
+ }
+ if (config.showRayHit) {
+ lastRayHit?.let {
+ event.drawFilledBoundingBox_nea(it.boundingBox.expandBlock(), LorenzColor.GOLD.toColor(), 0.5f)
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onMobEvent(event: MobEvent) {
+ if (!config.logEvents) return
+ LorenzDebug.log(
+ "Mob ${if (event is MobEvent.Spawn) "Spawn" else "Despawn"}: ${
+ getMobInfo(event.mob).joinToString(
+ ", "
+ )
+ }"
+ )
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/mob/MobDetection.kt b/src/main/java/at/hannibal2/skyhanni/data/mob/MobDetection.kt
new file mode 100644
index 000000000..c674c0593
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/mob/MobDetection.kt
@@ -0,0 +1,361 @@
+package at.hannibal2.skyhanni.data.mob
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.IslandType
+import at.hannibal2.skyhanni.data.mob.MobFilter.isDisplayNPC
+import at.hannibal2.skyhanni.data.mob.MobFilter.isRealPlayer
+import at.hannibal2.skyhanni.data.mob.MobFilter.isSkyBlockMob
+import at.hannibal2.skyhanni.events.DebugDataCollectEvent
+import at.hannibal2.skyhanni.events.EntityHealthUpdateEvent
+import at.hannibal2.skyhanni.events.IslandChangeEvent
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.MobEvent
+import at.hannibal2.skyhanni.events.PacketEvent
+import at.hannibal2.skyhanni.utils.CollectionUtils.drainForEach
+import at.hannibal2.skyhanni.utils.CollectionUtils.drainTo
+import at.hannibal2.skyhanni.utils.CollectionUtils.put
+import at.hannibal2.skyhanni.utils.CollectionUtils.refreshReference
+import at.hannibal2.skyhanni.utils.EntityUtils
+import at.hannibal2.skyhanni.utils.LocationUtils
+import at.hannibal2.skyhanni.utils.LorenzLogger
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.getLorenzVec
+import net.minecraft.client.Minecraft
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.entity.item.EntityArmorStand
+import net.minecraft.entity.monster.EntityCreeper
+import net.minecraft.entity.passive.EntityBat
+import net.minecraft.entity.passive.EntityVillager
+import net.minecraft.entity.player.EntityPlayer
+import net.minecraft.network.play.server.S0CPacketSpawnPlayer
+import net.minecraft.network.play.server.S0FPacketSpawnMob
+import net.minecraft.network.play.server.S37PacketStatistics
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.network.FMLNetworkEvent
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.atomic.AtomicBoolean
+
+private const val MAX_RETRIES = 20 * 5
+
+private const val MOB_DETECTION_LOG_PREFIX = "MobDetection: "
+
+class MobDetection {
+
+ /* Unsupported "Mobs"
+ Nicked Players
+ Odanate
+ Silk Worm
+ Fairy (in Dungeon)
+ Totem of Corruption
+ Worm
+ Scatha
+ Butterfly
+ Exe
+ Wai
+ Zee
+ */
+
+ private val forceReset get() = !SkyHanniMod.feature.dev.mobDebug.enable
+
+ private var shouldClear: AtomicBoolean = AtomicBoolean(false)
+
+ private val logger = LorenzLogger("mob/detection")
+
+ init {
+ MobFilter.bossMobNameFilter
+ MobFilter.mobNameFilter
+ MobFilter.dojoFilter
+ MobFilter.summonFilter
+ MobFilter.dungeonNameFilter
+ MobFilter.petCareNamePattern
+ MobFilter.slayerNameFilter
+ MobFilter.summonOwnerPattern
+ MobFilter.wokeSleepingGolemPattern
+ MobFilter.jerryPattern
+ MobFilter.jerryMagmaCubePattern
+ }
+
+ private fun mobDetectionReset() {
+ MobData.currentMobs.map {
+ it.createDeSpawnEvent()
+ }.forEach { it.postAndCatch() }
+ }
+
+ @SubscribeEvent
+ fun onTick(event: LorenzTickEvent) {
+ if (shouldClear.get()) { // Needs to work outside skyblock since it needs clearing when leaving skyblock and joining limbo
+ mobDetectionReset()
+ shouldClear.set(false)
+ }
+ if (!LorenzUtils.inSkyBlock) return
+ if (event.isMod(2)) return
+
+ makeEntityReferenceUpdate()
+
+ handleMobsFromPacket()
+
+ handleRetries()
+
+ MobData.previousEntityLiving.clear()
+ MobData.previousEntityLiving.addAll(MobData.currentEntityLiving)
+ MobData.currentEntityLiving.clear()
+ MobData.currentEntityLiving.addAll(EntityUtils.getEntities<EntityLivingBase>()
+ .filter { it !is EntityArmorStand })
+
+ if (forceReset) {
+ MobData.currentEntityLiving.clear() // Naturally removing the mobs using the despawn
+ }
+
+ (MobData.currentEntityLiving - MobData.previousEntityLiving).forEach { addRetry(it) } // Spawn
+ (MobData.previousEntityLiving - MobData.currentEntityLiving).forEach { entityDeSpawn(it) } // Despawn
+
+ if (forceReset) {
+ mobDetectionReset() // Ensure that all mobs are cleared 100%
+ }
+ }
+
+ /** Splits the entity into player, displayNPC and other */
+ private fun EntityLivingBase.getRoughType() = when {
+ this is EntityPlayer && this.isRealPlayer() -> Mob.Type.PLAYER
+ this.isDisplayNPC() -> Mob.Type.DISPLAY_NPC
+ this.isSkyBlockMob() && !islandException() -> Mob.Type.BASIC
+ else -> null
+ }
+
+ private fun addRetry(entity: EntityLivingBase) = entity.getRoughType()?.let { type ->
+ val re = MobData.RetryEntityInstancing(entity, 0, type)
+ MobData.retries.put(re.toKeyValuePair())
+ }
+
+ private fun removeRetry(entity: EntityLivingBase) = MobData.retries.remove(entity.entityId)
+
+ private fun getRetry(entity: EntityLivingBase) = MobData.retries[entity.entityId]
+
+ /** @return always true */
+ private fun mobDetectionError(string: String) = logger.log(string).let { true }
+
+ /**@return a false means that it should try again (later)*/
+ private fun entitySpawn(entity: EntityLivingBase, roughType: Mob.Type): Boolean {
+ when (roughType) {
+ Mob.Type.PLAYER -> MobEvent.Spawn.Player(MobFactories.player(entity)).postAndCatch()
+
+ Mob.Type.DISPLAY_NPC -> return MobFilter.createDisplayNPC(entity)
+ Mob.Type.BASIC -> {
+ val (result, mob) = MobFilter.createSkyblockEntity(entity)
+ when (result) {
+ MobData.Result.NotYetFound -> return false
+ MobData.Result.Illegal -> return true // Remove entity from the spawning queue
+ MobData.Result.SomethingWentWrong -> return mobDetectionError("Something Went Wrong!")
+ MobData.Result.Found -> {
+ if (mob == null) return mobDetectionError("Mob is null even though result is Found")
+ when (mob.mobType) {
+ Mob.Type.SUMMON -> MobEvent.Spawn.Summon(mob)
+
+ Mob.Type.BASIC, Mob.Type.DUNGEON, Mob.Type.BOSS, Mob.Type.SLAYER -> MobEvent.Spawn.SkyblockMob(
+ mob
+ )
+
+ Mob.Type.SPECIAL -> MobEvent.Spawn.Special(mob)
+ Mob.Type.PROJECTILE -> MobEvent.Spawn.Projectile(mob)
+ Mob.Type.DISPLAY_NPC -> MobEvent.Spawn.DisplayNPC(mob) // Needed for some special cases
+ Mob.Type.PLAYER -> return mobDetectionError("An Player Ended Here. How?")
+ }.postAndCatch()
+ }
+ }
+ }
+
+ else -> return true
+ }
+ return true
+ }
+
+ private val entityFromPacket = ConcurrentLinkedQueue<Pair<EntityPacketType, Int>>()
+
+ /** For mobs that have default health of the entity */
+ private enum class EntityPacketType {
+ SPIRIT_BAT, VILLAGER, CREEPER_VAIL
+ }
+
+ /** Handles some mobs that have default health of the entity, specially using the [EntityHealthUpdateEvent] */
+ private fun handleMobsFromPacket() = entityFromPacket.drainForEach { (type, id) ->
+ when (type) {
+ EntityPacketType.SPIRIT_BAT -> {
+ val entity = EntityUtils.getEntityByID(id) as? EntityBat ?: return@drainForEach
+ if (MobData.entityToMob[entity] != null) return@drainForEach
+ removeRetry(entity)
+ MobEvent.Spawn.Projectile(MobFactories.projectile(entity, "Spirit Scepter Bat")).postAndCatch()
+ }
+
+ EntityPacketType.VILLAGER -> {
+ val entity = EntityUtils.getEntityByID(id) as? EntityVillager ?: return@drainForEach
+ val mob = MobData.entityToMob[entity]
+ if (mob != null && mob.mobType == Mob.Type.DISPLAY_NPC) {
+ MobEvent.DeSpawn.DisplayNPC(mob)
+ addRetry(entity)
+ return@drainForEach
+ }
+ getRetry(entity)?.let {
+ if (it.roughType == Mob.Type.DISPLAY_NPC) {
+ removeRetry(entity)
+ addRetry(entity)
+ }
+ }
+ }
+
+ EntityPacketType.CREEPER_VAIL -> {
+ val entity = EntityUtils.getEntityByID(id) as? EntityCreeper ?: return@drainForEach
+ if (MobData.entityToMob[entity] != null) return@drainForEach
+ if (!entity.powered) return@drainForEach
+ removeRetry(entity)
+ MobEvent.Spawn.Special(MobFactories.special(entity, "Creeper Veil")).postAndCatch()
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onEntityHealthUpdateEvent(event: EntityHealthUpdateEvent) {
+ when {
+ event.entity is EntityBat && event.health == 6 -> {
+ entityFromPacket.add(EntityPacketType.SPIRIT_BAT to event.entity.entityId)
+ }
+
+ event.entity is EntityVillager && event.health != 20 -> {
+ entityFromPacket.add(EntityPacketType.VILLAGER to event.entity.entityId)
+ }
+
+ event.entity is EntityCreeper && event.health == 20 -> {
+ entityFromPacket.add(EntityPacketType.CREEPER_VAIL to event.entity.entityId)
+ }
+ }
+ }
+
+ private fun islandException(): Boolean = when (LorenzUtils.skyBlockIsland) {
+ IslandType.GARDEN_GUEST -> true
+ IslandType.PRIVATE_ISLAND_GUEST -> true
+ else -> false
+ }
+
+ private fun entityDeSpawn(entity: EntityLivingBase) {
+ MobData.entityToMob[entity]?.createDeSpawnEvent()?.postAndCatch() ?: removeRetry(entity)
+ allEntitiesViaPacketId.remove(entity.entityId)
+ }
+
+ private fun Mob.createDeSpawnEvent() = when (this.mobType) {
+ Mob.Type.PLAYER -> MobEvent.DeSpawn.Player(this)
+ Mob.Type.SUMMON -> MobEvent.DeSpawn.Summon(this)
+ Mob.Type.SPECIAL -> MobEvent.DeSpawn.Special(this)
+ Mob.Type.PROJECTILE -> MobEvent.DeSpawn.Projectile(this)
+ Mob.Type.DISPLAY_NPC -> MobEvent.DeSpawn.DisplayNPC(this)
+ Mob.Type.BASIC, Mob.Type.DUNGEON, Mob.Type.BOSS, Mob.Type.SLAYER -> MobEvent.DeSpawn.SkyblockMob(this)
+ }
+
+ private fun handleRetries() {
+ val iterator = MobData.retries.iterator()
+ while (iterator.hasNext()) {
+ val (_, retry) = iterator.next()
+
+ if (MobData.externRemoveOfRetryAmount > 0) {
+ iterator.remove()
+ MobData.externRemoveOfRetryAmount--
+ continue
+ }
+
+ if (retry.outsideRange()) continue
+
+ val entity = retry.entity
+ if (retry.times == MAX_RETRIES) {
+ logger.log(
+ "`${retry.entity.name}`${retry.entity.entityId} missed {\n "
+ + "is already Found: ${MobData.entityToMob[retry.entity] != null})."
+ + "\n Position: ${retry.entity.getLorenzVec()}\n "
+ + "DistanceC: ${
+ entity.getLorenzVec().distanceChebyshevIgnoreY(LocationUtils.playerLocation())
+ }\n"
+ + "Relative Position: ${entity.getLorenzVec().subtract(LocationUtils.playerLocation())}\n " +
+ "}"
+ )
+ // Uncomment this to make it closed a loop
+ // iterator.remove()
+ // continue
+ }
+ if (!entitySpawn(entity, retry.roughType)) {
+ retry.times++
+ continue
+ }
+ iterator.remove()
+ }
+ }
+
+ private val entityUpdatePackets = ConcurrentLinkedQueue<Int>()
+ private val entitiesThatRequireUpdate = mutableSetOf<Int>() // needs to be distinct, therefore not using a queue
+
+ /** Refreshes the references of the entities in entitiesThatRequireUpdate */
+ private fun makeEntityReferenceUpdate() {
+ entitiesThatRequireUpdate.iterator().let { iterator ->
+ while (iterator.hasNext()) {
+ if (handleEntityUpdate(iterator.next())) iterator.remove()
+ }
+ }
+ entityUpdatePackets.drainTo(entitiesThatRequireUpdate)
+ }
+
+ private fun handleEntityUpdate(entityID: Int): Boolean {
+ val entity = EntityUtils.getEntityByID(entityID) as? EntityLivingBase ?: return false
+ getRetry(entity)?.apply { this.entity = entity }
+ MobData.currentEntityLiving.refreshReference(entity)
+ MobData.previousEntityLiving.refreshReference(entity)
+ // update map
+ MobData.entityToMob[entity]?.internalUpdateOfEntity(entity)
+ return true
+ }
+
+ @SubscribeEvent
+ fun onEntitySpawnPacket(event: PacketEvent.ReceiveEvent) {
+ when (val packet = event.packet) {
+ is S0FPacketSpawnMob -> addEntityUpdate(packet.entityID)
+ is S0CPacketSpawnPlayer -> addEntityUpdate(packet.entityID)
+ // is S0EPacketSpawnObject -> addEntityUpdate(packet.entityID)
+ is S37PacketStatistics -> // one of the first packets that is sent when switching servers inside the BungeeCord Network (please some prove this, I just found it out via Testing)
+ {
+ shouldClear.set(true)
+ allEntitiesViaPacketId.clear()
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onIslandChange(event: IslandChangeEvent) {
+ MobData.currentEntityLiving.remove(Minecraft.getMinecraft().thePlayer) // Fix for the Player
+ }
+
+ private val allEntitiesViaPacketId = mutableSetOf<Int>()
+
+ private fun addEntityUpdate(id: Int) = if (allEntitiesViaPacketId.contains(id)) {
+ entityUpdatePackets.add(id)
+ } else {
+ allEntitiesViaPacketId.add(id)
+ }
+
+ @SubscribeEvent
+ fun onDisconnect(event: FMLNetworkEvent.ClientDisconnectionFromServerEvent) {
+ shouldClear.set(true)
+ }
+
+ @SubscribeEvent
+ fun onDebugDataCollect(event: DebugDataCollectEvent) {
+ event.title("Mob Detection")
+ if (forceReset) {
+ event.addData("Mob Detection is manually disabled!")
+ } else {
+ event.addIrrelevant {
+ add("normal enabled")
+ add("Active Mobs: ${MobData.currentMobs.size}")
+ val inDistanceMobs = MobData.retries.count { it.value.outsideRange() }
+ add("Searching for Mobs: ${MobData.retries.size - inDistanceMobs}")
+ add("Mobs over Max Search Count: ${MobData.retries.count { it.value.times > MAX_RETRIES }}")
+ add("Mobs outside of Range: $inDistanceMobs")
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/mob/MobFactories.kt b/src/main/java/at/hannibal2/skyhanni/data/mob/MobFactories.kt
new file mode 100644
index 000000000..b8c9f9227
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/mob/MobFactories.kt
@@ -0,0 +1,133 @@
+package at.hannibal2.skyhanni.data.mob
+
+import at.hannibal2.skyhanni.utils.EntityUtils.cleanName
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal
+import at.hannibal2.skyhanni.utils.StringUtils.findMatcher
+import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.entity.item.EntityArmorStand
+
+object MobFactories {
+ fun slayer(
+ baseEntity: EntityLivingBase,
+ armorStand: EntityArmorStand,
+ extraEntityList: List<EntityLivingBase>
+ ): Mob? =
+ MobFilter.slayerNameFilter.matchMatcher(armorStand.cleanName()) {
+ Mob(
+ baseEntity = baseEntity,
+ mobType = Mob.Type.SLAYER,
+ armorStand = armorStand,
+ name = this.group("name"),
+ additionalEntities = extraEntityList,
+ levelOrTier = this.group("tier").romanToDecimal()
+ )
+ }
+
+ fun boss(
+ baseEntity: EntityLivingBase,
+ armorStand: EntityArmorStand,
+ extraEntityList: List<EntityLivingBase> = emptyList(),
+ overriddenName: String? = null
+ ): Mob? =
+ MobFilter.bossMobNameFilter.matchMatcher(armorStand.cleanName()) {
+ Mob(
+ baseEntity = baseEntity,
+ mobType = Mob.Type.BOSS,
+ armorStand = armorStand,
+ name = overriddenName ?: this.group("name"),
+ levelOrTier = group("level")?.takeIf { it.isNotEmpty() }?.toInt() ?: -1,
+ additionalEntities = extraEntityList
+ )
+ }
+
+ fun dungeon(
+ baseEntity: EntityLivingBase,
+ armorStand: EntityArmorStand,
+ extraEntityList: List<EntityLivingBase> = emptyList()
+ ): Mob? =
+ MobFilter.dungeonNameFilter.matchMatcher(armorStand.cleanName()) {
+ Mob(
+ baseEntity = baseEntity,
+ mobType = Mob.Type.DUNGEON,
+ armorStand = armorStand,
+ name = this.group("name"),
+ additionalEntities = extraEntityList,
+ hasStar = this.group("star")?.isNotEmpty() ?: false,
+ attribute = this.group("attribute")?.takeIf { it.isNotEmpty() }
+ ?.let {
+ LorenzUtils.enumValueOfOrNull<MobFilter.DungeonAttribute>(it)
+ }
+ )
+ }
+
+ fun basic(
+ baseEntity: EntityLivingBase,
+ armorStand: EntityArmorStand,
+ extraEntityList: List<EntityLivingBase>? = null
+ ): Mob? =
+ MobFilter.mobNameFilter.findMatcher(armorStand.cleanName()) {
+ Mob(
+ baseEntity = baseEntity,
+ mobType = Mob.Type.BASIC,
+ armorStand = armorStand,
+ name = this.group("name").removeCorruptedSuffix(
+ this.group("corrupted")?.isNotEmpty() ?: false
+ ),
+ additionalEntities = extraEntityList,
+ levelOrTier = this.group("level")?.takeIf { it.isNotEmpty() }
+ ?.toInt() ?: -1
+ )
+ }
+
+ fun basic(baseEntity: EntityLivingBase, name: String) =
+ Mob(baseEntity = baseEntity, mobType = Mob.Type.BASIC, name = name)
+