aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils
diff options
context:
space:
mode:
authorhannibal2 <24389977+hannibal00212@users.noreply.github.com>2023-09-16 12:34:18 +0200
committerhannibal2 <24389977+hannibal00212@users.noreply.github.com>2023-09-16 12:34:18 +0200
commit4293cfd919c3c93d4532534f722c407d7ad1370d (patch)
treef9f612f021ef7f4283d74312edfaca30badc6749 /src/main/java/at/hannibal2/skyhanni/utils
parent538e3ceb76f8e0b590291ce9aa90aa94896cdcb6 (diff)
parent024ba52fb69b6cd44b4e31542867f802de656f15 (diff)
downloadSkyHanni-cum.tar.gz
SkyHanni-cum.tar.bz2
SkyHanni-cum.zip
Merge branch 'beta' into cumcum
# Conflicts: # src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt # src/main/java/at/hannibal2/skyhanni/config/features/AshfangConfig.java
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt408
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt75
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt46
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt66
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/NEUVersionCheck.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt85
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/SimpleTimeMark.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/SoundUtils.kt1
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/TimeMark.kt16
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/Timer.kt43
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/jsonobjects/SackListJson.java11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderLineTooltips.kt32
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt39
15 files changed, 797 insertions, 70 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt b/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
new file mode 100644
index 000000000..d290d540b
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
@@ -0,0 +1,408 @@
+package at.hannibal2.skyhanni.utils
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.RenderEntityOutlineEvent
+import at.hannibal2.skyhanni.mixins.transformers.CustomRenderGlobal
+import at.hannibal2.skyhanni.test.command.CopyErrorCommand
+import net.minecraft.client.Minecraft
+import net.minecraft.client.renderer.GlStateManager
+import net.minecraft.client.renderer.OpenGlHelper
+import net.minecraft.client.renderer.RenderHelper
+import net.minecraft.client.renderer.culling.ICamera
+import net.minecraft.client.shader.Framebuffer
+import net.minecraft.entity.Entity
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.util.BlockPos
+import net.minecraftforge.client.MinecraftForgeClient
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.lwjgl.opengl.GL11
+import org.lwjgl.opengl.GL13
+import org.lwjgl.opengl.GL30
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+
+/**
+ * Class to handle all entity outlining, including xray and no-xray rendering
+ * Features that include entity outlining should subscribe to the {@link RenderEntityOutlineEvent}.
+ *
+ * Credit to SkyblockAddons and Biscuit Development
+ * https://github.com/BiscuitDevelopment/SkyblockAddons/blob/main/src/main/java/codes/biscuit/skyblockaddons/features/EntityOutlines/EntityOutlineRenderer.java
+ *
+ */
+object EntityOutlineRenderer {
+ private val entityRenderCache: CachedInfo = CachedInfo(null, null, null)
+ private var stopLookingForOptifine = false
+ private var isMissingMixin = false
+ private var isFastRender: Method? = null
+ private var isShaders: Method? = null
+ private var isAntialiasing: Method? = null
+ private var emptyLastTick = false
+ private val swapBuffer by lazy { initSwapBuffer() }
+ private val logger = LorenzLogger("entity_outline_renderer")
+ private val mc get() = Minecraft.getMinecraft()
+ private val BUF_FLOAT_4: java.nio.FloatBuffer = org.lwjgl.BufferUtils.createFloatBuffer(4)
+
+ private val CustomRenderGlobal.frameBuffer get() = entityOutlineFramebuffer_skyhanni
+ private val CustomRenderGlobal.shader get() = entityOutlineShader_skyhanni
+
+ /**
+ * @return a new framebuffer with the size of the main framebuffer
+ */
+ private fun initSwapBuffer(): Framebuffer {
+ val main = mc.framebuffer
+ val framebuffer = Framebuffer(main.framebufferTextureWidth, main.framebufferTextureHeight, true)
+ framebuffer.setFramebufferFilter(GL11.GL_NEAREST)
+ framebuffer.setFramebufferColor(0.0f, 0.0f, 0.0f, 0.0f)
+ return framebuffer
+ }
+
+ private fun updateFramebufferSize() {
+ val width = mc.displayWidth
+ val height = mc.displayHeight
+ if (swapBuffer.framebufferWidth != width || swapBuffer.framebufferHeight != height) {
+ swapBuffer.createBindFramebuffer(width, height)
+ }
+ val renderGlobal = mc.renderGlobal as CustomRenderGlobal
+ val outlineBuffer = renderGlobal.frameBuffer
+ if (outlineBuffer.framebufferWidth != width || outlineBuffer.framebufferHeight != height) {
+ outlineBuffer.createBindFramebuffer(width, height)
+ renderGlobal.shader.createBindFramebuffers(width, height)
+ }
+ }
+
+ /**
+ * Renders xray and no-xray entity outlines.
+ *
+ * @param camera the current camera
+ * @param partialTicks the progress to the next tick
+ * @param x the camera x position
+ * @param y the camera y position
+ * @param z the camera z position
+ */
+ @JvmStatic
+ fun renderEntityOutlines(camera: ICamera, partialTicks: Float, vector: LorenzVec): Boolean {
+ val shouldRenderOutlines = shouldRenderEntityOutlines()
+
+ if (!(shouldRenderOutlines && !isCacheEmpty() && MinecraftForgeClient.getRenderPass() == 0)) {
+ return !shouldRenderOutlines
+ }
+
+ val renderGlobal = mc.renderGlobal as CustomRenderGlobal
+ val renderManager = mc.renderManager
+ mc.theWorld.theProfiler.endStartSection("entityOutlines")
+ updateFramebufferSize()
+
+ // Clear and bind the outline framebuffer
+ renderGlobal.frameBuffer.framebufferClear()
+ renderGlobal.frameBuffer.bindFramebuffer(false)
+
+ // Vanilla options
+ RenderHelper.disableStandardItemLighting()
+ GlStateManager.disableFog()
+ mc.renderManager.setRenderOutlines(true)
+
+ // Enable outline mode
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_REPLACE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL13.GL_CONSTANT);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
+
+ // Render x-ray outlines first, ignoring the depth buffer bit
+ if (!isXrayCacheEmpty()) {
+ // Xray is enabled by disabling depth testing
+ GlStateManager.depthFunc(GL11.GL_ALWAYS)
+
+ entityRenderCache.xrayCache?.forEach { (key, value) ->
+ // Test if the entity should render, given the player's camera position
+ if (!shouldRender(camera, key, vector)) return@forEach
+
+ try {
+ if (key !is EntityLivingBase) outlineColor(value)
+ renderManager.renderEntityStatic(key, partialTicks, true)
+ } catch (ignored: Exception) {
+ }
+ }
+
+ // Reset depth function
+ GlStateManager.depthFunc(GL11.GL_LEQUAL)
+ }
+
+ // Render no-xray outlines second, taking into consideration the depth bit
+ if (!isNoXrayCacheEmpty()) {
+ if (!isNoOutlineCacheEmpty()) {
+ // Render other entities + terrain that may occlude an entity outline into a depth buffer
+ swapBuffer.framebufferClear()
+ copyBuffers(mc.framebuffer, swapBuffer, GL11.GL_DEPTH_BUFFER_BIT)
+ swapBuffer.bindFramebuffer(false)
+
+ // Copy terrain + other entities depth into outline frame buffer to now switch to no-xray outlines
+ entityRenderCache.noOutlineCache?.forEach { entity ->
+ // Test if the entity should render, given the player's instantaneous camera position
+ if (!shouldRender(camera, entity, vector)) return@forEach
+
+ try {
+ renderManager.renderEntityStatic(entity, partialTicks, true)
+ } catch (ignored: Exception) {
+ }
+ }
+
+ // Copy the entire depth buffer of everything that might occlude outline to outline framebuffer
+ copyBuffers(swapBuffer, renderGlobal.frameBuffer, GL11.GL_DEPTH_BUFFER_BIT)
+ renderGlobal.frameBuffer.bindFramebuffer(false)
+ } else {
+ copyBuffers(mc.framebuffer, renderGlobal.frameBuffer, GL11.GL_DEPTH_BUFFER_BIT)
+ }
+
+ // Xray disabled by re-enabling traditional depth testing
+ entityRenderCache.noXrayCache?.forEach { (key, value) ->
+ // Test if the entity should render, given the player's instantaneous camera position
+ if (!shouldRender(camera, key, vector)) return@forEach
+
+ try {
+ if (key !is EntityLivingBase) outlineColor(value)
+ renderManager.renderEntityStatic(key, partialTicks, true)
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ // Disable outline mode
+ with(GL11.GL_TEXTURE_ENV) {
+ GL11.glTexEnvi(this, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE)
+ GL11.glTexEnvi(this, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE)
+ GL11.glTexEnvi(this, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE)
+ GL11.glTexEnvi(this, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR)
+ GL11.glTexEnvi(this, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE)
+ GL11.glTexEnvi(this, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE)
+ GL11.glTexEnvi(this, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA)
+ }
+
+ // Vanilla options
+ RenderHelper.enableStandardItemLighting()
+ mc.renderManager.setRenderOutlines(false)
+
+ // Load the outline shader
+ GlStateManager.depthMask(false)
+ renderGlobal.shader.loadShaderGroup(partialTicks)
+ GlStateManager.depthMask(true)
+
+ // Reset GL/framebuffers for next render layers
+ GlStateManager.enableLighting()
+ mc.framebuffer.bindFramebuffer(false)
+ GlStateManager.enableFog()
+ GlStateManager.enableBlend()
+ GlStateManager.enableColorMaterial()
+ GlStateManager.enableDepth()
+ GlStateManager.enableAlpha()
+
+ return !shouldRenderOutlines
+ }
+
+ @JvmStatic
+ fun getCustomOutlineColor(entity: Entity?): Int? {
+ if (entityRenderCache.xrayCache?.containsKey(entity) == true) {
+ return entityRenderCache.xrayCache!![entity]
+ }
+ return if (entityRenderCache.noXrayCache?.containsKey(entity) == true) {
+ entityRenderCache.noXrayCache!![entity]
+ } else null
+ }
+
+ /**
+ * Caches optifine settings and determines whether outlines should be rendered
+ *
+ * @return `true` iff outlines should be rendered
+ */
+ @JvmStatic
+ fun shouldRenderEntityOutlines(): Boolean {
+ // SkyBlock Conditions
+ if (!LorenzUtils.inSkyBlock) {
+ return false
+ }
+
+ // Main toggle for outlines features
+ if (!isEnabled()) {
+ return false
+ }
+
+ // Vanilla Conditions
+ val renderGlobal = mc.renderGlobal as CustomRenderGlobal
+ if (renderGlobal.frameBuffer == null || renderGlobal.shader == null || mc.thePlayer == null) return false
+
+ // Optifine Conditions
+ if (!stopLookingForOptifine && isFastRender == null) {
+ try {
+ val config = Class.forName("Config")
+ try {
+ isFastRender = config.getMethod("isFastRender")
+ isShaders = config.getMethod("isShaders")
+ isAntialiasing = config.getMethod("isAntialiasing")
+ } catch (ex: Exception) {
+ logger.log("Couldn't find Optifine methods for entity outlines.")
+ stopLookingForOptifine = true
+ }
+ } catch (ex: Exception) {
+ logger.log("Couldn't find Optifine for entity outlines.")
+ stopLookingForOptifine = true
+ }
+ }
+ var isFastRenderValue = false
+ var isShadersValue = false
+ var isAntialiasingValue = false
+ if (isFastRender != null) {
+ try {
+ isFastRenderValue = isFastRender!!.invoke(null) as Boolean
+ isShadersValue = isShaders!!.invoke(null) as Boolean
+ isAntialiasingValue = isAntialiasing!!.invoke(null) as Boolean
+ } catch (ex: IllegalAccessException) {
+ logger.log("An error occurred while calling Optifine methods for entity outlines... $ex")
+ } catch (ex: InvocationTargetException) {
+ logger.log("An error occurred while calling Optifine methods for entity outlines... $ex")
+ }
+ }
+ return !isFastRenderValue && !isShadersValue && !isAntialiasingValue
+ }
+
+ // Add new features that need the entity outline logic here
+ private fun isEnabled(): Boolean {
+ if (isMissingMixin) return false
+ if (SkyHanniMod.feature.fishing.rareSeaCreatureHighlight) return true
+ if (SkyHanniMod.feature.misc.glowingDroppedItems.enabled) return true
+ if (SkyHanniMod.feature.dungeon.highlightTeammates) return true
+
+ return false
+ }
+
+ /**
+ * Apply the same rendering standards as in [net.minecraft.client.renderer.RenderGlobal.renderEntities] lines 659 to 669
+ *
+ * @param camera the current camera
+ * @param entity the entity to render
+ * @param x the camera x position
+ * @param y the camera y position
+ * @param z the camera z position
+ * @return whether the entity should be rendered
+ */
+ private fun shouldRender(camera: ICamera, entity: Entity, vector: LorenzVec): Boolean =
+ // Only render the view entity when sleeping or in 3rd person mode
+ if (entity === mc.renderViewEntity &&
+ !(mc.renderViewEntity is EntityLivingBase && (mc.renderViewEntity as EntityLivingBase).isPlayerSleeping ||
+ mc.gameSettings.thirdPersonView != 0)
+ ) {
+ false
+ } else mc.theWorld.isBlockLoaded(BlockPos(entity)) && (mc.renderManager.shouldRender(
+ entity,
+ camera,
+ vector.x,
+ vector.y,
+ vector.z
+ ) || entity.riddenByEntity === mc.thePlayer)
+ // Only render if renderManager would render and the world is loaded at the entity
+
+ private fun outlineColor(color: Int) {
+ BUF_FLOAT_4.put(0, (color shr 16 and 255).toFloat() / 255.0f)
+ BUF_FLOAT_4.put(1, (color shr 8 and 255).toFloat() / 255.0f)
+ BUF_FLOAT_4.put(2, (color and 255).toFloat() / 255.0f)
+ BUF_FLOAT_4.put(3, 1f)
+ GL11.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, BUF_FLOAT_4)
+ }
+
+ /**
+ * Function that copies a portion of a framebuffer to another framebuffer.
+ *
+ *
+ * Note that this requires GL3.0 to function properly
+ *
+ *
+ * The major use of this function is to copy the depth-buffer portion of the world framebuffer to the entity outline framebuffer.
+ * This enables us to perform no-xray outlining on entities, as we can use the world framebuffer's depth testing on the outline frame buffer
+ *
+ * @param frameToCopy the framebuffer from which we are copying data
+ * @param frameToPaste the framebuffer onto which we are copying the data
+ * @param buffersToCopy the bit mask indicating the sections to copy (see [GL11.GL_DEPTH_BUFFER_BIT], [GL11.GL_COLOR_BUFFER_BIT], [GL11.GL_STENCIL_BUFFER_BIT])
+ */
+ private fun copyBuffers(frameToCopy: Framebuffer?, frameToPaste: Framebuffer?, buffersToCopy: Int) {
+ if (OpenGlHelper.isFramebufferEnabled()) {
+ OpenGlHelper.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, frameToCopy!!.framebufferObject)
+ OpenGlHelper.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, frameToPaste!!.framebufferObject)
+ GL30.glBlitFramebuffer(
+ 0, 0, frameToCopy.framebufferWidth, frameToCopy.framebufferHeight,
+ 0, 0, frameToPaste.framebufferWidth, frameToPaste.framebufferHeight,
+ buffersToCopy, GL11.GL_NEAREST
+ )
+ }
+ }
+
+ fun isCacheEmpty() = isXrayCacheEmpty() && isNoXrayCacheEmpty()
+
+ private fun isXrayCacheEmpty() = entityRenderCache.xrayCache?.isEmpty() ?: true
+ private fun isNoXrayCacheEmpty() = entityRenderCache.noXrayCache?.isEmpty() ?: true
+ private fun isNoOutlineCacheEmpty() = entityRenderCache.noOutlineCache?.isEmpty() ?: true
+
+ /**
+ * Updates the cache at the start of every minecraft tick to improve efficiency.
+ * Identifies and caches all entities in the world that should be outlined.
+ *
+ *
+ * Calls to [.shouldRender] are frustum based, rely on partialTicks,
+ * and so can't be updated on a per-tick basis without losing information.
+ *
+ *
+ * This works since entities are only updated once per tick, so the inclusion or exclusion of an entity
+ * to be outlined can be cached each tick with no loss of data
+ *
+ * @param event the client tick event
+ */
+ @SubscribeEvent
+ fun onTick(event: LorenzTickEvent) {
+ if (!(event.phase == EventPriority.NORMAL && isEnabled())) return;
+
+ val renderGlobal = try {
+ mc.renderGlobal as CustomRenderGlobal
+ } catch (e: NoClassDefFoundError) {
+ CopyErrorCommand.logError(e, "Unable to enable entity outlines, the required mixin is not loaded")
+ isMissingMixin = true
+ return
+ }
+
+ if (mc.theWorld != null && shouldRenderEntityOutlines()) {
+ // These events need to be called in this specific order for the xray to have priority over the no xray
+ // Get all entities to render xray outlines
+ val xrayOutlineEvent = RenderEntityOutlineEvent(RenderEntityOutlineEvent.Type.XRAY, null)
+ xrayOutlineEvent.postAndCatch()
+ // Get all entities to render no xray outlines, using pre-filtered entities (no need to test xray outlined entities)
+ val noxrayOutlineEvent = RenderEntityOutlineEvent(
+ RenderEntityOutlineEvent.Type.NO_XRAY,
+ xrayOutlineEvent.entitiesToChooseFrom
+ )
+ noxrayOutlineEvent.postAndCatch()
+ // Cache the entities for future use
+ entityRenderCache.xrayCache = xrayOutlineEvent.entitiesToOutline
+ entityRenderCache.noXrayCache = noxrayOutlineEvent.entitiesToOutline
+ entityRenderCache.noOutlineCache = noxrayOutlineEvent.entitiesToChooseFrom
+ emptyLastTick = if (isCacheEmpty()) {
+ if (!emptyLastTick) {
+ renderGlobal.frameBuffer.framebufferClear()
+ }
+ true
+ } else false
+ } else if (!emptyLastTick) {
+ entityRenderCache.xrayCache = null
+ entityRenderCache.noXrayCache = null
+ entityRenderCache.noOutlineCache = null
+ if (renderGlobal.frameBuffer != null) renderGlobal.frameBuffer.framebufferClear()
+ emptyLastTick = true
+ }
+ }
+
+ private class CachedInfo(
+ var xrayCache: HashMap<Entity, Int>?,
+ var noXrayCache: HashMap<Entity, Int>?,
+ var noOutlineCache: HashSet<Entity>?
+ )
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt
index bedbebc46..fd2ca66e1 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt
@@ -1,5 +1,6 @@
package at.hannibal2.skyhanni.utils
+import at.hannibal2.skyhanni.test.command.CopyErrorCommand
import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName
import at.hannibal2.skyhanni.utils.NEUItems.getItemStack
import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.isRecombobulated
@@ -152,19 +153,39 @@ object ItemUtils {
return nbt.getCompoundTag("SkullOwner").getString("Id")
}
- fun ItemStack.getItemRarity(): Int {
- //todo make into an enum in future
- return when (this.getLore().lastOrNull()?.take(4)) {
- "§f§l" -> 0 // common
- "§a§l" -> 1 // uncommon
- "§9§l" -> 2 // rare
- "§5§l" -> 3 // epic
- "§6§l" -> 4 // legendary
- "§d§l" -> 5 // mythic
- "§b§l" -> 6 // divine
- "§4§l" -> 7 // supreme
- "§c§l" -> 8 // special/very special
- else -> -1 // unknown
+ fun ItemStack.getItemRarityOrCommon() = getItemRarityOrNull() ?: LorenzRarity.COMMON
+
+ fun ItemStack.getItemRarityOrNull(): LorenzRarity? {
+ if (isPet(cleanName())) {
+ return getPetRarity(this)
+ }
+
+ val lore = getLore()
+ var lastLine = lore.lastOrNull() ?: return null
+ if (lastLine == "§eClick to inspect!") {
+ // Assuming inside ah browser
+ val index = lore.indexOfFirst { it.startsWith("§7Seller: ") } - 2
+ if (index > 0) {
+ lastLine = lore[index]
+ }
+ }
+ return when (lastLine.take(4)) {
+ "§f§l" -> LorenzRarity.COMMON
+ "§a§l" -> LorenzRarity.UNCOMMON
+ "§9§l" -> LorenzRarity.RARE
+ "§5§l" -> LorenzRarity.EPIC
+ "§6§l" -> LorenzRarity.LEGENDARY
+ "§d§l" -> LorenzRarity.MYTHIC
+ "§b§l" -> LorenzRarity.DIVINE
+ "§4§l" -> LorenzRarity.SUPREME
+ "§c§l" -> LorenzRarity.SPECIAL
+ else -> {
+ CopyErrorCommand.logErrorState(
+ "Could not read rarity for item $name",
+ "getItemRarityOrNull not found for: ${getInternalName()}, name:'$name', lastLine:'$lastLine'"
+ )
+ return null
+ }
}
}
@@ -189,7 +210,12 @@ object ItemUtils {
private val itemAmountCache = mutableMapOf<String, Pair<String, Int>>()
- fun readItemAmount(input: String): Pair<String, Int>? {
+ fun readItemAmount(originalInput: String): Pair<String, Int>? {
+ // This workaround fixes 'Tubto Cacti I Book'
+ val input = if (originalInput.endsWith(" Book")) {
+ originalInput.replace(" Book", "")
+ } else originalInput
+
if (itemAmountCache.containsKey(input)) {
return itemAmountCache[input]!!
}
@@ -234,10 +260,23 @@ object ItemUtils {
return getItemStack().nameWithEnchantment ?: error("Could not find item name for $this")
}
+ // TODO: Replace entirely some day
fun getPetRarityOld(petStack: ItemStack?): Int {
- val petInternalName = petStack?.getInternalName_old()
- if (petInternalName == "NONE" || petInternalName == null) return -1
- val split = petInternalName.split(";")
- return split.last().toInt()
+ val rarity = petStack?.getItemRarityOrNull() ?: return -1
+
+ return rarity.id
+ }
+
+ private fun getPetRarity(pet: ItemStack): LorenzRarity? {
+ val rarityId = pet.getInternalName().asString().split(";").last().toInt()
+ val rarity = LorenzRarity.getById(rarityId)
+ val name = pet.name
+ if (rarity == null) {
+ CopyErrorCommand.logErrorState(
+ "Could not read rarity for pet $name",
+ "getPetRarity not found for: ${pet.getInternalName()}, name:'$name'"
+ )
+ }
+ return rarity
}
} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt
new file mode 100644
index 000000000..4af8f7e2f
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt
@@ -0,0 +1,46 @@
+package at.hannibal2.skyhanni.utils
+
+import at.hannibal2.skyhanni.test.command.CopyErrorCommand
+
+
+// TODO: replace id with ordinal
+enum class LorenzRarity(val color: LorenzColor, val id: Int) {
+ COMMON(LorenzColor.WHITE, 0),
+ UNCOMMON(LorenzColor.GREEN, 1),
+ RARE(LorenzColor.BLUE, 2),
+ EPIC(LorenzColor.DARK_PURPLE, 3),
+ LEGENDARY(LorenzColor.GOLD, 4),
+ MYTHIC(LorenzColor.LIGHT_PURPLE, 5),
+ DIVINE(LorenzColor.AQUA, 6),
+ SUPREME(LorenzColor.DARK_RED, 7),
+ SPECIAL(LorenzColor.RED, 8),
+ VERY_SPECIAL(LorenzColor.RED, 9),
+ ;
+
+ fun oneBelow(logError: Boolean = true): LorenzRarity? {
+ val rarityBelow = getById(ordinal - 1)
+ if (rarityBelow == null && logError) {
+ CopyErrorCommand.logErrorState(
+ "Problem with item rarity detected.",
+ "Trying to get an item rarity below common"
+ )
+ }
+ return rarityBelow
+ }
+
+ fun oneAbove(logError: Boolean = true): LorenzRarity? {
+ val rarityBelow = getById(ordinal + 1)
+ if (rarityBelow == null && logError) {
+ CopyErrorCommand.logErrorState(
+ "Problem with item rarity detected.",
+ "Trying to get an item rarity above special"
+ )
+ }
+ return rarityBelow
+ }
+
+ companion object {
+ fun getById(id: Int) = entries.firstOrNull { it.ordinal == id }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
index 248efd5df..9085b96e8 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
@@ -5,14 +5,15 @@ import at.hannibal2.skyhanni.data.HypixelData
import at.hannibal2.skyhanni.data.IslandType
import at.hannibal2.skyhanni.data.MayorElection
import at.hannibal2.skyhanni.features.dungeon.DungeonData
+import at.hannibal2.skyhanni.mixins.transformers.AccessorGuiEditSign
import at.hannibal2.skyhanni.test.TestBingo
import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull
+import at.hannibal2.skyhanni.utils.StringUtils.capAtMinecraftLength
import at.hannibal2.skyhanni.utils.StringUtils.removeColor
import at.hannibal2.skyhanni.utils.StringUtils.toDashlessUUID
import at.hannibal2.skyhanni.utils.renderables.Renderable
import io.github.moulberry.moulconfig.observer.Observer
import io.github.moulberry.moulconfig.observer.Property
-import io.github.moulberry.notenoughupdates.mixins.AccessorGuiEditSign
import io.github.moulberry.notenoughupdates.util.SkyBlockTime
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.inventory.GuiEditSign
@@ -21,6 +22,7 @@ import net.minecraft.entity.SharedMonsterAttributes
import net.minecraft.event.ClickEvent
import net.minecraft.event.HoverEvent
import net.minecraft.util.ChatComponentText
+import org.apache.commons.lang3.SystemUtils
import org.lwjgl.input.Keyboard
import java.awt.Color
import java.lang.reflect.Field
@@ -29,6 +31,7 @@ import java.text.DecimalFormat
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.*
+import java.util.Timer
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty
@@ -54,6 +57,8 @@ object LorenzUtils {
val noTradeMode get() = HypixelData.noTrade
+ val isStrandedProfile get() = HypixelData.stranded
+
val isBingoProfile get() = inSkyBlock && (HypixelData.bingo || TestBingo.testBingo)
val lastWorldSwitch get() = HypixelData.joinedWorld
@@ -227,16 +232,25 @@ object LorenzUtils {
fun setTextIntoSign(text: String) {
val gui = Minecraft.getMinecraft().currentScreen
- if (gui !is GuiEditSign) return
- gui as AccessorGuiEditSign
+ if (gui !is AccessorGuiEditSign) return
gui.tileSign.signText[0] = ChatComponentText(text)
}
+ fun addTextIntoSign(addedText: String) {
+ val gui = Minecraft.getMinecraft().currentScreen
+ if (gui !is AccessorGuiEditSign) return
+ val lines = gui.tileSign.signText
+ val index = gui.editLine
+ val text = lines[index].unformattedText + addedText
+ lines[index] = ChatComponentText(text.capAtMinecraftLength(90))
+ }
+
fun clickableChat(message: String, command: String) {
val text = ChatComponentText(message)
- text.chatStyle.chatClickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/${command.removePrefix("/")}")
+ val fullCommand = "/" + command.removePrefix("/")
+ text.chatStyle.chatClickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand)
text.chatStyle.chatHoverEvent =
- HoverEvent(HoverEvent.Action.SHOW_TEXT, ChatComponentText("§eExecute /${command.removePrefix("/")}"))
+ HoverEvent(HoverEvent.Action.SHOW_TEXT, ChatComponentText("§eExecute $fullCommand"))
Minecraft.getMinecraft().thePlayer.addChatMessage(text)
}
@@ -280,6 +294,9 @@ object LorenzUtils {
fun isControlKeyDown() = OSUtils.isKeyHeld(Keyboard.KEY_LCONTROL) || OSUtils.isKeyHeld(Keyboard.KEY_RCONTROL)
+ // A mac-only key, represents Windows key on windows (but different key code)
+ fun isCommandKeyDown() = OSUtils.isKeyHeld(Keyboard.KEY_LMETA) || OSUtils.isKeyHeld(Keyboard.KEY_LMETA)
+
// MoulConfig is in Java, I don't want to downgrade this logic
fun <T> onChange(vararg properties: Property<out T>, observer: Observer<T>) {
for (property in properties) {
@@ -426,17 +443,25 @@ object LorenzUtils {
fun IslandType.isInIsland() = inIsland(this)
- fun <K, N : Number> MutableMap<K, N>.addOrPut(item: K, amount: N): N {
- val old = this[item] ?: 0
- val new = when (old) {
- is Double -> old + amount.toDouble()
- is Float -> old + amount.toFloat()
- is Long -> old + amount.toLong()
- else -> old.toInt() + amount.toInt()
- }
- @Suppress("UNCHECKED_CAST")
- this[item] = new as N
- return new
+ fun <K> MutableMap<K, Int>.addOrPut(key: K, number: Int): Int {
+ val currentValue = this[key] ?: 0
+ val newValue = currentValue + number
+ this[key] = newValue
+ return newValue
+ }
+
+ fun <K> MutableMap<K, Long>.addOrPut(key: K, number: Long): Long {
+ val currentValue = this[key] ?: 0L
+ val newValue = currentValue + number
+ this[key] = newValue
+ return newValue
+ }
+
+ fun <K> MutableMap<K, Double>.addOrPut(key: K, number: Double): Double {
+ val currentValue = this[key] ?: 0.0
+ val newValue = currentValue + number
+ this[key] = newValue
+ return newValue
}
fun <K, N : Number> MutableMap<K, N>.sumAllValues(): Double {
@@ -468,7 +493,7 @@ object LorenzUtils {
// Taken and modified from Skytils
@JvmStatic
- fun Any.equalsOneOf(vararg other: Any): Boolean {
+ fun <T> T.equalsOneOf(vararg other: T): Boolean {
for (obj in other) {
if (this == obj) return true
}
@@ -500,4 +525,9 @@ object LorenzUtils {
}
}, duration.inWholeMilliseconds)
}
-} \ No newline at end of file
+
+ fun isPastingKeysDown(): Boolean {
+ val modifierHeld = if (SystemUtils.IS_OS_MAC) isCommandKeyDown() else isControlKeyDown()
+ return modifierHeld && OSUtils.isKeyHeld(Keyboard.KEY_V)
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NEUVersionCheck.kt b/src/main/java/at/hannibal2/skyhanni/utils/NEUVersionCheck.kt
index 93d4fab98..a1a35b9d8 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/NEUVersionCheck.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUVersionCheck.kt
@@ -51,7 +51,7 @@ object NEUVersionCheck {
private fun neuWarning(text: String) {
openPopupWindow(
text,
- Pair("Join SkyHanni Discord", "https://discord.com/invite/8DXVN4BJz3"),
+ Pair("Join SkyHanni Discord", "https://discord.com/invite/skyhanni-997079228510117908"),
Pair("Open SkyHanni GitHub", "https://github.com/hannibal002/SkyHanni"),
Pair("Join NEU Discord", "https://discord.gg/moulberry"),
)
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
index 0150e7311..405dc1bb5 100644
--- a/