/*
* Skytils - Hypixel Skyblock Quality of Life Mod
* Copyright (C) 2021 Skytils
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package skytils.skytilsmod.utils
import dev.falsehonesty.asmhelper.AsmHelper
import dev.falsehonesty.asmhelper.dsl.instructions.Descriptor
import gg.essential.universal.UResolution
import gg.essential.universal.wrappers.message.UMessage
import gg.essential.universal.wrappers.message.UTextComponent
import gg.essential.vigilance.Vigilant
import gg.essential.vigilance.gui.settings.CheckboxComponent
import net.minecraft.client.gui.ChatLine
import net.minecraft.client.gui.GuiNewChat
import net.minecraft.client.settings.GameSettings
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityLivingBase
import net.minecraft.entity.SharedMonsterAttributes
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.event.HoverEvent
import net.minecraft.item.ItemStack
import net.minecraft.network.play.server.S02PacketChat
import net.minecraft.network.play.server.S2APacketParticles
import net.minecraft.util.*
import net.minecraftforge.client.event.ClientChatReceivedEvent
import net.minecraftforge.common.MinecraftForge
import org.objectweb.asm.tree.MethodInsnNode
import skytils.skytilsmod.Skytils
import skytils.skytilsmod.Skytils.Companion.mc
import skytils.skytilsmod.asm.SkytilsTransformer
import skytils.skytilsmod.events.impl.PacketEvent.ReceiveEvent
import skytils.skytilsmod.mixins.transformers.accessors.AccessorGuiNewChat
import skytils.skytilsmod.utils.graphics.colors.ColorFactory.web
import skytils.skytilsmod.utils.graphics.colors.CustomColor
import skytils.skytilsmod.utils.graphics.colors.RainbowColor.Companion.fromString
import java.awt.Color
import java.io.File
import java.io.IOException
import java.util.*
import java.util.concurrent.Future
import kotlin.math.floor
import kotlin.math.roundToInt
object Utils {
val azooPuzzoo by lazy {
File(Skytils.modDir, "azoopuzzoo").exists()
}
val breefingdog by lazy {
File(Skytils.modDir, "breefingdog").exists()
}
@JvmField
var noSychic = false
@JvmField
var inSkyblock = false
@JvmField
var inDungeons = false
@JvmField
var isOnHypixel = false
@JvmField
var shouldBypassVolume = false
@JvmField
var lastRenderedSkullStack: ItemStack? = null
@JvmField
var lastRenderedSkullEntity: EntityLivingBase? = null
@JvmStatic
var random = Random()
fun getBlocksWithinRangeAtSameY(center: BlockPos, radius: Int, y: Int): Iterable {
val corner1 = BlockPos(center.x - radius, y, center.z - radius)
val corner2 = BlockPos(center.x + radius, y, center.z + radius)
return BlockPos.getAllInBox(corner1, corner2)
}
@JvmStatic
fun isInTablist(player: EntityPlayer): Boolean {
if (mc.isSingleplayer) {
return true
}
return mc.netHandler.playerInfoMap.any { it.gameProfile.name.equals(player.name, ignoreCase = true) }
}
/**
* Taken from SkyblockAddons under MIT License
* https://github.com/BiscuitDevelopment/SkyblockAddons/blob/master/LICENSE
* @author BiscuitDevelopment
*/
fun playLoudSound(sound: String?, pitch: Double) {
shouldBypassVolume = true
mc.thePlayer.playSound(sound, 1f, pitch.toFloat())
shouldBypassVolume = false
}
/**
* Checks if an object is equal to any of the other objects
* @param object Object to compare
* @param other Objects being compared
* @return boolean
*/
@JvmStatic
fun equalsOneOf(`object`: Any?, vararg other: Any): Boolean {
for (obj in other) {
if (`object` == obj) return true
}
return false
}
fun customColorFromString(string: String?): CustomColor {
if (string == null) throw NullPointerException("Argument cannot be null!")
if (string.startsWith("rainbow(")) {
return fromString(string)
}
return try {
getCustomColorFromColor(web(string))
} catch (e: IllegalArgumentException) {
try {
CustomColor.fromInt(string.toInt())
} catch (ignored: NumberFormatException) {
throw e
}
}
}
private fun getCustomColorFromColor(color: Color) = CustomColor.fromInt(color.rgb)
fun checkThreadAndQueue(run: () -> Unit) {
if (!mc.isCallingFromMinecraftThread) {
mc.addScheduledTask(run)
} else run()
}
/**
* Cancels a chat packet and posts the chat event to the event bus if other mods need it
* @param ReceivePacketEvent packet to cancel
*/
fun cancelChatPacket(ReceivePacketEvent: ReceiveEvent) {
if (ReceivePacketEvent.packet !is S02PacketChat) return
ReceivePacketEvent.isCanceled = true
val packet = ReceivePacketEvent.packet
checkThreadAndQueue {
MinecraftForge.EVENT_BUS.post(ClientChatReceivedEvent(packet.type, packet.chatComponent))
}
}
fun timeFormat(seconds: Double): String {
return if (seconds >= 60) {
floor(seconds / 60).toInt().toString() + "m " + (seconds % 60).roundToInt() + "s"
} else {
seconds.roundToInt().toString() + "s"
}
}
/**
* @link https://stackoverflow.com/a/47925649
*/
@Throws(IOException::class)
fun getJavaRuntime(): String {
val os = System.getProperty("os.name")
val java = "${System.getProperty("java.home")}${File.separator}bin${File.separator}${
if (os != null && os.lowercase().startsWith("windows")) "java.exe" else "java"
}"
if (!File(java).isFile) {
throw IOException("Unable to find suitable java runtime at $java")
}
return java
}
fun checkBossName(floor: String, bossName: String): Boolean {
val correctBoss = when (floor) {
"E" -> "The Watcher"
"F1", "M1" -> "Bonzo"
"F2", "M2" -> "Scarf"
"F3", "M3" -> "The Professor"
"F4", "M4" -> "Thorn"
"F5", "M5" -> "Livid"
"F6", "M6" -> "Sadan"
"F7", "M7" -> "Necron"
else -> null
} ?: return false
// Livid has a prefix in front of the name, so we check ends with to cover all the livids
return bossName.endsWith(correctBoss)
}
fun getKeyDisplayStringSafe(keyCode: Int): String =
runCatching { GameSettings.getKeyDisplayString(keyCode) }.getOrNull() ?: "Key $keyCode"
}
val AxisAlignedBB.minVec: Vec3
get() = Vec3(minX, minY, minZ)
val AxisAlignedBB.maxVec: Vec3
get() = Vec3(maxX, maxY, maxZ)
fun AxisAlignedBB.isPosInside(pos: BlockPos): Boolean {
return pos.x > this.minX && pos.x < this.maxX && pos.y > this.minY && pos.y < this.maxY && pos.z > this.minZ && pos.z < this.maxZ
}
fun Vigilant.openGUI(): Future<*> = Skytils.threadPool.submit {
Skytils.displayScreen = this.gui()
}
val EntityLivingBase.baseMaxHealth: Double
get() = this.getEntityAttribute(SharedMonsterAttributes.maxHealth).baseValue
fun GuiNewChat.getChatLine(mouseX: Int, mouseY: Int): ChatLine? {
if (this is AccessorGuiNewChat) {
if (this.chatOpen) {
val scaleFactor = UResolution.scaleFactor
val chatScale = this.chatScale
val xPos = MathHelper.floor_float((mouseX / scaleFactor.toInt() - 3).toFloat() / chatScale)
val yPos = MathHelper.floor_float((mouseY / scaleFactor.toInt() - 27).toFloat() / chatScale)
if (xPos >= 0 && yPos >= 0) {
val lineCount: Int = this.lineCount.coerceAtMost(this.drawnChatLines.size)
if (xPos <= MathHelper.floor_float(this.chatWidth.toFloat() / this.chatScale) && yPos < mc.fontRendererObj.FONT_HEIGHT * lineCount + lineCount) {
val lineNum: Int = yPos / mc.fontRendererObj.FONT_HEIGHT + this.scrollPos
if (lineNum >= 0 && lineNum < this.drawnChatLines.size) {
return drawnChatLines[lineNum]
}
}
}
}
}
return null
}
fun UMessage.append(item: Any) = this.addTextComponent(item)
fun UTextComponent.setHoverText(text: String): UTextComponent {
hoverAction = HoverEvent.Action.SHOW_TEXT
hoverValue = text
return this
}
fun Entity.getXZDistSq(other: Entity): Double {
val xDelta = this.posX - other.posX
val zDelta = this.posZ - other.posZ
return xDelta * xDelta + zDelta * zDelta
}
val Entity.hasMoved
get() = this.posX != this.prevPosX || this.posY != this.prevPosY || this.posZ != this.prevPosZ
fun Entity.getRotationFor(pos: BlockPos): Pair {
val deltaX = pos.x - posX
val deltaZ = pos.z - posZ
val deltaY = pos.y - (posY + eyeHeight)
val dist = MathHelper.sqrt_double(deltaX * deltaX + deltaZ * deltaZ).toDouble()
val yaw = (MathHelper.atan2(deltaZ, deltaX) * 180.0 / Math.PI).toFloat() - 90.0f
val pitch = (-(MathHelper.atan2(deltaY, dist) * 180.0 / Math.PI)).toFloat()
return yaw to pitch
}
fun CheckboxComponent.toggle() {
this.mouseClick(this.getLeft().toDouble(), this.getTop().toDouble(), 0)
}
fun CheckboxComponent.setState(checked: Boolean) {
if (this.checked != checked) this.toggle()
}
fun BlockPos?.toVec3() = if (this == null) null else Vec3(this)
fun T?.ifNull(run: () -> Unit): T? {
if (this == null) run()
return this
}
val MethodInsnNode.descriptor: Descriptor
get() = Descriptor(
AsmHelper.remapper.remapClassName(this.owner),
SkytilsTransformer.methodMaps.getOrSelf(AsmHelper.remapper.remapMethodName(this.owner, this.name, this.desc)),
AsmHelper.remapper.remapDesc(this.desc)
)
fun Map.getOrSelf(key: T): T = this.getOrDefault(key, key)
val S2APacketParticles.x
get() = this.xCoordinate
val S2APacketParticles.y
get() = this.yCoordinate
val S2APacketParticles.z
get() = this.zCoordinate
val S2APacketParticles.type: EnumParticleTypes
get() = this.particleType
val S2APacketParticles.count
get() = this.particleCount
val S2APacketParticles.speed
get() = this.particleSpeed