package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.data.mob.MobFilter.isRealPlayer import at.hannibal2.skyhanni.events.SkyHanniRenderEntityEvent import at.hannibal2.skyhanni.features.dungeon.DungeonAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule 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 at.hannibal2.skyhanni.utils.compat.getArmorOrFullInventory import at.hannibal2.skyhanni.utils.compat.getLoadedPlayers import at.hannibal2.skyhanni.utils.compat.getNameAsString import at.hannibal2.skyhanni.utils.compat.isOnMainThread import at.hannibal2.skyhanni.utils.compat.normalizeAsArray import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.multiplayer.WorldClient import net.minecraft.client.resources.DefaultPlayerSkin import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase import net.minecraft.entity.item.EntityArmorStand import net.minecraft.entity.monster.EntityEnderman import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EnumPlayerModelParts import net.minecraft.item.ItemStack import net.minecraft.potion.Potion import net.minecraft.scoreboard.ScorePlayerTeam import net.minecraft.util.AxisAlignedBB import net.minecraftforge.client.event.RenderLivingEvent //#if FORGE import net.minecraftforge.fml.common.eventhandler.Event import net.minecraftforge.fml.common.eventhandler.SubscribeEvent //#endif @SkyHanniModule object EntityUtils { fun EntityLivingBase.hasNameTagWith( y: Int, contains: String, debugRightEntity: Boolean = false, inaccuracy: Double = 1.6, debugWrongEntity: Boolean = false, ): Boolean { return getNameTagWith(y, contains, debugRightEntity, inaccuracy, debugWrongEntity) != null } fun getPlayerEntities(): MutableList { val list = mutableListOf() for (entity in Minecraft.getMinecraft().theWorld?.getLoadedPlayers() ?: emptyList()) { if (!entity.isNPC() && entity is EntityOtherPlayerMP) { list.add(entity) } } return list } fun EntityLivingBase.getAllNameTagsInRadiusWith( contains: String, radius: Double = 3.0, ): List = getArmorStandsInRadius(getLorenzVec().add(y = 3), radius).filter { it.getNameAsString().contains(contains) } fun EntityLivingBase.getNameTagWith( y: Int, contains: String, debugRightEntity: Boolean = false, inaccuracy: Double = 1.6, debugWrongEntity: Boolean = false, ): EntityArmorStand? = getAllNameTagsWith(y, contains, debugRightEntity, inaccuracy, debugWrongEntity).firstOrNull() fun EntityLivingBase.getAllNameTagsWith( y: Int, contains: String, debugRightEntity: Boolean = false, inaccuracy: Double = 1.6, debugWrongEntity: Boolean = false, ): List { val center = getLorenzVec().add(y = y) return getArmorStandsInRadius(center, inaccuracy).filter { val result = it.getNameAsString().contains(contains) if (debugWrongEntity && !result) { LorenzUtils.consoleLog("wrong entity in aabb: '" + it.name + "'") } if (debugRightEntity && result) { LorenzUtils.consoleLog("mob: " + center.printWithAccuracy(2)) LorenzUtils.consoleLog("nametag: " + it.getLorenzVec().printWithAccuracy(2)) LorenzUtils.consoleLog("accuracy: " + (it.getLorenzVec() - center).printWithAccuracy(3)) } result } } private fun getArmorStandsInRadius(center: LorenzVec, radius: Double): List { val a = center.add(-radius, -radius - 3, -radius).toBlockPos() val b = center.add(radius, radius + 3, radius).toBlockPos() val alignedBB = AxisAlignedBB(a, b) val clazz = EntityArmorStand::class.java val worldObj = Minecraft.getMinecraft()?.theWorld ?: return emptyList() return worldObj.getEntitiesWithinAABB(clazz, alignedBB) } fun EntityLivingBase.hasBossHealth(health: Int): Boolean = this.hasMaxHealth(health, true) // TODO remove baseMaxHealth fun EntityLivingBase.hasMaxHealth(health: Int, boss: Boolean = false, maxHealth: Int = baseMaxHealth): Boolean { val derpyMultiplier = if (LorenzUtils.isDerpy) 2 else 1 if (maxHealth == health * derpyMultiplier) return true if (!boss && !DungeonAPI.inDungeon()) { // Corrupted if (maxHealth == health * 3 * derpyMultiplier) return true // Runic if (maxHealth == health * 4 * derpyMultiplier) return true // Corrupted+Runic if (maxHealth == health * 12 * derpyMultiplier) return true } return false } fun EntityPlayer.getSkinTexture(): String? { val gameProfile = gameProfile ?: return null return gameProfile.properties.entries() .filter { it.key == "textures" } .map { it.value } .firstOrNull { it.name == "textures" } ?.value } inline fun getEntitiesNextToPlayer(radius: Double): Sequence = getEntitiesNearby(LocationUtils.playerLocation(), radius) inline fun getEntitiesNearby(location: LorenzVec, radius: Double): Sequence = getEntities().filter { it.distanceTo(location) < radius } inline fun getEntitiesNearbyIgnoreY(location: LorenzVec, radius: Double): Sequence = getEntities().filter { it.distanceToIgnoreY(location) < radius } fun EntityLivingBase.isAtFullHealth() = baseMaxHealth == health.toInt() fun EntityArmorStand.hasSkullTexture(skin: String): Boolean { val inventory = this.getArmorOrFullInventory() ?: return false return inventory.any { it != null && it.getSkullTexture() == skin } } fun EntityPlayer.isNPC() = !isRealPlayer() fun EntityLivingBase.hasPotionEffect( potion: //#if MC <1.21 Potion, //#else //$$ net.minecraft.registry.entry.RegistryEntry //#endif ) = getActivePotionEffect(potion) != null fun EntityLivingBase.getArmorInventory(): Array? = if (this is EntityPlayer) inventory.armorInventory.normalizeAsArray() else null fun EntityEnderman.getBlockInHand(): IBlockState? = heldBlockState inline fun getEntities(): Sequence = getAllEntities().filterIsInstance() private fun WorldClient.getAllEntities(): Iterable = //#if MC < 1.14 loadedEntityList //#else //$$ entitiesForRendering() //#endif fun getAllEntities(): Sequence = Minecraft.getMinecraft().theWorld?.getAllEntities()?.let { if (Minecraft.getMinecraft() .isOnMainThread() ) it else it.toMutableList() // TODO: while i am here, i want to point out that copying the entity list does not constitute proper synchronization, but *does* make crashes because of it rarer. }?.asSequence()?.filterNotNull() ?: emptySequence() fun Entity.canBeSeen(radius: Double = 150.0) = getLorenzVec().add(y = 0.5).canBeSeen(radius) fun getEntityByID(entityId: Int) = Minecraft.getMinecraft()?.thePlayer?.entityWorld?.getEntityByID(entityId) //#if FORGE @SubscribeEvent fun onEntityRenderPre( event: //#if MC < 1.14 RenderLivingEvent.Pre<*>, //#else //$$ RenderLivingEvent.Pre<*, *> //#endif ) { val shEvent = SkyHanniRenderEntityEvent.Pre(event.entity, event.renderer, event.x, event.y, event.z) if (shEvent.postAndCatch()) { event.cancel() } } @SubscribeEvent fun onEntityRenderPost( event: //#if MC < 11400 RenderLivingEvent.Post<*>, //#else //$$ RenderLivingEvent.Post<*, *> //#endif ) { SkyHanniRenderEntityEvent.Post(event.entity, event.renderer, event.x, event.y, event.z).postAndCatch() } //#if MC < 11400 @SubscribeEvent fun onEntityRenderSpecialsPre( event: RenderLivingEvent.Specials.Pre<*>, ) { val shEvent = SkyHanniRenderEntityEvent.Specials.Pre(event.entity, event.renderer, event.x, event.y, event.z) if (shEvent.postAndCatch()) { event.cancel() } } @SubscribeEvent fun onEntityRenderSpecialsPost( event: RenderLivingEvent.Specials.Post<*>, ) { SkyHanniRenderEntityEvent.Specials.Post(event.entity, event.renderer, event.x, event.y, event.z).postAndCatch() } //#endif //#endif 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.getNameAsString().removeColor() /** * @return a fake player with the same skin as the real player */ fun getFakePlayer(): EntityOtherPlayerMP { val mc = Minecraft.getMinecraft() val player = mc.thePlayer!! return object : EntityOtherPlayerMP( mc.theWorld, player.gameProfile, ) { override fun getLocationSkin() = player.getLocationSkin() ?: DefaultPlayerSkin.getDefaultSkin(player.uniqueID) override fun getTeam() = object : ScorePlayerTeam(null, null) { override fun getNameTagVisibility() = EnumVisible.NEVER } override fun isWearing(part: EnumPlayerModelParts): Boolean = player.isWearing(part) && part != EnumPlayerModelParts.CAPE } } } //#if FORGE private fun Event.cancel() { isCanceled = true } //#endif