diff options
Diffstat (limited to 'src/main/java')
17 files changed, 1887 insertions, 4 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index e14589155..440c1922e 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -55,6 +55,9 @@ import at.hannibal2.skyhanni.data.TrackerManager import at.hannibal2.skyhanni.data.jsonobjects.local.FriendsJson import at.hannibal2.skyhanni.data.jsonobjects.local.JacobContestsJson import at.hannibal2.skyhanni.data.jsonobjects.local.KnownFeaturesJson +import at.hannibal2.skyhanni.data.mob.MobData +import at.hannibal2.skyhanni.data.mob.MobDebug +import at.hannibal2.skyhanni.data.mob.MobDetection import at.hannibal2.skyhanni.data.jsonobjects.local.VisualWordsJson import at.hannibal2.skyhanni.data.repo.RepoManager import at.hannibal2.skyhanni.events.LorenzTickEvent @@ -441,6 +444,8 @@ class SkyHanniMod { loadModule(SeaCreatureManager()) loadModule(ItemRenderBackground()) loadModule(EntityData()) + loadModule(MobData()) + loadModule(MobDetection()) loadModule(EntityMovementData()) loadModule(TestExportTools) loadModule(ItemClickData()) @@ -827,6 +832,7 @@ class SkyHanniMod { loadModule(SkyHanniDebugsAndTests) loadModule(WorldEdit) PreInitFinishedEvent().postAndCatch() + loadModule(MobDebug()) } @Mod.EventHandler diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugMobConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugMobConfig.java new file mode 100644 index 000000000..9093459a5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugMobConfig.java @@ -0,0 +1,81 @@ +package at.hannibal2.skyhanni.config.features.dev; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.Accordion; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorDropdown; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class DebugMobConfig { + + @Expose + @ConfigOption(name = "Mob Detection Enable", desc = "Turn off and on again to reset all Mobs.") + @ConfigEditorBoolean + public boolean enable = true; + + @Expose + @ConfigOption(name = "Mob Detection", desc = "Debug feature to check the Mob Detection.") + @Accordion + public MobDetection mobDetection = new MobDetection(); + + public enum HowToShow { + OFF("Off"), + ONLY_NAME("Only Name"), + ONLY_HIGHLIGHT("Only Highlight"), + NAME_AND_HIGHLIGHT("Both"); + + final String str; + + HowToShow(String str) { + this.str = str; + } + + @Override + public String toString() { + return str; + } + } + + public static class MobDetection { + + @Expose + @ConfigOption(name = "Log Events", desc = "Logs the spawn and despawn event with full mob info.") + @ConfigEditorBoolean + public boolean logEvents = false; + + @Expose + @ConfigOption(name = "Show RayHit", desc = "Highlights the mob that is currently in front of your view (only SkyblockMob).") + @ConfigEditorBoolean + public boolean showRayHit = false; + + @Expose + @ConfigOption(name = "Player Highlight", desc = "Highlight each entity that is a real Player in blue. (You are also include in the list but won't be highlighted for obvious reason).") + @ConfigEditorBoolean + public boolean realPlayerHighlight = false; + + @Expose + @ConfigOption(name = "DisplayNPC", desc = "Shows the internal mobs that are 'DisplayNPC' as highlight (in red) or the name.") + @ConfigEditorDropdown + public HowToShow displayNPC = HowToShow.OFF; + + @Expose + @ConfigOption(name = "SkyblockMob", desc = "Shows the internal mobs that are 'SkyblockMob' as highlight (in green) or the name.") + @ConfigEditorDropdown + public HowToShow skyblockMob = HowToShow.OFF; + + @Expose + @ConfigOption(name = "Summon", desc = "Shows the internal mobs that are 'Summon' as highlight (in yellow) or the name.") + @ConfigEditorDropdown + public HowToShow summon = HowToShow.OFF; + + @Expose + @ConfigOption(name = "Special", desc = "Shows the internal mobs that are 'Special' as highlight (in aqua) or the name.") + @ConfigEditorDropdown + public HowToShow special = HowToShow.OFF; + + @Expose + @ConfigOption(name = "Show Invisible", desc = "Shows the mob even though they are invisible (do to invisibility effect) if looked at directly.") + @ConfigEditorBoolean + public boolean showInvisible = false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java index 29d9e1779..f4cc9943e 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java @@ -70,4 +70,8 @@ public class DevConfig { @Category(name = "Minecraft Console", desc = "Minecraft Console Settings") public MinecraftConsoleConfig minecraftConsoles = new MinecraftConsoleConfig(); + @Expose + @Category(name = "Debug Mob", desc = "Every Debug related to the Mob System") + public DebugMobConfig mobDebug = new DebugMobConfig(); + } 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) + } + } + } + |
