diff options
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils')
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/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -4,8 +4,10 @@ import at.hannibal2.skyhanni.config.core.config.Position import at.hannibal2.skyhanni.data.GuiEditManager import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsX import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsY +import at.hannibal2.skyhanni.events.GuiRenderItemEvent import at.hannibal2.skyhanni.utils.renderables.Renderable import io.github.moulberry.moulconfig.internal.TextRenderUtils +import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.Minecraft import net.minecraft.client.gui.FontRenderer import net.minecraft.client.gui.Gui @@ -59,6 +61,15 @@ object RenderUtils { beacon: Boolean = false, alpha: Float = -1f, ) { + drawColor(location, color.toColor(), beacon, alpha) + } + + fun RenderWorldLastEvent.drawColor( + location: LorenzVec, + color: Color, + beacon: Boolean = false, + alpha: Float = -1f, + ) { val (viewerX, viewerY, viewerZ) = getViewerPos(partialTicks) val x = location.x - viewerX val y = location.y - viewerY @@ -73,11 +84,11 @@ object RenderUtils { GlStateManager.disableCull() drawFilledBoundingBox( AxisAlignedBB(x, y, z, x + 1, y + 1, z + 1).expandBlock(), - color.toColor(), + color, realAlpha ) |
