diff options
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni')
114 files changed, 10224 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java new file mode 100644 index 000000000..09355383c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java @@ -0,0 +1,119 @@ +package at.hannibal2.skyhanni; + +import at.hannibal2.skyhanni.bazaar.BazaarApi; +import at.hannibal2.skyhanni.bazaar.BazaarOrderHelper; +import at.hannibal2.skyhanni.chat.ChatFilter; +import at.hannibal2.skyhanni.chat.ChatManager; +import at.hannibal2.skyhanni.chat.PlayerChatFilter; +import at.hannibal2.skyhanni.config.Features; +import at.hannibal2.skyhanni.config.commands.Commands; +import at.hannibal2.skyhanni.dungeon.*; +import at.hannibal2.skyhanni.dungeon.damageindicator.DungeonBossDamageIndicator; +import at.hannibal2.skyhanni.items.HideNotClickableItems; +import at.hannibal2.skyhanni.items.ItemDisplayOverlayFeatures; +import at.hannibal2.skyhanni.items.abilitycooldown.ItemAbilityCooldown; +import at.hannibal2.skyhanni.misc.*; +import at.hannibal2.skyhanni.test.LorenzTest; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.*; +import java.nio.charset.StandardCharsets; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +@Mod(modid = SkyHanniMod.MODID, version = SkyHanniMod.VERSION) +public class SkyHanniMod { + + public static final String MODID = "skyhanni"; + public static final String VERSION = "0."; + + public static Features feature; + private File configFile; + + private final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); + + public static File configDirectory; + + @EventHandler + public void preInit(FMLPreInitializationEvent event) { + new BazaarApi(); + MinecraftForge.EVENT_BUS.register(this); + MinecraftForge.EVENT_BUS.register(new ChatManager()); + MinecraftForge.EVENT_BUS.register(new HypixelData()); + MinecraftForge.EVENT_BUS.register(new DungeonData()); + MinecraftForge.EVENT_BUS.register(new ScoreboardData()); + + MinecraftForge.EVENT_BUS.register(new BazaarOrderHelper()); + MinecraftForge.EVENT_BUS.register(new ChatFilter()); + MinecraftForge.EVENT_BUS.register(new PlayerChatFilter()); + MinecraftForge.EVENT_BUS.register(new DungeonChatFilter()); + MinecraftForge.EVENT_BUS.register(new HideNotClickableItems()); + MinecraftForge.EVENT_BUS.register(new DungeonHighlightClickedBlocks()); + MinecraftForge.EVENT_BUS.register(new ItemDisplayOverlayFeatures()); + MinecraftForge.EVENT_BUS.register(new CurrentPetDisplay()); + MinecraftForge.EVENT_BUS.register(new ExpBottleOnGroundHider()); + MinecraftForge.EVENT_BUS.register(new DungeonBossDamageIndicator()); + MinecraftForge.EVENT_BUS.register(new ItemAbilityCooldown()); + MinecraftForge.EVENT_BUS.register(new DungeonMilestoneDisplay()); + MinecraftForge.EVENT_BUS.register(new DungeonDeathCounter()); + MinecraftForge.EVENT_BUS.register(new DungeonCleanEnd()); + MinecraftForge.EVENT_BUS.register(new DungeonBossMessages()); + + Commands.init(); + + MinecraftForge.EVENT_BUS.register(new LorenzTest()); + MinecraftForge.EVENT_BUS.register(new ButtonOnPause()); + + configDirectory = new File("mods/SkyHanni/config"); + try { + //noinspection ResultOfMethodCallIgnored + configDirectory.mkdir(); + } catch (Exception ignored) {} + + configFile = new File(configDirectory, "config.json"); + + if (configFile.exists()) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8))) { + feature = gson.fromJson(reader, Features.class); + } catch (Exception ignored) {} + } + + if (feature == null) { + feature = new Features(); + saveConfig(); + } + Runtime.getRuntime().addShutdownHook(new Thread(this::saveConfig)); + } + + public void saveConfig() { + try { + //noinspection ResultOfMethodCallIgnored + configFile.createNewFile(); + + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(configFile), StandardCharsets.UTF_8))) { + writer.write(gson.toJson(feature)); + } + } catch (IOException ignored) {} + } + + public static GuiScreen screenToOpen = null; + private static int screenTicks = 0; + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent event) { + if (screenToOpen != null) { + screenTicks++; + if (screenTicks == 5) { + Minecraft.getMinecraft().displayGuiScreen(screenToOpen); + screenTicks = 0; + screenToOpen = null; + } + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarApi.kt b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarApi.kt new file mode 100644 index 000000000..cbebe951c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarApi.kt @@ -0,0 +1,59 @@ +package at.hannibal2.skyhanni.bazaar + +import at.hannibal2.skyhanni.utils.LorenzUtils + +class BazaarApi { + + companion object { + private val bazaarMap = mutableMapOf<String, BazaarData>() + + fun isBazaarInventory(inventoryName: String): Boolean { + if (inventoryName.contains(" ➜ ") && !inventoryName.contains("Museum")) return true + if (BazaarOrderHelper.isBazaarOrderInventory(inventoryName)) return true + + return when (inventoryName) { + "Your Bazaar Orders" -> true + "How many do you want?" -> true + "How much do you want to pay?" -> true + "Confirm Buy Order" -> true + "Confirm Instant Buy" -> true + "At what price are you selling?" -> true + "Confirm Sell Offer" -> true + "Order options" -> true + + else -> false + } + } + + fun getCleanBazaarName(name: String): String { + if (name.endsWith(" Gemstone")) { + return name.substring(6) + } + if (name.startsWith("§")) { + return name.substring(2) + } + + return name + } + + fun getBazaarDataForName(name: String): BazaarData { + if (bazaarMap.containsKey(name)) { + val bazaarData = bazaarMap[name] + if (bazaarData != null) { + return bazaarData + } + LorenzUtils.error("Bazaar data is null for item '$name'") + } + throw Error("no bz data found for name '$name'") + } + + fun isBazaarItem(name: String): Boolean { + val bazaarName = getCleanBazaarName(name) + return bazaarMap.containsKey(bazaarName) + } + } + + init { + BazaarDataGrabber(bazaarMap).start() + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarData.kt b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarData.kt new file mode 100644 index 000000000..01300e67c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarData.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.bazaar + +data class BazaarData(val apiName: String, val itemName: String, val sellPrice: Double, val buyPrice: Double)
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarDataGrabber.kt b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarDataGrabber.kt new file mode 100644 index 000000000..1f2f1171a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarDataGrabber.kt @@ -0,0 +1,113 @@ +package at.hannibal2.skyhanni.bazaar + +import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import kotlin.concurrent.fixedRateTimer + +internal class BazaarDataGrabber(private var bazaarMap: MutableMap<String, BazaarData>) { + + companion object { + private val itemNames = mutableMapOf<String, String>() + + private var lastData = "" + var lastTime = 0L + var blockNoChange = false + var currentlyUpdating = false + } + + private fun loadItemNames(): Boolean { + currentlyUpdating = true + try { + val itemsData = APIUtil.getJSONResponse("https://api.hypixel.net/resources/skyblock/items") + for (element in itemsData["items"].asJsonArray) { + val jsonObject = element.asJsonObject + val name = jsonObject["name"].asString + val id = jsonObject["id"].asString + itemNames[id] = name + } + currentlyUpdating = false + return true + } catch (e: Throwable) { + e.printStackTrace() + LorenzUtils.error("Error while trying to read bazaar item list from api: " + e.message) + currentlyUpdating = false + return false + } + } + + fun start() { + fixedRateTimer(name = "skyhanni-bazaar-update", period = 1000L) { + if (!LorenzUtils.inSkyblock) { + return@fixedRateTimer + } + + if (currentlyUpdating) { + LorenzUtils.error("Bazaar update took too long! Error?") + return@fixedRateTimer + } + + if (itemNames.isEmpty()) { + if (!loadItemNames()) { + return@fixedRateTimer + } + } + checkIfUpdateNeeded() + } + } + + private fun checkIfUpdateNeeded() { + if (lastData != "") { + if (System.currentTimeMillis() - lastTime > 9_000) { + blockNoChange = true + } else { + if (blockNoChange) { + return + } + } + } + + currentlyUpdating = true + updateBazaarData() + currentlyUpdating = false + } + + private fun updateBazaarData() { + val bazaarData = APIUtil.getJSONResponse("https://api.hypixel.net/skyblock/bazaar") + if (bazaarData.toString() != lastData) { + lastData = bazaarData.toString() + lastTime = System.currentTimeMillis() + } + + val products = bazaarData["products"].asJsonObject + + for (entry in products.entrySet()) { + val apiName = entry.key + + if (apiName == "ENCHANTED_CARROT_ON_A_STICK") continue + if (apiName == "BAZAAR_COOKIE") continue + + val itemData = entry.value.asJsonObject + + val itemName = itemNames.getOrDefault(apiName, null) + if (itemName == null) { + LorenzUtils.error("Bazaar item name is null for '$apiName'! Restart to fix this problem!") + continue + } + + val sellPrice: Double = try { + itemData["sell_summary"].asJsonArray[0].asJsonObject["pricePerUnit"].asDouble.round(1) + } catch (e: Exception) { + 0.0 + } + val buyPrice: Double = try { + itemData["buy_summary"].asJsonArray[0].asJsonObject["pricePerUnit"].asDouble.round(1) + } catch (e: Exception) { + 0.0 + } + + val data = BazaarData(apiName, itemName, sellPrice, buyPrice) + bazaarMap[itemName] = data + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarOrderHelper.kt b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarOrderHelper.kt new file mode 100644 index 000000000..eec055253 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/bazaar/BazaarOrderHelper.kt @@ -0,0 +1,87 @@ +package at.hannibal2.skyhanni.bazaar + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.inventory.ContainerChest +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.lwjgl.opengl.GL11 + +class BazaarOrderHelper { + + companion object { + fun isBazaarOrderInventory(inventoryName: String): Boolean = when (inventoryName) { + "Your Bazaar Orders" -> true + "Co-op Bazaar Orders" -> true + else -> false + } + } + + @SubscribeEvent + fun onBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!SkyHanniMod.feature.bazaar.orderHelper) return + if (event.gui !is GuiChest) return + val guiChest = event.gui + val chest = guiChest.inventorySlots as ContainerChest + val inventoryName = chest.lowerChestInventory.displayName.unformattedText.trim() + + if (!isBazaarOrderInventory(inventoryName)) return + val lightingState = GL11.glIsEnabled(GL11.GL_LIGHTING) + GlStateManager.disableLighting() + GlStateManager.color(1f, 1f, 1f, 1f) + + out@ for (slot in chest.inventorySlots) { + if (slot == null) continue + if (slot.slotNumber != slot.slotIndex) continue + if (slot.stack == null) continue + + val stack = slot.stack + val displayName = stack.displayName + val isSelling = displayName.startsWith("§6§lSELL§7: ") + val isBuying = displayName.startsWith("§a§lBUY§7: ") + if (!isSelling && !isBuying) continue + + val text = displayName.split("§7: ")[1] + val name = BazaarApi.getCleanBazaarName(text) + val data = BazaarApi.getBazaarDataForName(name) + val buyPrice = data.buyPrice + val sellPrice = data.sellPrice + + val itemLore = stack.getLore() + for (line in itemLore) { + if (line.startsWith("§7Filled:")) { + if (line.endsWith(" §a§l100%!")) { + slot highlight LorenzColor.GREEN + continue@out + } + } + } + for (line in itemLore) { + if (line.startsWith("§7Price per unit:")) { + var text = line.split(": §6")[1] + text = text.substring(0, text.length - 6) + text = text.replace(",", "") + val price = text.toDouble() + if (isSelling) { + if (buyPrice < price) { + slot highlight LorenzColor.GOLD + continue@out + } + } else { + if (sellPrice > price) { + slot highlight LorenzColor.GOLD + continue@out + } + } + + } + } + } + + if (lightingState) GlStateManager.enableLighting() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/chat/ChatFilter.kt b/src/main/java/at/hannibal2/skyhanni/chat/ChatFilter.kt new file mode 100644 index 000000000..fc245405c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/chat/ChatFilter.kt @@ -0,0 +1,284 @@ +package at.hannibal2.skyhanni.chat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ChatFilter { + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.isOnHypixel) return + + val blockReason = block(event.message) + if (blockReason != "") { + event.blockedReason = blockReason + } + } + + private fun block(message: String): String = when { + message.startsWith("§aYou are playing on profile: §e") -> "profile"//TODO move into own class + lobby(message) && SkyHanniMod.feature.chat.hypixelHub -> "lobby" + empty(message) && SkyHanniMod.feature.chat.empty -> "empty" + warping(message) && SkyHanniMod.feature.chat.warping -> "warping" + welcome(message) && SkyHanniMod.feature.chat.welcome -> "welcome" + guild(message) && SkyHanniMod.feature.chat.others -> "guild" + killCombo(message) && SkyHanniMod.feature.chat.others -> "kill_combo" + bazaarAndAHMiniMessages(message) && SkyHanniMod.feature.chat.others -> "bz_ah_minis" + watchdogAnnouncement(message) && SkyHanniMod.feature.chat.others -> "watchdog" + slayer(message) && SkyHanniMod.feature.chat.others -> "slayer" + slayerDrop(message) && SkyHanniMod.feature.chat.others -> "slayer_drop" + uselessDrop(message) && SkyHanniMod.feature.chat.others -> "useless_drop" + uselessNotification(message) && SkyHanniMod.feature.chat.others -> "useless_notification" + party(message) && SkyHanniMod.feature.chat.others -> "party" + money(message) && SkyHanniMod.feature.chat.others -> "money" + winterIsland(message) && SkyHanniMod.feature.chat.others -> "winter_island" + uselessWarning(message) && SkyHanniMod.feature.chat.others -> "useless_warning" + friendJoin(message) && SkyHanniMod.feature.chat.others -> "friend_join" + + + + + else -> "" + } + + private fun friendJoin(message: String): Boolean { + return when { + message.matchRegex("§aFriend > §r(.*) §r§e(joined|left).") -> { + true + } + else -> false + } + + } + + private fun uselessNotification(message: String): Boolean { + return when { + message == "§eYour previous §r§6Plasmaflux Power Orb §r§ewas removed!" -> true + + message == "§aYou used your §r§6Mining Speed Boost §r§aPickaxe Ability!" -> true + message == "§cYour Mining Speed Boost has expired!" -> true + message == "§a§r§6Mining Speed Boost §r§ais now available!" -> true + + else -> false + } + } + + private fun uselessWarning(message: String): Boolean = when { + message == "§cYou are sending commands too fast! Please slow down." -> true//TODO prevent in the future + message == "§cYou can't use this while in combat!" -> true + message == "§cYou can not modify your equipped armor set!" -> true + message == "§cPlease wait a few seconds between refreshing!" -> true + message == "§cThis item is not salvageable!" -> true//prevent in the future + message == "§cPlace a Dungeon weapon or armor piece above the anvil to salvage it!" -> true + message == "§cWhoa! Slow down there!" -> true + message == "§cWait a moment before confirming!" -> true + message == "§cYou need to be out of combat for 3 seconds before opening the SkyBlock Menu!" -> true//TODO prevent in the future + + else -> false + } + + private fun uselessDrop(message: String): Boolean { + when { + message.matchRegex("§6§lRARE DROP! §r§aEnchanted Ender Pearl (.*)") -> return true + + message.matchRegex("§6§lRARE DROP! §r§fCarrot (.*)") -> return true + message.matchRegex("§6§lRARE DROP! §r§fPotato (.*)") -> return true + + message.matchRegex("§6§lRARE DROP! §r§9Machine Gun Bow (.*)") -> return true + message.matchRegex("§6§lRARE DROP! §r§5Earth Shard (.*)") -> return true + message.matchRegex("§6§lRARE DROP! §r§5Zombie Lord Chestplate (.*)") -> return true + } + + return false + } + + private fun winterIsland(message: String): Boolean = when { + message.matchRegex(" §r§f☃ §r§7§r(.*) §r§7mounted a §r§fSnow Cannon§r§7!") -> true + + else -> false + } + + private fun money(message: String): Boolean { + if (isBazaar(message)) return true + if (isAuctionHouse(message)) return true + + return false + } + + private fun isAuctionHouse(message: String): Boolean { + if (message == "§b-----------------------------------------------------") return true + if (message == "§eVisit the Auction House to collect your item!") return true + + return false + } + + private fun isBazaar(message: String): Boolean { + if (message.matchRegex("§eBuy Order Setup! §r§a(.*)§r§7x (.*) §r§7for §r§6(.*) coins§r§7.")) return true + if (message.matchRegex("§eSell Offer Setup! §r§a(.*)§r§7x (.*) §r§7for §r§6(.*) coins§r§7.")) return true + if (message.matchRegex("§cCancelled! §r§7Refunded §r§6(.*) coins §r§7from cancelling buy order!")) return true + if (message.matchRegex("§cCancelled! §r§7Refunded §r§a(.*)§r§7x (.*) §r§7from cancelling sell offer!")) return true + + return false + } + + private fun party(message: String): Boolean { + if (message == "§9§m-----------------------------") return true + if (message == "§9§m-----------------------------------------------------") return true + + return false + } + + private fun slayerDrop(message: String): Boolean { + //Revenant + if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§9Revenant Viscera§r§7\\) (.*)")) return true + if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§7(.*)x §r§f§r§9Foul Flesh§r§7\\) (.*)")) return true + if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§9Foul Flesh§r§7\\) (.*)")) return true + if (message.matchRegex("§6§lRARE DROP! §r§5Golden Powder (.*)")) return true + if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§2(.*) Pestilence Rune I§r§7\\) (.*)")) { + LorenzUtils.debug("check regex for this blocked message!") + return true + } + if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§5Revenant Catalyst§r§7\\) (.*)")) return true + if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§9Undead Catalyst§r§7\\) (.*)")) return true + + //Enderman + if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§7(.*)x §r§f§r§aTwilight Arrow Poison§r§7\\) (.*)")) return true + if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§fMana Steal I§r§7\\) (.*)")) return true + if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§5Sinful Dice§r§7\\) (.*)")) return true + if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§9Null Atom§r§7\\) (.*)")) return true + if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§5Transmission Tuner§r§7\\) (.*)")) return true + if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§fMana Steal I§r§7\\) (.*)")) return true + if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§5◆ Endersnake Rune I§r§7\\) (.*)")) return true + if (message.matchRegex("§d§lCRAZY RARE DROP! §r§7\\(§r§f§r§fPocket Espresso Machine§r§7\\) (.*)")) return true + if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§5◆ End Rune I§r§7\\) (.*)")) return true + + return false + } + + private fun slayer(message: String): Boolean { + //start + if (message.matchRegex(" §r§5§lSLAYER QUEST STARTED!")) return true + if (message.matchRegex(" §5§l» §7Slay §c(.*) Combat XP §7worth of (.*)§7.")) return true + + //end + if (message.matchRegex(" §r§a§lSLAYER QUEST COMPLETE!")) return true + if (message == " §r§6§lNICE! SLAYER BOSS SLAIN!") return true + if (message.matchRegex(" §r§e(.*)Slayer LVL 9 §r§5- §r§a§lLVL MAXED OUT!")) return true + if (message.matchRegex(" §r§5§l» §r§7Talk to Maddox to claim your (.*) Slayer XP!")) return true + + + if (message == "§eYou received kill credit for assisting on a slayer miniboss!") return true + + if (message == "§e✆ Ring... ") return true + if (message == "§e✆ Ring... Ring... ") return true + if (message == "§e✆ Ring... Ring... Ring... ") return true + + return false + } + + private fun watchdogAnnouncement(message: String): Boolean = when { + message == "§4[WATCHDOG ANNOUNCEMENT]" -> true + message.matchRegex("§fWatchdog has banned §r§c§l(.*)§r§f players in the last 7 days.") -> true + message.matchRegex("§fStaff have banned an additional §r§c§l(.*)§r§f in the last 7 days.") -> true + message == "§cBlacklisted modifications are a bannable offense!" -> true + else -> false + } + + private fun bazaarAndAHMiniMessages(message: String): Boolean = when (message) { + "§7Putting item in escrow...", + "§7Putting goods in escrow...", + "§7Putting coins in escrow...", + + //Auction House + "§7Setting up the auction...", + "§7Processing purchase...", + "§7Claiming order...", + "§7Processing bid...", + "§7Claiming BIN auction...", + + //Bazaar + "§7Submitting sell offer...", + "§7Submitting buy order...", + "§7Executing instant sell...", + "§7Executing instant buy...", + + //Bank + "§8Depositing coins...", + "§8Withdrawing coins..." -> true + else -> false + } + + private fun killCombo(message: String): Boolean { + //§a§l+5 Kill Combo §r§8+§r§b3% §r§b? Magic Find + return when { + message.matchRegex("§.§l\\+(.*) Kill Combo §r§8\\+(.*)") -> true + message.matchRegex("§cYour Kill Combo has expired! You reached a (.*) Kill Combo!") -> true + else -> false + } + } + + private fun lobby(message: String): Boolean = when { + //player join + message.matchRegex("(.*) §6joined the lobby!") -> true + message.matchRegex(" §b>§c>§a>§r (.*) §6joined the lobby!§r §a<§c<§b<") -> true + + //mystery box + message.matchRegex("§b✦ §r(.*) §r§7found a §r§e(.*) §r§bMystery Box§r§7!") -> true + message.matchRegex("§b✦ §r(.*) §r§7found (a|an) §r(.*) §r§7in a §r§aMystery Box§r§7!") -> true + + //prototype + message.contains("§r§6§lWelcome to the Prototype Lobby§r") -> true + message == " §r§f§l➤ §r§6You have reached your Hype limit! Add Hype to Prototype Lobby minigames by right-clicking with the Hype Diamond!" -> true + + //hypixel tournament notifications + message.contains("§r§e§6§lHYPIXEL§e is hosting a §b§lBED WARS DOUBLES§e tournament!") -> true + message.contains("§r§e§6§lHYPIXEL BED WARS DOUBLES§e tournament is live!") -> true + + //other + message.contains("§aYou are still radiating with §bGenerosity§r§a!") -> true + else -> false + } + + private fun guild(message: String): Boolean = when { + message.matchRegex("§2Guild > (.*) §r§e(joined|left).") -> true + message.matchRegex("§aYou earned §r§2(.*) GEXP §r§afrom playing SkyBlock!") -> true + message.matchRegex("§aYou earned §r§2(.*) GEXP §r§a\\+ §r§e(.*) Event EXP §r§afrom playing SkyBlock!") -> true + message == "§b§m-----------------------------------------------------" -> true + else -> false + } + + private fun welcome(message: String): Boolean = message == "§eWelcome to §r§aHypixel SkyBlock§r§e!" + + private fun warping(message: String): Boolean = when { + message.matchRegex("§7Sending to server (.*)\\.\\.\\.") -> true + message.matchRegex("§7Request join for Hub (.*)\\.\\.\\.") -> true + message.matchRegex("§7Request join for Dungeon Hub #(.*)\\.\\.\\.") -> true + message == "§7Warping..." -> true + message == "§7Warping you to your SkyBlock island..." -> true + message == "§7Warping using transfer token..." -> true + + //visiting other players + message == "§7Finding player..." -> true + message == "§7Sending a visit request..." -> true + + //warp portals on public islands (canvas room - flower house, election room - community center, void sepulture - the end) + message.matchRegex("§dWarped to (.*)§r§d!") -> true + else -> false + } + + private fun empty(message: String): Boolean = when (message) { + "§8 §r§8 §r§1 §r§3 §r§3 §r§7 §r§8 ", + + "§f §r§f §r§1 §r§0 §r§2 §r§4§r§f §r§f §r§2 §r§0 §r§4 §r§8§r§0§r§1§r§0§r§1§r§2§r§f§r§f§r§0§r§1§r§3§r§4§r§f§r§f§r§0§r§1§r§5§r§f§r§f§r§0§r§1§r§6§r§f§r§f§r§0§r§1§r§8§r§9§r§a§r§b§r§f§r§f§r§0§r§1§r§7§r§f§r§f§r§3 §r§9 §r§2 §r§0 §r§0 §r§1§r§3 §r§9 §r§2 §r§0 §r§0 §r§2§r§3 §r§9 §r§2 §r§0 §r§0 §r§3§r§0§r§0§r§1§r§f§r§e§r§0§r§0§r§2§r§f§r§e§r§0§r§0§r§3§r§4§r§5§r§6§r§7§r§8§r§f§r§e§r§3 §r§6 §r§3 §r§6 §r§3 §r§6 §r§e§r§3 §r§6 §r§3 §r§6 §r§3 §r§6 §r§d", + + "§f §r§r§r§f §r§r§r§1 §r§r§r§0 §r§r§r§2 §r§r§r§f §r§r§r§f §r§r§r§2 §r§r§r§0 §r§r§r§4 §r§r§r§3 §r§r§r§9 §r§r§r§2 §r§r§r§0 §r§r§r§0 §r§r§r§3 §r§r§r§9 §r§r§r§2 §r§r§r§0 §r§r§r§0 §r§r§r§3 §r§r§r§9 §r§r§r§2 §r§r§r§0 §r§r§r§0 ", + + "", + "§f", + "§c" -> true + else -> false + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/chat/ChatManager.kt b/src/main/java/at/hannibal2/skyhanni/chat/ChatManager.kt new file mode 100644 index 000000000..c189caa3d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/chat/ChatManager.kt @@ -0,0 +1,51 @@ +package at.hannibal2.skyhanni.chat + +import at.hannibal2.skyhanni.events.LorenzActionBarEvent +import at.hannibal2.skyhanni.utils.LorenzLogger +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.PacketEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraft.network.play.server.S02PacketChat +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ChatManager { + + private val loggerAll = LorenzLogger("chat/filter_all") + private val loggerFiltered = LorenzLogger("chat/filter_blocked") + private val loggerAllowed = LorenzLogger("chat/filter_allowed") + private val loggerFilteredTypes = mutableMapOf<String, LorenzLogger>() + + @SubscribeEvent(priority = EventPriority.LOW, receiveCanceled = true) + fun onChatPacket(event: PacketEvent.ReceiveEvent) { + val packet = event.packet + if (packet !is S02PacketChat) return + val messageComponent = packet.chatComponent + + val message = LorenzUtils.stripVanillaMessage(messageComponent.formattedText) + if (packet.type.toInt() == 2) { + val actionBarEvent = LorenzActionBarEvent(message) + actionBarEvent.postAndCatch() + } else { + + val chatEvent = LorenzChatEvent(message, messageComponent) + chatEvent.postAndCatch() + + val blockReason = chatEvent.blockedReason.uppercase() + if (blockReason != "") { + event.isCanceled = true + loggerFiltered.log("[$blockReason] $message") + loggerAll.log("[$blockReason] $message") + loggerFilteredTypes.getOrPut(blockReason) { LorenzLogger("chat/filter_blocked/$blockReason") } + .log(message) + return + } + + if (!message.startsWith("§f{\"server\":\"")) { + loggerAllowed.log(message) + loggerAll.log("[allowed] $message") + } + + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/chat/PlayerChatFilter.kt b/src/main/java/at/hannibal2/skyhanni/chat/PlayerChatFilter.kt new file mode 100644 index 000000000..0159d77ec --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/chat/PlayerChatFilter.kt @@ -0,0 +1,90 @@ +package at.hannibal2.skyhanni.chat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.PlayerSendChatEvent +import at.hannibal2.skyhanni.utils.LorenzLogger +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.removeColorCodes +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class PlayerChatFilter { + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.isOnHypixel) return + if (!SkyHanniMod.feature.chat.playerMessages) return + + if (shouldBlock(event.message)) { + event.blockedReason = "player_chat" + } + } + + val loggerPlayerChat = LorenzLogger("chat/player") + + fun shouldBlock(originalMessage: String): Boolean { + val split: List<String> = if (originalMessage.contains("§7§r§7: ")) { + originalMessage.split("§7§r§7: ") + } else if (originalMessage.contains("§f: ")) { + originalMessage.split("§f: ") + } else { + return false + } + + var rawName = split[0] + val message = split[1] + + val channel: PlayerMessageChannel + if (rawName.startsWith("§9Party §8> ")) { + channel = PlayerMessageChannel.PARTY + rawName = rawName.substring(12) + } else if (rawName.startsWith("§2Guild > ")) { + channel = PlayerMessageChannel.GUILD + rawName = rawName.substring(10) + } else if (rawName.startsWith("§bCo-op > ")) { + channel = PlayerMessageChannel.COOP + rawName = rawName.substring(10) + } else { + channel = PlayerMessageChannel.ALL + } + + val nameSplit = rawName.split(" ") + val first = nameSplit[0] + + val last = nameSplit.last() + val name = if (last.endsWith("]")) { + nameSplit[nameSplit.size - 2] + } else { + last + } + + if (first != name) { + if (!first.contains("VIP") && !first.contains("MVP")) { + //TODO support yt + admin + return false + } + } + + send(channel, name.removeColorCodes(), message.removeColorCodes()) + return true + } + + private fun send(channel: PlayerMessageChannel, name: String, message: String) { + loggerPlayerChat.log("[$channel] $name: $message") + val event = PlayerSendChatEvent(channel, name, message) + event.postAndCatch() + + if (event.cancelledReason != "") { + loggerPlayerChat.log("cancelled: " + event.cancelledReason) + } else { + val finalMessage = event.message + if (finalMessage != message) { + loggerPlayerChat.log("message changed: $finalMessage") + } + + val prefix = channel.prefix + LorenzUtils.chat("$prefix §b$name §f$finalMessage") + } + } + +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/chat/PlayerMessageChannel.kt b/src/main/java/at/hannibal2/skyhanni/chat/PlayerMessageChannel.kt new file mode 100644 index 000000000..826b490de --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/chat/PlayerMessageChannel.kt @@ -0,0 +1,10 @@ +package at.hannibal2.skyhanni.chat + +enum class PlayerMessageChannel(val prefix: String) { + + ALL("§fA>"), + ALL_ADVERTISEMENT("§8A>"), + PARTY("§9P>"), + GUILD("§2G>"), + COOP("§bCC>"), +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/config/Features.java b/src/main/java/at/hannibal2/skyhanni/config/Features.java new file mode 100644 index 000000000..90027ce4e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/Features.java @@ -0,0 +1,274 @@ +package at.hannibal2.skyhanni.config; + +import at.hannibal2.skyhanni.config.core.GuiElement; +import at.hannibal2.skyhanni.config.core.GuiScreenElementWrapper; +import at.hannibal2.skyhanni.config.core.config.Position; +import at.hannibal2.skyhanni.config.core.config.annotations.*; +import at.hannibal2.skyhanni.config.core.config.gui.GuiPositionEditor; +import at.hannibal2.skyhanni.SkyHanniMod; +import at.hannibal2.skyhanni.config.config.ConfigEditor; +import com.google.gson.annotations.Expose; +import net.minecraft.client.Minecraft; + +public class Features { + + private void editOverlay(String activeConfig, int width, int height, Position position) { + Minecraft.getMinecraft().displayGuiScreen(new GuiPositionEditor(position, width, height, () -> {}, () -> {}, () -> SkyHanniMod.screenToOpen = new GuiScreenElementWrapper(new ConfigEditor(SkyHanniMod.feature, activeConfig)))); + } + + public void executeRunnable(String runnableId) { + String activeConfigCategory = null; + if (Minecraft.getMinecraft().currentScreen instanceof GuiScreenElementWrapper) { + GuiScreenElementWrapper wrapper = (GuiScreenElementWrapper) Minecraft.getMinecraft().currentScreen; + GuiElement element = wrapper.element; + if (element instanceof ConfigEditor) { + activeConfigCategory = ((ConfigEditor) element).getSelectedCategoryName(); + } + } + + if (runnableId.equals("petDisplay")) { + editOverlay(activeConfigCategory, 200, 16, misc.petDisplayPos); + return; + } + + if (runnableId.equals("testPos")) { + editOverlay(activeConfigCategory, 200, 16, debug.testPos); + return; + } + + if (runnableId.equals("dungeonMilestoneDisplay")) { + editOverlay(activeConfigCategory, 200, 16, dungeon.milestoneDisplayPos); + return; + } + + if (runnableId.equals("dungeonDeathCounter")) { + editOverlay(activeConfigCategory, 200, 16, dungeon.deathCounterDisplay); + return; + } + } + + @Expose + @Category(name = "Chat", desc = "Chat related features.") + public Chat chat = new Chat(); + + @Expose + @Category(name = "Dungeon", desc = "Features that change the catacombs dungeon experience.") + public Dungeon dungeon = new Dungeon(); + + @Expose + @Category(name = "Items", desc = "Changing the behavior around items and the inventory.") + public Items items = new Items(); + + @Expose + @Category(name = "Bazaar", desc = "Bazaar settings.") + public Bazaar bazaar = new Bazaar(); + + @Expose + @Category(name = "Misc", desc = "Settings without a category.") + public Misc misc = new Misc(); + + @Expose + @Category(name = "Debug", desc = "Debug and test stuff.") + public Debug debug = new Debug(); + + public static class Chat { + + @Expose + @ConfigOption(name = "Chat Filter Types", desc = "") + @ConfigEditorAccordion(id = 1) + public boolean filterTypes = false; + + @Expose + @ConfigOption(name = "HyPixel Hub", desc = "Block messages outside SkyBlock in the HyPixel lobby: player joins, loot boxes, prototype lobby messages, radiating generosity and HyPixel tournaments.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 1) + public boolean hypixelHub = false; + + @Expose + @ConfigOption(name = "Empty", desc = "Hide all the empty messages from the chat.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 1) + public boolean empty = false; + + @Expose + @ConfigOption(name = "Warping", desc = "Block 'sending request to join ..' and 'warping ..' messages.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 1) + public boolean warping = false; + + @Expose + @ConfigOption(name = "Welcome", desc = "Hide the 'welcome to skyblock' message.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 1) + public boolean welcome = false; + + //TODO remove + @Expose + @ConfigOption(name = "Others", desc = "Hide other annoying messages.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 1) + public boolean others = false; + + @Expose + @ConfigOption(name = "Player Messages", desc = "Add a fancy new chat format for player messages.") + @ConfigEditorBoolean + public boolean playerMessages = false; + + @Expose + @ConfigOption(name = "Dungeon Filter", desc = "Hide annoying messages inside dungeon.") + @ConfigEditorBoolean + public boolean dungeonMessages = false; + + @Expose + @ConfigOption(name = "Dungeon Boss Messages", desc = "Hide messages from watcher and bosses inside dungeon.") + @ConfigEditorBoolean + public boolean dungeonBossMessages = false; + } + + public static class Dungeon { + + @Expose + @ConfigOption(name = "Clicked Blocks", desc = "Highlight the following blocks when clicked in dungeon: Lever, Chest, Wither Essence") + @ConfigEditorBoolean + public boolean highlightClickedBlocks = false; + + @Expose + @ConfigOption(name = "Boss Damage Indicator", desc = "Show the missing health of a boss in the dungeon and the cooldown time until the boss becomes attackable.") + @ConfigEditorBoolean + public boolean bossDamageIndicator = false; + + @Expose + @ConfigOption(name = "Milestone Display", desc = "Show the current milestone inside Dungeons.") + @ConfigEditorBoolean + public boolean showMilestoneDisplay = false; + + @Expose + @ConfigOption(name = "Milestone Display Position", desc = "") + @ConfigEditorButton(runnableId = "dungeonMilestoneDisplay", buttonText = "Edit") + public Position milestoneDisplayPos = new Position(10, 10, false, true); + + @Expose + @ConfigOption(name = "Death Counter", desc = "Display the total amount of deaths in the current dungeon.") + @ConfigEditorBoolean + public boolean deathCounter = false; + + @Expose + @ConfigOption(name = "Death Counter Position", desc = "") + @ConfigEditorButton(runnableId = "dungeonDeathCounter", buttonText = "Edit") + public Position deathCounterDisplay = new Position(10, 10, false, true); + + @Expose + @ConfigOption(name = "Clean End", desc = "Hide entities and particles after the boss in Floor 1 - 6 has died.") + @ConfigEditorBoolean + public boolean cleanEnd = false; + + @Expose + @ConfigOption(name = "Ignore Guardians", desc = "Ignore F3 and M3 guardians from the clean end feature when sneaking. Makes it easier to kill them after the boss died already. Thanks hypixel.") + @ConfigEditorBoolean + public boolean cleanEndF3IgnoreGuardians = false; + } + + public static class Items { + + @Expose + @ConfigOption(name = "Not Clickable Items", desc = "Hide items that are not clickable in " + "the current inventory: ah, bz, accessory bag, etc") + @ConfigEditorBoolean + public boolean hideNotClickableItems = false; + + @Expose + @ConfigOption(name = "Item number as stack size", desc = "") + @ConfigEditorAccordion(id = 2) + public boolean filterTypes = false; + + @Expose + @ConfigOption(name = "Master Star Number", desc = "Show the Tier of the Master Star.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displayMasterStarNumber = false; + + @Expose + @ConfigOption(name = "Master Skull Number", desc = "Show the tier of the Master Skull accessory.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displayMasterSkullNumber = false; + + @Expose + @ConfigOption(name = "Dungeon Head Floor", desc = "Show the correct floor for golden and diamond heads.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displayDungeonHeadFloor = false; + + @Expose + @ConfigOption(name = "New Year Cake", desc = "Show the Number of the Year of New Year Cakes.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displayNewYearCakeNumber = false; + + @Expose + @ConfigOption(name = "Pet Level", desc = "Show the level of the pet when not maxed.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displayPetLevel = false; + + @Expose + @ConfigOption(name = "Sack Name", desc = "Show an abbreviation of the Sack name.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displaySackName = false; + + @Expose + @ConfigOption(name = "Minion Tier", desc = "Show the Minion Tier over Items.") + @ConfigEditorBoolean + @ConfigAccordionId(id = 2) + public boolean displayMinionTier = false; + + @Expose + @ConfigOption(name = "Ability Cooldown", desc = "Show the cooldown of item abilities.") + @ConfigEditorBoolean + public boolean itemAbilityCooldown = false; + } + + public static class Bazaar { + + @Expose + @ConfigOption(name = "Order Helper", desc = "Show visual hints inside the Bazaar Manage Order view when items are ready to pickup or outbid.") + @ConfigEditorBoolean + public boolean orderHelper = false; + } + + public static class Misc { + + @Expose + @ConfigOption(name = "Pet Display", desc = "Show the currently active pet.") + @ConfigEditorBoolean + public boolean petDisplay = false; + + @Expose + @ConfigOption(name = "Pet Display Position", desc = "") + @ConfigEditorButton(runnableId = "petDisplay", buttonText = "Edit") + public Position petDisplayPos = new Position(10, 10, false, true); + + @Expose + @ConfigOption(name = "Exp Bottles", desc = "Hides all the experience bottles lying on the ground.") + @ConfigEditorBoolean + public boolean hideExpBottles = false; + + @Expose + @ConfigOption(name = "Config Button", desc = "Add a button to the pause menu to configure SkyHanni.") + @ConfigEditorBoolean + public boolean configButtonOnPause = true; + } + + public static class Debug { + + @Expose + @ConfigOption(name = "Enable Test", desc = "Enable Test logic") + @ConfigEditorBoolean + public boolean enabled = false; + + @Expose + @ConfigOption(name = "Test Location", desc = "") + @ConfigEditorButton(runnableId = "testPos", buttonText = "Edit") + public Position testPos = new Position(10, 10, false, true); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/GuiTextures.java b/src/main/java/at/hannibal2/skyhanni/config/GuiTextures.java new file mode 100644 index 000000000..8f96fd9f2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/GuiTextures.java @@ -0,0 +1,32 @@ +package at.hannibal2.skyhanni.config; + +import net.minecraft.util.ResourceLocation; + +public class GuiTextures { + + private GuiTextures() {} + + public static final ResourceLocation DISCORD = new ResourceLocation("skyhanni:discord.png"); + + public static final ResourceLocation button_tex = new ResourceLocation("skyhanni:button.png"); + + public static final ResourceLocation button_white = new ResourceLocation("skyhanni:button_white.png"); + + public static final ResourceLocation BAR = new ResourceLocation("skyhanni:core/bar.png"); + public static final ResourceLocation OFF = new ResourceLocation("skyhanni:core/toggle_off.png"); + public static final ResourceLocation ONE = new ResourceLocation("skyhanni:core/toggle_1.png"); + public static final ResourceLocation TWO = new ResourceLocation("skyhanni:core/toggle_2.png"); + public static final ResourceLocation THREE = new ResourceLocation("skyhanni:core/toggle_3.png"); + public static final ResourceLocation ON = new ResourceLocation("skyhanni:core/toggle_on.png"); + public static final ResourceLocation DELETE = new ResourceLocation("skyhanni:core/delete.png"); + + public static final ResourceLocation slider_off_cap = new ResourceLocation("skyhanni:core/slider/slider_off_cap.png"); + public static final ResourceLocation slider_off_notch = new ResourceLocation("skyhanni:core/slider/slider_off_notch.png"); + public static final ResourceLocation slider_off_segment = new ResourceLocation("skyhanni:core/slider/slider_off_segment.png"); + public static final ResourceLocation slider_on_cap = new ResourceLocation("skyhanni:core/slider/slider_on_cap.png"); + public static final ResourceLocation slider_on_notch = new ResourceLocation("skyhanni:core/slider/slider_on_notch.png"); + public static final ResourceLocation slider_on_segment = new ResourceLocation("skyhanni:core/slider/slider_on_segment.png"); + public static final ResourceLocation slider_button_new = new ResourceLocation("skyhanni:core/slider/slider_button.png"); + + public static final ResourceLocation mapOverlay = new ResourceLocation("skyhanni", "maps/map_overlay.png"); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.java b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.java new file mode 100644 index 000000000..dbf4e12c9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.java @@ -0,0 +1,26 @@ +package at.hannibal2.skyhanni.config.commands; + +import at.hannibal2.skyhanni.SkyHanniMod; +import at.hannibal2.skyhanni.config.core.GuiScreenElementWrapper; +import at.hannibal2.skyhanni.config.config.ConfigEditor; +import net.minecraft.command.ICommandSender; +import net.minecraftforge.client.ClientCommandHandler; +import org.apache.commons.lang3.StringUtils; + +public class Commands { + + private static final SimpleCommand.ProcessCommandRunnable settingsRunnable = new SimpleCommand.ProcessCommandRunnable() { + public void processCommand(ICommandSender sender, String[] args) { + if (args.length > 0) { + SkyHanniMod.screenToOpen = new GuiScreenElementWrapper(new ConfigEditor(SkyHanniMod.feature, StringUtils.join(args, " "))); + } else { + SkyHanniMod.screenToOpen = new GuiScreenElementWrapper(new ConfigEditor(SkyHanniMod.feature)); + } + } + }; + + public static void init() { + ClientCommandHandler.instance.registerCommand(new SimpleCommand("sh", settingsRunnable)); + ClientCommandHandler.instance.registerCommand(new SimpleCommand("skyhanni", settingsRunnable)); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/SimpleCommand.java b/src/main/java/at/hannibal2/skyhanni/config/commands/SimpleCommand.java new file mode 100644 index 000000000..de03ee2bd --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/SimpleCommand.java @@ -0,0 +1,60 @@ +package at.hannibal2.skyhanni.config.commands; + +import java.util.List; +import net.minecraft.command.CommandBase; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.BlockPos; + +/** + @author Moulberry + **/ +public class SimpleCommand extends CommandBase { + + private final String commandName; + private final ProcessCommandRunnable runnable; + private TabCompleteRunnable tabRunnable; + + public SimpleCommand(String commandName, ProcessCommandRunnable runnable) { + this.commandName = commandName; + this.runnable = runnable; + } + + public SimpleCommand(String commandName, ProcessCommandRunnable runnable, TabCompleteRunnable tabRunnable) { + this.commandName = commandName; + this.runnable = runnable; + this.tabRunnable = tabRunnable; + } + + public abstract static class ProcessCommandRunnable { + + public abstract void processCommand(ICommandSender sender, String[] args); + } + + public abstract static class TabCompleteRunnable { + + public abstract List<String> tabComplete(ICommandSender sender, String[] args, BlockPos pos); + } + + public boolean canCommandSenderUseCommand(ICommandSender sender) { + return true; + } + + public String getCommandName() { + return commandName; + } + + public String getCommandUsage(ICommandSender sender) { + return "/" + commandName; + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + runnable.processCommand(sender, args); + } + + @Override + public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) { + if (tabRunnable != null) return tabRunnable.tabComplete(sender, args, pos); + return null; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/config/ConfigEditor.java b/src/main/java/at/hannibal2/skyhanni/config/config/ConfigEditor.java new file mode 100644 index 000000000..72e46ad1d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/config/ConfigEditor.java @@ -0,0 +1,602 @@ +package at.hannibal2.skyhanni.config.config; + +import at.hannibal2.skyhanni.SkyHanniMod; +import at.hannibal2.skyhanni.config.Features; +import at.hannibal2.skyhanni.config.GuiTextures; +import at.hannibal2.skyhanni.config.core.GlScissorStack; +import at.hannibal2.skyhanni.config.core.GuiElement; +import at.hannibal2.skyhanni.config.core.config.gui.GuiOptionEditor; +import at.hannibal2.skyhanni.config.core.config.gui.GuiOptionEditorAccordion; +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.lerp.LerpUtils; +import at.hannibal2.skyhanni.config.core.util.lerp.LerpingInteger; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; + +import com.google.common.collect.Lists; +import java.awt.*; +import java.net.URI; +import java.util.*; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class ConfigEditor extends GuiElement { + + private static final ResourceLocation[] socialsIco = new ResourceLocation[] { GuiTextures.DISCORD }; + private static final String[] socialsLink = new String[] { "https://discord.gg/8DXVN4BJz3" }; + + private final long openedMillis; + + private String selectedCategory = null; + + private final LerpingInteger optionsScroll = new LerpingInteger(0, 150); + private final LerpingInteger categoryScroll = new LerpingInteger(0, 150); + + private final LinkedHashMap<String, ConfigProcessor.ProcessedCategory> processedConfig; + private final TreeMap<String, Set<ConfigProcessor.ProcessedOption>> searchOptionMap = new TreeMap<>(); + private final HashMap<ConfigProcessor.ProcessedOption, ConfigProcessor.ProcessedCategory> categoryForOption = new HashMap<>(); + + public ConfigEditor(Features config) { + this(config, null); + } + + public ConfigEditor(Features config, String categoryOpen) { + this.openedMillis = System.currentTimeMillis(); + this.processedConfig = ConfigProcessor.create(config); + + for (ConfigProcessor.ProcessedCategory category : processedConfig.values()) { + for (ConfigProcessor.ProcessedOption option : category.options.values()) { + categoryForOption.put(option, category); + } + } + + if (categoryOpen != null) { + for (Map.Entry<String, ConfigProcessor.ProcessedCategory> category : processedConfig.entrySet()) { + if (category.getValue().name.equalsIgnoreCase(categoryOpen)) { + selectedCategory = category.getKey(); + break; + } + } + if (selectedCategory == null) { + for (Map.Entry<String, ConfigProcessor.ProcessedCategory> category : processedConfig.entrySet()) { + if (category.getValue().name.toLowerCase().startsWith(categoryOpen.toLowerCase())) { + selectedCategory = category.getKey(); + break; + } + } + } + if (selectedCategory == null) { + for (Map.Entry<String, ConfigProcessor.ProcessedCategory> category : processedConfig.entrySet()) { + if (category.getValue().name.toLowerCase().contains(categoryOpen.toLowerCase())) { + selectedCategory = category.getKey(); + break; + } + } + } + } + } + + private LinkedHashMap<String, ConfigProcessor.ProcessedCategory> getCurrentConfigEditing() { + return new LinkedHashMap<>(processedConfig); + } + + private LinkedHashMap<String, ConfigProcessor.ProcessedOption> getOptionsInCategory(ConfigProcessor.ProcessedCategory cat) { + return new LinkedHashMap<>(cat.options); + } + + public String getSelectedCategory() { + return selectedCategory; + } + + public String getSelectedCategoryName() { + return processedConfig.get(selectedCategory).name; + } + + private void setSelectedCategory(String category) { + selectedCategory = category; + optionsScroll.setValue(0); + } + + public void render() { + optionsScroll.tick(); + categoryScroll.tick(); + + List<String> tooltipToDisplay = null; + + long currentTime = System.currentTimeMillis(); + long delta = currentTime - openedMillis; + + ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledResolution.getScaledWidth(); + int height = scaledResolution.getScaledHeight(); + int mouseX = Mouse.getX() * width / Minecraft.getMinecraft().displayWidth; + int mouseY = height - Mouse.getY() * height / Minecraft.getMinecraft().displayHeight - 1; + + float opacityFactor = LerpUtils.sigmoidZeroOne(delta / 500f); + RenderUtils.drawGradientRect(0, 0, 0, width, height, (int) (0x80 * opacityFactor) << 24 | 0x101010, (int) (0x90 * opacityFactor) << 24 | 0x101010); + + int xSize = Math.min(scaledResolution.getScaledWidth() - 100 / scaledResolution.getScaleFactor(), 500); + int ySize = Math.min(scaledResolution.getScaledHeight() - 100 / scaledResolution.getScaleFactor(), 400); + + int x = (scaledResolution.getScaledWidth() - xSize) / 2; + int y = (scaledResolution.getScaledHeight() - ySize) / 2; + + int adjScaleFactor = Math.max(2, scaledResolution.getScaleFactor()); + + int openingXSize = xSize; + int openingYSize = ySize; + if (delta < 150) { + openingXSize = (int) (delta * xSize / 150); + openingYSize = 5; + } else if (delta < 300) { + openingYSize = 5 + (int) (delta - 150) * (ySize - 5) / 150; + } + RenderUtils.drawFloatingRectDark((scaledResolution.getScaledWidth() - openingXSize) / 2, (scaledResolution.getScaledHeight() - openingYSize) / 2, openingXSize, openingYSize); + GlScissorStack.clear(); + GlScissorStack.push((scaledResolution.getScaledWidth() - openingXSize) / 2, (scaledResolution.getScaledHeight() - openingYSize) / 2, (scaledResolution.getScaledWidth() + openingXSize) / 2, (scaledResolution.getScaledHeight() + openingYSize) / 2, scaledResolution); + + RenderUtils.drawFloatingRectDark(x + 5, y + 5, xSize - 10, 20, false); + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + TextRenderUtils.drawStringCenteredScaledMaxWidth("SkyHanni " + SkyHanniMod.VERSION + " by " + EnumChatFormatting.RED + "hannibal2" + EnumChatFormatting.RESET + ", config by " + EnumChatFormatting.DARK_PURPLE + "Moulberry", fr, x + xSize / 2f, y + 15, false, 200, 0xa0a0a0); + + RenderUtils.drawFloatingRectDark(x + 4, y + 49 - 20, 140, ySize - 54 + 20, false); + + int innerPadding = 20 / adjScaleFactor; + int innerLeft = x + 4 + innerPadding; + int innerRight = x + 144 - innerPadding; + int innerTop = y + 49 + innerPadding; + int innerBottom = y + ySize - 5 - innerPadding; + Gui.drawRect(innerLeft, innerTop, innerLeft + 1, innerBottom, 0xff08080E); //Left + Gui.drawRect(innerLeft + 1, innerTop, innerRight, innerTop + 1, 0xff08080E); //Top + Gui.drawRect(innerRight - 1, innerTop + 1, innerRight, innerBottom, 0xff28282E); //Right + Gui.drawRect(innerLeft + 1, innerBottom - 1, innerRight - 1, innerBottom, 0xff28282E); //Bottom + Gui.drawRect(innerLeft + 1, innerTop + 1, innerRight - 1, innerBottom - 1, 0x6008080E); //Middle + + GlScissorStack.push(0, innerTop + 1, scaledResolution.getScaledWidth(), innerBottom - 1, scaledResolution); + + float catBarSize = 1; + int catY = -categoryScroll.getValue(); + + LinkedHashMap<String, ConfigProcessor.ProcessedCategory> currentConfigEditing = getCurrentConfigEditing(); + for (Map.Entry<String, ConfigProcessor.ProcessedCategory> entry : currentConfigEditing.entrySet()) { + String selectedCategory = getSelectedCategory(); + if (selectedCategory == null || !currentConfigEditing.containsKey(selectedCategory)) { + setSelectedCategory(entry.getKey()); + } + String catName = entry.getValue().name; + if (entry.getKey().equals(getSelectedCategory())) { + catName = EnumChatFormatting.DARK_AQUA.toString() + EnumChatFormatting.UNDERLINE + catName; + } else { + catName = EnumChatFormatting.GRAY + catName; + } + TextRenderUtils.drawStringCenteredScaledMaxWidth(catName, fr, x + 75, y + 70 + catY, false, 100, -1); + catY += 15; + if (catY > 0) { + catBarSize = LerpUtils.clampZeroOne((float) (innerBottom - innerTop - 2) / (catY + 5 + categoryScroll.getValue())); + } + } + + float catBarStart = categoryScroll.getValue() / (float) (catY + categoryScroll.getValue()); + float catBarEnd = catBarStart + catBarSize; + if (catBarEnd > 1) { + catBarEnd = 1; + if (categoryScroll.getTarget() / (float) (catY + categoryScroll.getValue()) + catBarSize < 1) { + int target = optionsScroll.getTarget(); + categoryScroll.setValue((int) Math.ceil((catY + 5 + categoryScroll.getValue()) - catBarSize * (catY + 5 + categoryScroll.getValue()))); + categoryScroll.setTarget(target); + } else { + categoryScroll.setValue((int) Math.ceil((catY + 5 + categoryScroll.getValue()) - catBarSize * (catY + 5 + categoryScroll.getValue()))); + } + } + int catDist = innerBottom - innerTop - 12; + Gui.drawRect(innerLeft + 2, innerTop + 5, innerLeft + 7, innerBottom - 5, 0xff101010); + Gui.drawRect(innerLeft + 3, innerTop + 6 + (int) (catDist * catBarStart), innerLeft + 6, innerTop + 6 + (int) (catDist * catBarEnd), 0xff303030); + + GlScissorStack.pop(scaledResolution); + + TextRenderUtils.drawStringCenteredScaledMaxWidth("Categories", fr, x + 75, y + 44, false, 120, 0xa368ef); + + RenderUtils.drawFloatingRectDark(x + 149, y + 29, xSize - 154, ySize - 34, false); + + innerLeft = x + 149 + innerPadding; + innerRight = x + xSize - 5 - innerPadding; + innerBottom = y + ySize - 5 - innerPadding; + + GlStateManager.color(1, 1, 1, 1); + int rightStuffLen = 20; + + if (getSelectedCategory() != null && currentConfigEditing.containsKey(getSelectedCategory())) { + ConfigProcessor.ProcessedCategory cat = currentConfigEditing.get(getSelectedCategory()); + + TextRenderUtils.drawStringScaledMaxWidth(cat.desc, fr, innerLeft + 5, y + 40, true, innerRight - innerLeft - rightStuffLen - 10, 0xb0b0b0); + } + + Gui.drawRect(innerLeft, innerTop, innerLeft + 1, innerBottom, 0xff08080E); //Left + Gui.drawRect(innerLeft + 1, innerTop, innerRight, innerTop + 1, 0xff08080E); //Top + Gui.drawRect(innerRight - 1, innerTop + 1, innerRight, innerBottom, 0xff303036); //Right + Gui.drawRect(innerLeft + 1, innerBottom - 1, innerRight - 1, innerBottom, 0xff303036); //Bottom + Gui.drawRect(innerLeft + 1, innerTop + 1, innerRight - 1, innerBottom - 1, 0x6008080E); //Middle + + GlScissorStack.push(innerLeft + 1, innerTop + 1, innerRight - 1, innerBottom - 1, scaledResolution); + float barSize = 1; + int optionY = -optionsScroll.getValue(); + if (getSelectedCategory() != null && currentConfigEditing.containsKey(getSelectedCategory())) { + ConfigProcessor.ProcessedCategory cat = currentConfigEditing.get(getSelectedCategory()); + int optionWidthDefault = innerRight - innerLeft - 20; + GlStateManager.enableDepth(); + HashMap<Integer, Integer> activeAccordions = new HashMap<>(); + for (ConfigProcessor.ProcessedOption option : getOptionsInCategory(cat).values()) { + int optionWidth = optionWidthDefault; + if (option.accordionId >= 0) { + if (!activeAccordions.containsKey(option.accordionId)) { + continue; + } + int accordionDepth = activeAccordions.get(option.accordionId); + optionWidth = optionWidthDefault - (2 * innerPadding) * (accordionDepth + 1); + } + + GuiOptionEditor editor = option.editor; + if (editor == null) { + continue; + } + if (editor instanceof GuiOptionEditorAccordion) { + GuiOptionEditorAccordion accordion = (GuiOptionEditorAccordion) editor; + if (accordion.getToggled()) { + int accordionDepth = 0; + if (option.accordionId >= 0) { + accordionDepth = activeAccordions.get(option.accordionId) + 1; + } + activeAccordions.put(accordion.getAccordionId(), accordionDepth); + } + } + int optionHeight = editor.getHeight(); + if (innerTop + 5 + optionY + optionHeight > innerTop + 1 && innerTop + 5 + optionY < innerBottom - 1) { + editor.render((innerLeft + innerRight - optionWidth) / 2 - 5, innerTop + 5 + optionY, optionWidth); + } + optionY += optionHeight + 5; + } + GlStateManager.disableDepth(); + if (optionY > 0) { + barSize = LerpUtils.clampZeroOne((float) (innerBottom - innerTop - 2) / (optionY + 5 + optionsScroll.getValue())); + } + } + + GlScissorStack.pop(scaledResolution); + + GL11.glDisable(GL11.GL_SCISSOR_TEST); + if (getSelectedCategory() != null && currentConfigEditing.containsKey(getSelectedCategory())) { + int optionYOverlay = -optionsScroll.getValue(); + ConfigProcessor.ProcessedCategory cat = currentConfigEditing.get(getSelectedCategory()); + int optionWidthDefault = innerRight - innerLeft - 20; + + GlStateManager.translate(0, 0, 10); + GlStateManager.enableDepth(); + HashMap<Integer, Integer> activeAccordions = new HashMap<>(); + for (ConfigProcessor.ProcessedOption option : getOptionsInCategory(cat).values()) { + int optionWidth = optionWidthDefault; + if (option.accordionId >= 0) { + if (!activeAccordions.containsKey(option.accordionId)) { + continue; + } + int accordionDepth = activeAccordions.get(option.accordionId); + optionWidth = optionWidthDefault - (2 * innerPadding) * (accordionDepth + 1); + } + + GuiOptionEditor editor = option.editor; + if (editor == null) { + continue; + } + if (editor instanceof GuiOptionEditorAccordion) { + GuiOptionEditorAccordion accordion = (GuiOptionEditorAccordion) editor; + if (accordion.getToggled()) { + int accordionDepth = 0; + if (option.accordionId >= 0) { + accordionDepth = activeAccordions.get(option.accordionId) + 1; + } + activeAccordions.put(accordion.getAccordionId(), accordionDepth); + } + } + int optionHeight = editor.getHeight(); + if (innerTop + 5 + optionYOverlay + optionHeight > innerTop + 1 && innerTop + 5 + optionYOverlay < innerBottom - 1) { + editor.renderOverlay((innerLeft + innerRight - optionWidth) / 2 - 5, innerTop + 5 + optionYOverlay, optionWidth); + } + optionYOverlay += optionHeight + 5; + } + GlStateManager.disableDepth(); + GlStateManager.translate(0, 0, -10); + } + GL11.glEnable(GL11.GL_SCISSOR_TEST); + + float barStart = optionsScroll.getValue() / (float) (optionY + optionsScroll.getValue()); + float barEnd = barStart + barSize; + if (barEnd > 1) { + barEnd = 1; + if (optionsScroll.getTarget() / (float) (optionY + optionsScroll.getValue()) + barSize < 1) { + int target = optionsScroll.getTarget(); + optionsScroll.setValue((int) Math.ceil((optionY + 5 + optionsScroll.getValue()) - barSize * (optionY + 5 + optionsScroll.getValue()))); + optionsScroll.setTarget(target); + } else { + optionsScroll.setValue((int) Math.ceil((optionY + 5 + optionsScroll.getValue()) - barSize * (optionY + 5 + optionsScroll.getValue()))); + } + } + int dist = innerBottom - innerTop - 12; + Gui.drawRect(innerRight - 10, innerTop + 5, innerRight - 5, innerBottom - 5, 0xff101010); + Gui.drawRect(innerRight - 9, innerTop + 6 + (int) (dist * barStart), innerRight - 6, innerTop + 6 + (int) (dist * barEnd), 0xff303030); + + for (int socialIndex = 0; socialIndex < socialsIco.length; socialIndex++) { + Minecraft.getMinecraft().getTextureManager().bindTexture(socialsIco[socialIndex]); + GlStateManager.color(1, 1, 1, 1); + int socialLeft = x + xSize - 23 - 18 * socialIndex; + RenderUtils.drawTexturedRect(socialLeft, y + 7, 16, 16, GL11.GL_LINEAR); + + if (mouseX >= socialLeft && mouseX <= socialLeft + 16 && mouseY >= y + 6 && mouseY <= y + 23) { + tooltipToDisplay = Lists.newArrayList(EnumChatFormatting.YELLOW + "Go to: " + EnumChatFormatting.RESET + socialsLink[socialIndex]); + } + } + + GlScissorStack.clear(); + + if (tooltipToDisplay != null) { + TextRenderUtils.drawHoveringText(tooltipToDisplay, mouseX, mouseY, width, height, -1, fr); + } + + GlStateManager.translate(0, 0, -2); + } + + public boolean mouseInput(int mouseX, int mouseY) { + ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledResolution.getScaledWidth(); + int height = scaledResolution.getScaledHeight(); + + int xSize = Math.min(width - 100 / scaledResolution.getScaleFactor(), 500); + int ySize = Math.min(height - 100 / scaledResolution.getScaleFactor(), 400); + + int x = (scaledResolution.getScaledWidth() - xSize) / 2; + int y = (scaledResolution.getScaledHeight() - ySize) / 2; + + int adjScaleFactor = Math.max(2, scaledResolution.getScaleFactor()); + + int innerPadding = 20 / adjScaleFactor; + int innerTop = y + 49 + innerPadding; + int innerBottom = y + ySize - 5 - innerPadding; + int innerLeft = x + 149 + innerPadding; + int innerRight = x + xSize - 5 - innerPadding; + + int dWheel = Mouse.getEventDWheel(); + if (mouseY > innerTop && mouseY < innerBottom && dWheel != 0) { + if (dWheel < 0) { + dWheel = -1; + } + if (dWheel > 0) { + dWheel = 1; + } + if (mouseX < innerLeft) { + int newTarget = categoryScroll.getTarget() - dWheel * 30; + if (newTarget < 0) { + newTarget = 0; + } + + float catBarSize = 1; + int catY = -newTarget; + for (Map.Entry<String, ConfigProcessor.ProcessedCategory> entry : getCurrentConfigEditing().entrySet()) { + if (getSelectedCategory() == null) { + setSelectedCategory(entry.getKey()); + } + + catY += 15; + if (catY > 0) { + catBarSize = LerpUtils.clampZeroOne((float) (innerBottom - innerTop - 2) / (catY + 5 + newTarget)); + } + } + + int barMax = (int) Math.floor((catY + 5 + newTarget) - catBarSize * (catY + 5 + newTarget)); + if (newTarget > barMax) { + newTarget = barMax; + } + categoryScroll.resetTimer(); + categoryScroll.setTarget(newTarget); + } else { + int newTarget = optionsScroll.getTarget() - dWheel * 30; + if (newTarget < 0) { + newTarget = 0; + } + + float barSize = 1; + int optionY = -newTarget; + if (getSelectedCategory() != null && getCurrentConfigEditing() != null && getCurrentConfigEditing().containsKey(getSelectedCategory())) { + ConfigProcessor.ProcessedCategory cat = getCurrentConfigEditing().get(getSelectedCategory()); + HashMap<Integer, Integer> activeAccordions = new HashMap<>(); + for (ConfigProcessor.ProcessedOption option : getOptionsInCategory(cat).values()) { + if (option.accordionId >= 0) { + if (!activeAccordions.containsKey(option.accordionId)) { + continue; + } + } + + GuiOptionEditor editor = option.editor; + if (editor == null) { + continue; + } + if (editor instanceof GuiOptionEditorAccordion) { + GuiOptionEditorAccordion accordion = (GuiOptionEditorAccordion) editor; + if (accordion.getToggled()) { + int accordionDepth = 0; + if (option.accordionId >= 0) { + accordionDepth = activeAccordions.get(option.accordionId) + 1; + } + activeAccordions.put(accordion.getAccordionId(), accordionDepth); + } + } + optionY += editor.getHeight() + 5; + + if (optionY > 0) { + barSize = LerpUtils.clampZeroOne((float) (innerBottom - innerTop - 2) / (optionY + 5 + newTarget)); + } + } + } + + int barMax = (int) Math.floor((optionY + 5 + newTarget) - barSize * (optionY + 5 + newTarget)); + if (newTarget > barMax) { + newTarget = barMax; + } + optionsScroll.setTimeToReachTarget(Math.min(150, Math.max(10, 5 * Math.abs(newTarget - optionsScroll.getValue())))); + optionsScroll.resetTimer(); + optionsScroll.setTarget(newTarget); + } + } else if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + if (getCurrentConfigEditing() != null) { + int catY = -categoryScroll.getValue(); + for (Map.Entry<String, ConfigProcessor.ProcessedCategory> entry : getCurrentConfigEditing().entrySet()) { + if (getSelectedCategory() == null) { + setSelectedCategory(entry.getKey()); + } + if (mouseX >= x + 5 && mouseX <= x + 145 && mouseY >= y + 70 + catY - 7 && mouseY <= y + 70 + catY + 7) { + setSelectedCategory(entry.getKey()); + return true; + } + catY += 15; + } + } + + for (int socialIndex = 0; socialIndex < socialsLink.length; socialIndex++) { + int socialLeft = x + xSize - 23 - 18 * socialIndex; + + if (mouseX >= socialLeft && mouseX <= socialLeft + 16 && mouseY >= y + 6 && mouseY <= y + 23) { + try { + Desktop.getDesktop().browse(new URI(socialsLink[socialIndex])); + } catch (Exception ignored) {} + return true; + } + } + } + + int optionY = -optionsScroll.getValue(); + if (getSelectedCategory() != null && getCurrentConfigEditing() != null && getCurrentConfigEditing().containsKey(getSelectedCategory())) { + int optionWidthDefault = innerRight - innerLeft - 20; + ConfigProcessor.ProcessedCategory cat = getCurrentConfigEditing().get(getSelectedCategory()); + HashMap<Integer, Integer> activeAccordions = new HashMap<>(); + for (ConfigProcessor.ProcessedOption option : getOptionsInCategory(cat).values()) { + int optionWidth = optionWidthDefault; + if (option.accordionId >= 0) { + if (!activeAccordions.containsKey(option.accordionId)) { + continue; + } + int accordionDepth = activeAccordions.get(option.accordionId); + optionWidth = optionWidthDefault - (2 * innerPadding) * (accordionDepth + 1); + } + + GuiOptionEditor editor = option.editor; + if (editor == null) { + continue; + } + if (editor instanceof GuiOptionEditorAccordion) { + GuiOptionEditorAccordion accordion = (GuiOptionEditorAccordion) editor; + if (accordion.getToggled()) { + int accordionDepth = 0; + if (option.accordionId >= 0) { + accordionDepth = activeAccordions.get(option.accordionId) + 1; + } + activeAccordions.put(accordion.getAccordionId(), accordionDepth); + } + } + if (editor.mouseInputOverlay((innerLeft + innerRight - optionWidth) / 2 - 5, innerTop + 5 + optionY, optionWidth, mouseX, mouseY)) { + return true; + } + optionY += editor.getHeight() + 5; + } + } + + if (mouseX > innerLeft && mouseX < innerRight && mouseY > innerTop && mouseY < innerBottom) { + optionY = -optionsScroll.getValue(); + if (getSelectedCategory() != null && getCurrentConfigEditing() != null && getCurrentConfigEditing().containsKey(getSelectedCategory())) { + int optionWidthDefault = innerRight - innerLeft - 20; + ConfigProcessor.ProcessedCategory cat = getCurrentConfigEditing().get(getSelectedCategory()); + HashMap<Integer, Integer> activeAccordions = new HashMap<>(); + for (ConfigProcessor.ProcessedOption option : getOptionsInCategory(cat).values()) { + int optionWidth = optionWidthDefault; + if (option.accordionId >= 0) { + if (!activeAccordions.containsKey(option.accordionId)) { + continue; + } + int accordionDepth = activeAccordions.get(option.accordionId); + optionWidth = optionWidthDefault - (2 * innerPadding) * (accordionDepth + 1); + } + + GuiOptionEditor editor = option.editor; + if (editor == null) { + continue; + } + if (editor instanceof GuiOptionEditorAccordion) { + GuiOptionEditorAccordion accordion = (GuiOptionEditorAccordion) editor; + if (accordion.getToggled()) { + int accordionDepth = 0; + if (option.accordionId >= 0) { + accordionDepth = activeAccordions.get(option.accordionId) + 1; + } + activeAccordions.put(accordion.getAccordionId(), accordionDepth); + } + } + if (editor.mouseInput((innerLeft + innerRight - optionWidth) / 2 - 5, innerTop + 5 + optionY, optionWidth, mouseX, mouseY)) { + return true; + } + optionY += editor.getHeight() + 5; + } + } + } + + return true; + } + + public boolean keyboardInput() { + ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledResolution.getScaledWidth(); + + int xSize = Math.min(width - 100 / scaledResolution.getScaleFactor(), 500); + + int adjScaleFactor = Math.max(2, scaledResolution.getScaleFactor()); + + int innerPadding = 20 / adjScaleFactor; + int innerWidth = xSize - 154 - innerPadding * 2; + + if (getSelectedCategory() != null && getCurrentConfigEditing() != null && getCurrentConfigEditing().containsKey(getSelectedCategory())) { + ConfigProcessor.ProcessedCategory cat = getCurrentConfigEditing().get(getSelectedCategory()); + HashMap<Integer, Integer> activeAccordions = new HashMap<>(); + for (ConfigProcessor.ProcessedOption option : getOptionsInCategory(cat).values()) { + if (option.accordionId >= 0) { + if (!activeAccordions.containsKey(option.accordionId)) { + continue; + } + } + + GuiOptionEditor editor = option.editor; + if (editor == null) { + continue; + } + if (editor instanceof GuiOptionEditorAccordion) { + GuiOptionEditorAccordion accordion = (GuiOptionEditorAccordion) editor; + if (accordion.getToggled()) { + int accordionDepth = 0; + if (option.accordionId >= 0) { + accordionDepth = activeAccordions.get(option.accordionId) + 1; + } + activeAccordions.put(accordion.getAccordionId(), accordionDepth); + } + } + if (editor.keyboardInput()) { + return true; + } + } + } + + return true; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/BackgroundBlur.java b/src/main/java/at/hannibal2/skyhanni/config/core/BackgroundBlur.java new file mode 100644 index 000000000..eb70e1282 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/BackgroundBlur.java @@ -0,0 +1,249 @@ +package at.hannibal2.skyhanni.config.core; + +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.client.shader.Shader; +import net.minecraft.util.Matrix4f; +import net.minecraftforge.client.event.EntityViewRenderEvent; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +public class BackgroundBlur { + + private static class OutputStuff { + + public Framebuffer framebuffer; + public Shader blurShaderHorz = null; + public Shader blurShaderVert = null; + + public OutputStuff(Framebuffer framebuffer, Shader blurShaderHorz, Shader blurShaderVert) { + this.framebuffer = framebuffer; + this.blurShaderHorz = blurShaderHorz; + this.blurShaderVert = blurShaderVert; + } + } + + private static final HashMap<Float, OutputStuff> blurOutput = new HashMap<>(); + private static final HashMap<Float, Long> lastBlurUse = new HashMap<>(); + private static long lastBlur = 0; + private static final HashSet<Float> requestedBlurs = new HashSet<>(); + + private static int fogColour = 0; + private static boolean registered = false; + + public static void registerListener() { + if (!registered) { + registered = true; + MinecraftForge.EVENT_BUS.register(new BackgroundBlur()); + } + } + + private static boolean shouldBlur = true; + + public static void markDirty() { + if (Minecraft.getMinecraft().theWorld != null) { + shouldBlur = true; + } + } + + public static void processBlurs() { + if (shouldBlur) { + shouldBlur = false; + + long currentTime = System.currentTimeMillis(); + + for (float blur : requestedBlurs) { + lastBlur = currentTime; + lastBlurUse.put(blur, currentTime); + + int width = Minecraft.getMinecraft().displayWidth; + int height = Minecraft.getMinecraft().displayHeight; + + OutputStuff output = blurOutput.computeIfAbsent( + blur, + k -> { + Framebuffer fb = new Framebuffer(width, height, false); + fb.setFramebufferFilter(GL11.GL_NEAREST); + return new OutputStuff(fb, null, null); + } + ); + + if (output.framebuffer.framebufferWidth != width || output.framebuffer.framebufferHeight != height) { + output.framebuffer.createBindFramebuffer(width, height); + if (output.blurShaderHorz != null) { + output.blurShaderHorz.setProjectionMatrix(createProjectionMatrix(width, height)); + } + if (output.blurShaderVert != null) { + output.blurShaderVert.setProjectionMatrix(createProjectionMatrix(width, height)); + } + } + + blurBackground(output, blur); + } + + Set<Float> remove = new HashSet<>(); + for (Map.Entry<Float, Long> entry : lastBlurUse.entrySet()) { + if (currentTime - entry.getValue() > 30 * 1000) { + remove.add(entry.getKey()); + } + } + remove.remove(5f); + + lastBlurUse.keySet().removeAll(remove); + blurOutput.keySet().removeAll(remove); + + requestedBlurs.clear(); + } + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onScreenRender(RenderGameOverlayEvent.Pre event) { + if (event.type == RenderGameOverlayEvent.ElementType.ALL) { + processBlurs(); + } + } + + @SubscribeEvent + public void onFogColour(EntityViewRenderEvent.FogColors event) { + fogColour = 0xff000000; + fogColour |= ((int) (event.red * 255) & 0xFF) << 16; + fogColour |= ((int) (event.green * 255) & 0xFF) << 8; + fogColour |= (int) (event.blue * 255) & 0xFF; + } + + private static Framebuffer blurOutputHorz = null; + + /** + * Creates a projection matrix that projects from our coordinate space [0->width; 0->height] to OpenGL coordinate + * space [-1 -> 1; 1 -> -1] (Note: flipped y-axis). + * + * This is so that we can render to and from the framebuffer in a way that is familiar to us, instead of needing to + * apply scales and translations manually. + */ + private static Matrix4f createProjectionMatrix(int width, int height) { + Matrix4f projMatrix = new Matrix4f(); + projMatrix.setIdentity(); + projMatrix.m00 = 2.0F / (float) width; + projMatrix.m11 = 2.0F / (float) (-height); + projMatrix.m22 = -0.0020001999F; + projMatrix.m33 = 1.0F; + projMatrix.m03 = -1.0F; + projMatrix.m13 = 1.0F; + projMatrix.m23 = -1.0001999F; + return projMatrix; + } + + private static void blurBackground(OutputStuff output, float blurFactor) { + if (!OpenGlHelper.isFramebufferEnabled() || !OpenGlHelper.areShadersSupported()) return; + + int width = Minecraft.getMinecraft().displayWidth; + int height = Minecraft.getMinecraft().displayHeight; + + GlStateManager.matrixMode(GL11.GL_PROJECTION); + GlStateManager.loadIdentity(); + GlStateManager.ortho(0.0D, width, height, 0.0D, 1000.0D, 3000.0D); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + GlStateManager.loadIdentity(); + GlStateManager.translate(0.0F, 0.0F, -2000.0F); + + if (blurOutputHorz == null) { + blurOutputHorz = new Framebuffer(width, height, false); + blurOutputHorz.setFramebufferFilter(GL11.GL_NEAREST); + } + if (blurOutputHorz == null || output == null) { + return; + } + if (blurOutputHorz.framebufferWidth != width || blurOutputHorz.framebufferHeight != height) { + blurOutputHorz.createBindFramebuffer(width, height); + Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false); + } + + if (output.blurShaderHorz == null) { + try { + output.blurShaderHorz = new Shader(Minecraft.getMinecraft().getResourceManager(), "blur", output.framebuffer, blurOutputHorz); + output.blurShaderHorz.getShaderManager().getShaderUniform("BlurDir").set(1, 0); + output.blurShaderHorz.setProjectionMatrix(createProjectionMatrix(width, height)); + } catch (Exception ignored) {} + } + if (output.blurShaderVert == null) { + try { + output.blurShaderVert = new Shader(Minecraft.getMinecraft().getResourceManager(), "blur", blurOutputHorz, output.framebuffer); + output.blurShaderVert.getShaderManager().getShaderUniform("BlurDir").set(0, 1); + output.blurShaderVert.setProjectionMatrix(createProjectionMatrix(width, height)); + } catch (Exception ignored) {} + } + if (output.blurShaderHorz != null && output.blurShaderVert != null) { + if (output.blurShaderHorz.getShaderManager().getShaderUniform("Radius") == null) { + //Corrupted shader? + return; + } + + output.blurShaderHorz.getShaderManager().getShaderUniform("Radius").set(blurFactor); + output.blurShaderVert.getShaderManager().getShaderUniform("Radius").set(blurFactor); + + GL11.glPushMatrix(); + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, Minecraft.getMinecraft().getFramebuffer().framebufferObject); + GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, output.framebuffer.framebufferObject); + GL30.glBlitFramebuffer(0, 0, width, height, 0, 0, output.framebuffer.framebufferWidth, output.framebuffer.framebufferHeight, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST); + + output.blurShaderHorz.loadShader(0); + output.blurShaderVert.loadShader(0); + GlStateManager.enableDepth(); + GL11.glPopMatrix(); + + Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false); + } + } + + public static void renderBlurredBackground(float blurStrength, int screenWidth, int screenHeight, int x, int y, int blurWidth, int blurHeight) { + renderBlurredBackground(blurStrength, screenWidth, screenHeight, x, y, blurWidth, blurHeight, false); + } + + /** + * Renders a subsection of the blurred framebuffer on to the corresponding section of the screen. + * Essentially, this method will "blur" the background inside the bounds specified by [x->x+blurWidth, y->y+blurHeight] + */ + public static void renderBlurredBackground(float blurStrength, int screenWidth, int screenHeight, int x, int y, int blurWidth, int blurHeight, boolean forcedUpdate) { + if (!OpenGlHelper.isFramebufferEnabled() || !OpenGlHelper.areShadersSupported()) return; + if (blurStrength < 0.5) return; + requestedBlurs.add(blurStrength); + + long currentTime = System.currentTimeMillis(); + if (currentTime - lastBlur > 300) { + shouldBlur = true; + if (currentTime - lastBlur > 400 && forcedUpdate) return; + } + + if (blurOutput.isEmpty()) return; + + OutputStuff out = blurOutput.get(blurStrength); + if (out == null) { + out = blurOutput.values().iterator().next(); + } + + float uMin = x / (float) screenWidth; + float uMax = (x + blurWidth) / (float) screenWidth; + float vMin = (screenHeight - y) / (float) screenHeight; + float vMax = (screenHeight - y - blurHeight) / (float) screenHeight; + + GlStateManager.depthMask(false); + Gui.drawRect(x, y, x + blurWidth, y + blurHeight, fogColour); + out.framebuffer.bindFramebufferTexture(); + GlStateManager.color(1f, 1f, 1f, 1f); + RenderUtils.drawTexturedRect(x, y, blurWidth, blurHeight, uMin, uMax, vMin, vMax); + out.framebuffer.unbindFramebufferTexture(); + GlStateManager.depthMask(true); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/ChromaColour.java b/src/main/java/at/hannibal2/skyhanni/config/core/ChromaColour.java new file mode 100644 index 000000000..d5966c975 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/ChromaColour.java @@ -0,0 +1,93 @@ +package at.hannibal2.skyhanni.config.core; + +import java.awt.*; + +public class ChromaColour { + + public static String special(int chromaSpeed, int alpha, int rgb) { + return special(chromaSpeed, alpha, (rgb & 0xFF0000) >> 16, (rgb & 0x00FF00) >> 8, (rgb & 0x0000FF)); + } + + private static final int RADIX = 10; + + public static String special(int chromaSpeed, int alpha, int r, int g, int b) { + StringBuilder sb = new StringBuilder(); + sb.append(Integer.toString(chromaSpeed, RADIX)).append(":"); + sb.append(Integer.toString(alpha, RADIX)).append(":"); + sb.append(Integer.toString(r, RADIX)).append(":"); + sb.append(Integer.toString(g, RADIX)).append(":"); + sb.append(Integer.toString(b, RADIX)); + return sb.toString(); + } + + private static int[] decompose(String csv) { + String[] split = csv.split(":"); + + int[] arr = new int[split.length]; + + for (int i = 0; i < split.length; i++) { + arr[i] = Integer.parseInt(split[split.length - 1 - i], RADIX); + } + return arr; + } + + public static int specialToSimpleRGB(String special) { + int[] d = decompose(special); + int r = d[2]; + int g = d[1]; + int b = d[0]; + int a = d[3]; + int chr = d[4]; + + return (a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF); + } + + public static int getSpeed(String special) { + return decompose(special)[4]; + } + + public static float getSecondsForSpeed(int speed) { + return (255 - speed) / 254f * (MAX_CHROMA_SECS - MIN_CHROMA_SECS) + MIN_CHROMA_SECS; + } + + private static final int MIN_CHROMA_SECS = 1; + private static final int MAX_CHROMA_SECS = 60; + + public static long startTime = -1; + + public static int specialToChromaRGB(String special) { + if (startTime < 0) startTime = System.currentTimeMillis(); + + int[] d = decompose(special); + int chr = d[4]; + int a = d[3]; + int r = d[2]; + int g = d[1]; + int b = d[0]; + + float[] hsv = Color.RGBtoHSB(r, g, b, null); + + if (chr > 0) { + float seconds = getSecondsForSpeed(chr); + hsv[0] += (System.currentTimeMillis() - startTime) / 1000f / seconds; + hsv[0] %= 1; + if (hsv[0] < 0) hsv[0] += 1; + } + + return (a & 0xFF) << 24 | (Color.HSBtoRGB(hsv[0], hsv[1], hsv[2]) & 0x00FFFFFF); + } + + public static int rotateHue(int argb, int degrees) { + int a = (argb >> 24) & 0xFF; + int r = (argb >> 16) & 0xFF; + int g = (argb >> 8) & 0xFF; + int b = (argb) & 0xFF; + + float[] hsv = Color.RGBtoHSB(r, g, b, null); + + hsv[0] += degrees / 360f; + hsv[0] %= 1; + + return (a & 0xFF) << 24 | (Color.HSBtoRGB(hsv[0], hsv[1], hsv[2]) & 0x00FFFFFF); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/GlScissorStack.java b/src/main/java/at/hannibal2/skyhanni/config/core/GlScissorStack.java new file mode 100644 index 000000000..1bded14e6 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/GlScissorStack.java @@ -0,0 +1,86 @@ +package at.hannibal2.skyhanni.config.core; + +import java.util.LinkedList; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import org.lwjgl.opengl.GL11; + +public class GlScissorStack { + + private static class Bounds { + + int left; + int top; + int right; + int bottom; + + public Bounds(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + public Bounds createSubBound(int left, int top, int right, int bottom) { + left = Math.max(left, this.left); + top = Math.max(top, this.top); + right = Math.min(right, this.right); + bottom = Math.min(bottom, this.bottom); + + if (top > bottom) { + top = bottom; + } + if (left > right) { + left = right; + } + + return new Bounds(left, top, right, bottom); + } + + public void set(ScaledResolution scaledResolution) { + int height = Minecraft.getMinecraft().displayHeight; + int scale = scaledResolution.getScaleFactor(); + GL11.glScissor(left * scale, height - bottom * scale, (right - left) * scale, (bottom - top) * scale); + } + } + + private static final LinkedList<Bounds> boundsStack = new LinkedList<>(); + + public static void push(int left, int top, int right, int bottom, ScaledResolution scaledResolution) { + if (right < left) { + int temp = right; + right = left; + left = temp; + } + if (bottom < top) { + int temp = bottom; + bottom = top; + top = temp; + } + if (boundsStack.isEmpty()) { + boundsStack.push(new Bounds(left, top, right, bottom)); + } else { + boundsStack.push(boundsStack.peek().createSubBound(left, top, right, bottom)); + } + if (!boundsStack.isEmpty()) { + boundsStack.peek().set(scaledResolution); + } + GL11.glEnable(GL11.GL_SCISSOR_TEST); + } + + public static void pop(ScaledResolution scaledResolution) { + if (!boundsStack.isEmpty()) { + boundsStack.pop(); + } + if (boundsStack.isEmpty()) { + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } else { + boundsStack.peek().set(scaledResolution); + } + } + + public static void clear() { + boundsStack.clear(); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/GuiElement.java b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElement.java new file mode 100644 index 000000000..6e30449ef --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElement.java @@ -0,0 +1,12 @@ +package at.hannibal2.skyhanni.config.core; + +import net.minecraft.client.gui.Gui; + +public abstract class GuiElement extends Gui { + + public abstract void render(); + + public abstract boolean mouseInput(int mouseX, int mouseY); + + public abstract boolean keyboardInput(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementBoolean.java b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementBoolean.java new file mode 100644 index 000000000..02d57de4e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementBoolean.java @@ -0,0 +1,118 @@ +package at.hannibal2.skyhanni.config.core; + +import at.hannibal2.skyhanni.config.GuiTextures; +import at.hannibal2.skyhanni.config.core.util.lerp.LerpUtils; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Mouse; + +public class GuiElementBoolean extends GuiElement { + + public int x; + public int y; + private boolean value; + private final int clickRadius; + private final Consumer<Boolean> toggleCallback; + + private boolean previewValue; + private int animation = 0; + private long lastMillis = 0; + + private static final int xSize = 48; + private static final int ySize = 14; + + public GuiElementBoolean(int x, int y, boolean value, Consumer<Boolean> toggleCallback) { + this(x, y, value, 0, toggleCallback); + } + + public GuiElementBoolean(int x, int y, boolean value, int clickRadius, Consumer<Boolean> toggleCallback) { + this.x = x; + this.y = y; + this.value = value; + this.previewValue = value; + this.clickRadius = clickRadius; + this.toggleCallback = toggleCallback; + this.lastMillis = System.currentTimeMillis(); + + if (value) animation = 36; + } + + @Override + public void render() { + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().getTextureManager().bindTexture(GuiTextures.BAR); + RenderUtils.drawTexturedRect(x, y, xSize, ySize); + + ResourceLocation buttonLoc = GuiTextures.ON; + long currentMillis = System.currentTimeMillis(); + long deltaMillis = currentMillis - lastMillis; + lastMillis = currentMillis; + boolean passedLimit = false; + if (previewValue != value) { + if ((previewValue && animation > 12) || (!previewValue && animation < 24)) { + passedLimit = true; + } + } + if (previewValue != passedLimit) { + animation += deltaMillis / 10; + } else { + animation -= deltaMillis / 10; + } + lastMillis -= deltaMillis % 10; + + if (previewValue == value) { + animation = Math.max(0, Math.min(36, animation)); + } else if (!passedLimit) { + if (previewValue) { + animation = Math.max(0, Math.min(12, animation)); + } else { + animation = Math.max(24, Math.min(36, animation)); + } + } else { + if (previewValue) { + animation = Math.max(12, animation); + } else { + animation = Math.min(24, animation); + } + } + + int animation = (int) (LerpUtils.sigmoidZeroOne(this.animation / 36f) * 36); + if (animation < 3) { + buttonLoc = GuiTextures.OFF; + } else if (animation < 13) { + buttonLoc = GuiTextures.ONE; + } else if (animation < 23) { + buttonLoc = GuiTextures.TWO; + } else if (animation < 33) { + buttonLoc = GuiTextures.THREE; + } + + Minecraft.getMinecraft().getTextureManager().bindTexture(buttonLoc); + RenderUtils.drawTexturedRect(x + animation, y, 12, 14); + } + + @Override + public boolean mouseInput(int mouseX, int mouseY) { + if (mouseX > x - clickRadius && mouseX < x + xSize + clickRadius && mouseY > y - clickRadius && mouseY < y + ySize + clickRadius) { + if (Mouse.getEventButton() == 0) { + if (Mouse.getEventButtonState()) { + previewValue = !value; + } else if (previewValue == !value) { + value = !value; + toggleCallback.accept(value); + } + } + } else { + previewValue = value; + } + return false; + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementColour.java b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementColour.java new file mode 100644 index 000000000..1ab229a38 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementColour.java @@ -0,0 +1,370 @@ +package at.hannibal2.skyhanni.config.core; + +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class GuiElementColour extends GuiElement { + + public static final ResourceLocation colour_selector_dot = new ResourceLocation("notenoughupdates:core/colour_selector_dot.png"); + public static final ResourceLocation colour_selector_bar = new ResourceLocation("notenoughupdates:core/colour_selector_bar.png"); + public static final ResourceLocation colour_selector_bar_alpha = new ResourceLocation("notenoughupdates:core/colour_selector_bar_alpha.png"); + public static final ResourceLocation colour_selector_chroma = new ResourceLocation("notenoughupdates:core/colour_selector_chroma.png"); + + private static final ResourceLocation colourPickerLocation = new ResourceLocation("mbcore:dynamic/colourpicker"); + private static final ResourceLocation colourPickerBarValueLocation = new ResourceLocation("mbcore:dynamic/colourpickervalue"); + private static final ResourceLocation colourPickerBarOpacityLocation = new ResourceLocation("mbcore:dynamic/colourpickeropacity"); + private final GuiElementTextField hexField = new GuiElementTextField("", GuiElementTextField.SCALE_TEXT | GuiElementTextField.FORCE_CAPS | GuiElementTextField.NO_SPACE); + + private final int x; + private final int y; + private int xSize = 119; + private final int ySize = 89; + + private float wheelAngle = 0; + private float wheelRadius = 0; + + private int clickedComponent = -1; + + private final Consumer<String> colourChangedCallback; + private final Runnable closeCallback; + private String colour; + + private final boolean opacitySlider; + private final boolean valueSlider; + + public GuiElementColour(int x, int y, String initialColour, Consumer<String> colourChangedCallback, Runnable closeCallback) { + this(x, y, initialColour, colourChangedCallback, closeCallback, true, true); + } + + public GuiElementColour(int x, int y, String initialColour, Consumer<String> colourChangedCallback, Runnable closeCallback, boolean opacitySlider, boolean valueSlider) { + final ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + + this.y = Math.max(10, Math.min(scaledResolution.getScaledHeight() - ySize - 10, y)); + this.x = Math.max(10, Math.min(scaledResolution.getScaledWidth() - xSize - 10, x)); + + this.colour = initialColour; + this.colourChangedCallback = colourChangedCallback; + this.closeCallback = closeCallback; + + int colour = ChromaColour.specialToSimpleRGB(initialColour); + Color c = new Color(colour); + float[] hsv = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); + updateAngleAndRadius(hsv); + + this.opacitySlider = opacitySlider; + this.valueSlider = valueSlider; + + if (!valueSlider) xSize -= 15; + if (!opacitySlider) xSize -= 15; + } + + public void updateAngleAndRadius(float[] hsv) { + this.wheelRadius = hsv[1]; + this.wheelAngle = hsv[0] * 360; + } + + public void render() { + RenderUtils.drawFloatingRectDark(x, y, xSize, ySize); + + int currentColour = ChromaColour.specialToSimpleRGB(colour); + Color c = new Color(currentColour, true); + float[] hsv = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); + + BufferedImage bufferedImage = new BufferedImage(288, 288, BufferedImage.TYPE_INT_ARGB); + float borderRadius = 0.05f; + if (Keyboard.isKeyDown(Keyboard.KEY_N)) borderRadius = 0; + for (int x = -16; x < 272; x++) { + for (int y = -16; y < 272; y++) { + float radius = (float) Math.sqrt(((x - 128) * (x - 128) + (y - 128) * (y - 128)) / 16384f); + float angle = (float) Math.toDegrees(Math.atan((128 - x) / (y - 128 + 1E-5)) + Math.PI / 2); + if (y < 128) angle += 180; + if (radius <= 1) { + int rgb = Color.getHSBColor(angle / 360f, (float) Math.pow(radius, 1.5f), hsv[2]).getRGB(); + bufferedImage.setRGB(x + 16, y + 16, rgb); + } else if (radius <= 1 + borderRadius) { + float invBlackAlpha = Math.abs(radius - 1 - borderRadius / 2) / borderRadius * 2; + float blackAlpha = 1 - invBlackAlpha; + + if (radius > 1 + borderRadius / 2) { + bufferedImage.setRGB(x + 16, y + 16, (int) (blackAlpha * 255) << 24); + } else { + Color col = Color.getHSBColor(angle / 360f, 1, hsv[2]); + int rgb = (int) (col.getRed() * invBlackAlpha) << 16 | (int) (col.getGreen() * invBlackAlpha) << 8 | (int) (col.getBlue() * invBlackAlpha); + bufferedImage.setRGB(x + 16, y + 16, 0xff000000 | rgb); + } + } + } + } + + BufferedImage bufferedImageValue = new BufferedImage(10, 64, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 64; y++) { + if ((x == 0 || x == 9) && (y == 0 || y == 63)) continue; + + int rgb = Color.getHSBColor(wheelAngle / 360, wheelRadius, (64 - y) / 64f).getRGB(); + bufferedImageValue.setRGB(x, y, rgb); + } + } + + BufferedImage bufferedImageOpacity = new BufferedImage(10, 64, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 64; y++) { + if ((x == 0 || x == 9) && (y == 0 || y == 63)) continue; + + int rgb = (currentColour & 0x00FFFFFF) | (Math.min(255, (64 - y) * 4) << 24); + bufferedImageOpacity.setRGB(x, y, rgb); + } + } + + float selradius = (float) Math.pow(wheelRadius, 1 / 1.5f) * 32; + int selx = (int) (Math.cos(Math.toRadians(wheelAngle)) * selradius); + int sely = (int) (Math.sin(Math.toRadians(wheelAngle)) * selradius); + + int valueOffset = 0; + if (valueSlider) { + valueOffset = 15; + + Minecraft.getMinecraft().getTextureManager().loadTexture(colourPickerBarValueLocation, new DynamicTexture(bufferedImageValue)); + Minecraft.getMinecraft().getTextureManager().bindTexture(colourPickerBarValueLocation); + GlStateManager.color(1, 1, 1, 1); + RenderUtils.drawTexturedRect(x + 5 + 64 + 5, y + 5, 10, 64, GL11.GL_NEAREST); + } + + int opacityOffset = 0; + if (opacitySlider) { + opacityOffset = 15; + + Minecraft.getMinecraft().getTextureManager().bindTexture(colour_selector_bar_alpha); + GlStateManager.color(1, 1, 1, 1); + RenderUtils.drawTexturedRect(x + 5 + 64 + 5 + valueOffset, y + 5, 10, 64, GL11.GL_NEAREST); + + Minecraft.getMinecraft().getTextureManager().loadTexture(colourPickerBarOpacityLocation, new DynamicTexture(bufferedImageOpacity)); + Minecraft.getMinecraft().getTextureManager().bindTexture(colourPickerBarOpacityLocation); + GlStateManager.color(1, 1, 1, 1); + RenderUtils.drawTexturedRect(x + 5 + 64 + 5 + valueOffset, y + 5, 10, 64, GL11.GL_NEAREST); + } + + int chromaSpeed = ChromaColour.getSpeed(colour); + int currentColourChroma = ChromaColour.specialToChromaRGB(colour); + Color cChroma = new Color(currentColourChroma, true); + float[] hsvChroma = Color.RGBtoHSB(cChroma.getRed(), cChroma.getGreen(), cChroma.getBlue(), null); + + if (chromaSpeed > 0) { + Gui.drawRect(x + 5 + 64 + valueOffset + opacityOffset + 5 + 1, y + 5 + 1, x + 5 + 64 + valueOffset + opacityOffset + 5 + 10 - 1, y + 5 + 64 - 1, Color.HSBtoRGB(hsvChroma[0], 0.8f, 0.8f)); + } else { + Gui.drawRect(x + 5 + 64 + valueOffset + opacityOffset + 5 + 1, y + 5 + 27 + 1, x + 5 + 64 + valueOffset + opacityOffset + 5 + 10 - 1, y + 5 + 37 - 1, Color.HSBtoRGB((hsvChroma[0] + (System.currentTimeMillis() - ChromaColour.startTime) / 1000f) % 1, 0.8f, 0.8f)); + } + + Minecraft.getMinecraft().getTextureManager().bindTexture(colour_selector_bar); + GlStateManager.color(1, 1, 1, 1); + if (valueSlider) RenderUtils.drawTexturedRect(x + 5 + 64 + 5, y + 5, 10, 64, GL11.GL_NEAREST); + if (opacitySlider) RenderUtils.drawTexturedRect(x + 5 + 64 + 5 + valueOffset, y + 5, 10, 64, GL11.GL_NEAREST); + + if (chromaSpeed > 0) { + RenderUtils.drawTexturedRect(x + 5 + 64 + valueOffset + opacityOffset + 5, y + 5, 10, 64, GL11.GL_NEAREST); + } else { + Minecraft.getMinecraft().getTextureManager().bindTexture(colour_selector_chroma); + RenderUtils.drawTexturedRect(x + 5 + 64 + valueOffset + opacityOffset + 5, y + 5 + 27, 10, 10, GL11.GL_NEAREST); + } + + if (valueSlider) Gui.drawRect(x + 5 + 64 + 5, y + 5 + 64 - (int) (64 * hsv[2]), x + 5 + 64 + valueOffset, y + 5 + 64 - (int) (64 * hsv[2]) + 1, 0xFF000000); + if (opacitySlider) Gui.drawRect(x + 5 + 64 + 5 + valueOffset, y + 5 + 64 - c.getAlpha() / 4, x + 5 + 64 + valueOffset + opacityOffset, y + 5 + 64 - c.getAlpha() / 4 - 1, 0xFF000000); + if (chromaSpeed > 0) { + Gui.drawRect(x + 5 + 64 + valueOffset + opacityOffset + 5, y + 5 + 64 - (int) (chromaSpeed / 255f * 64), x + 5 + 64 + valueOffset + opacityOffset + 5 + 10, y + 5 + 64 - (int) (chromaSpeed / 255f * 64) + 1, 0xFF000000); + } + + Minecraft.getMinecraft().getTextureManager().loadTexture(colourPickerLocation, new DynamicTexture(bufferedImage)); + Minecraft.getMinecraft().getTextureManager().bindTexture(colourPickerLocation); + GlStateManager.color(1, 1, 1, 1); + RenderUtils.drawTexturedRect(x + 1, y + 1, 72, 72, GL11.GL_LINEAR); + + Minecraft.getMinecraft().getTextureManager().bindTexture(colour_selector_dot); + GlStateManager.color(1, 1, 1, 1); + RenderUtils.drawTexturedRect(x + 5 + 32 + selx - 4, y + 5 + 32 + sely - 4, 8, 8, GL11.GL_NEAREST); + + TextRenderUtils.drawStringCenteredScaledMaxWidth(EnumChatFormatting.GRAY.toString() + Math.round(hsv[2] * 100) + "", Minecraft.getMinecraft().fontRendererObj, x + 5 + 64 + 5 + 5 - (Math.round(hsv[2] * 100) == 100 ? 1 : 0), y + 5 + 64 + 5 + 5, true, 13, -1); + if (opacitySlider) { + TextRenderUtils.drawStringCenteredScaledMaxWidth(EnumChatFormatting.GRAY.toString() + Math.round(c.getAlpha() / 255f * 100) + "", Minecraft.getMinecraft().fontRendererObj, x + 5 + 64 + 5 + valueOffset + 5, y + 5 + 64 + 5 + 5, true, 13, -1); + } + if (chromaSpeed > 0) { + TextRenderUtils.drawStringCenteredScaledMaxWidth(EnumChatFormatting.GRAY.toString() + (int) ChromaColour.getSecondsForSpeed(chromaSpeed) + "s", Minecraft.getMinecraft().fontRendererObj, x + 5 + 64 + 5 + valueOffset + opacityOffset + 6, y + 5 + 64 + 5 + 5, true, 13, -1); + } + + hexField.setSize(48, 10); + if (!hexField.getFocus()) hexField.setText(Integer.toHexString(c.getRGB() & 0xFFFFFF).toUpperCase()); + + StringBuilder sb = new StringBuilder(EnumChatFormatting.GRAY + "#"); + for (int i = 0; i < 6 - hexField.getText().length(); i++) { + sb.append("0"); + } + sb.append(EnumChatFormatting.WHITE); + + hexField.setPrependText(sb.toString()); + hexField.render(x + 5 + 8, y + 5 + 64 + 5); + } + + public boolean mouseInput(int mouseX, int mouseY) { + ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + float mouseXF = (float) (Mouse.getX() * scaledResolution.getScaledWidth_double() / Minecraft.getMinecraft().displayWidth); + float mouseYF = (float) (scaledResolution.getScaledHeight_double() - Mouse.getY() * scaledResolution.getScaledHeight_double() / Minecraft.getMinecraft().displayHeight - 1); + + if ((Mouse.getEventButton() == 0 || Mouse.getEventButton() == 1) && Mouse.getEventButtonState()) { + if (mouseX > x + 5 + 8 && mouseX < x + 5 + 8 + 48) { + if (mouseY > y + 5 + 64 + 5 && mouseY < y + 5 + 64 + 5 + 10) { + hexField.mouseClicked(mouseX, mouseY, Mouse.getEventButton()); + clickedComponent = -1; + return true; + } + } + } + if (!Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + clickedComponent = -1; + } + if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + if (mouseX >= x && mouseX <= x + 119 && mouseY >= y && mouseY <= y + 89) { + hexField.unfocus(); + + int xWheel = mouseX - x - 5; + int yWheel = mouseY - y - 5; + + if (xWheel > 0 && xWheel < 64) { + if (yWheel > 0 && yWheel < 64) { + clickedComponent = 0; + } + } + + int xValue = mouseX - (x + 5 + 64 + 5); + int y = mouseY - this.y - 5; + + int opacityOffset = opacitySlider ? 15 : 0; + int valueOffset = valueSlider ? 15 : 0; + + if (y > -5 && y <= 69) { + if (valueSlider) { + if (xValue > 0 && xValue < 10) { + clickedComponent = 1; + } + } + + if (opacitySlider) { + int xOpacity = mouseX - (x + 5 + 64 + 5 + valueOffset); + + if (xOpacity > 0 && xOpacity < 10) { + clickedComponent = 2; + } + } + } + + int chromaSpeed = ChromaColour.getSpeed(colour); + int xChroma = mouseX - (x + 5 + 64 + valueOffset + opacityOffset + 5); + if (xChroma > 0 && xChroma < 10) { + if (chromaSpeed > 0) { + if (y > -5 && y <= 69) { + clickedComponent = 3; + } + } else if (mouseY > this.y + 5 + 27 && mouseY < this.y + 5 + 37) { + int currentColour = ChromaColour.specialToSimpleRGB(colour); + Color c = new Color(currentColour, true); + colour = ChromaColour.special(200, c.getAlpha(), currentColour); + colourChangedCallback.accept(colour); + } + } + } else { + hexField.unfocus(); + closeCallback.run(); + return false; + } + } + if (Mouse.isButtonDown(0) && clickedComponent >= 0) { + int currentColour = ChromaColour.specialToSimpleRGB(colour); + Color c = new Color(currentColour, true); + float[] hsv = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); + + float xWheel = mouseXF - x - 5; + float yWheel = mouseYF - y - 5; + + if (clickedComponent == 0) { + float angle = (float) Math.toDegrees(Math.atan((32 - xWheel) / (yWheel - 32 + 1E-5)) + Math.PI / 2); + xWheel = Math.max(0, Math.min(64, xWheel)); + yWheel = Math.max(0, Math.min(64, yWheel)); + float radius = (float) Math.sqrt(((xWheel - 32) * (xWheel - 32) + (yWheel - 32) * (yWheel - 32)) / 1024f); + if (yWheel < 32) angle += 180; + + this.wheelAngle = angle; + this.wheelRadius = (float) Math.pow(Math.min(1, radius), 1.5f); + int rgb = Color.getHSBColor(angle / 360f, wheelRadius, hsv[2]).getRGB(); + colour = ChromaColour.special(ChromaColour.getSpeed(colour), c.getAlpha(), rgb); + colourChangedCallback.accept(colour); + return true; + } + + float y = mouseYF - this.y - 5; + y = Math.max(0, Math.min(64, y)); + + if (clickedComponent == 1) { + int rgb = Color.getHSBColor(wheelAngle / 360, wheelRadius, 1 - y / 64f).getRGB(); + colour = ChromaColour.special(ChromaColour.getSpeed(colour), c.getAlpha(), rgb); + colourChangedCallback.accept(colour); + return true; + } + + if (clickedComponent == 2) { + colour = ChromaColour.special(ChromaColour.getSpeed(colour), 255 - Math.round(y / 64f * 255), currentColour); + colourChangedCallback.accept(colour); + return true; + } + + if (clickedComponent == 3) { + colour = ChromaColour.special(255 - Math.round(y / 64f * 255), c.getAlpha(), currentColour); + colourChangedCallback.accept(colour); + } + return true; + } + return false; + } + + public boolean keyboardInput() { + if (Keyboard.getEventKeyState() && hexField.getFocus()) { + if (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) { + hexField.unfocus(); + return true; + } + String old = hexField.getText(); + + hexField.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey()); + + if (hexField.getText().length() > 6) { + hexField.setText(old); + } else { + try { + String text = hexField.getText().toLowerCase(); + + int rgb = Integer.parseInt(text, 16); + int alpha = (ChromaColour.specialToSimpleRGB(colour) >> 24) & 0xFF; + colour = ChromaColour.special(ChromaColour.getSpeed(colour), alpha, rgb); + colourChangedCallback.accept(colour); + + Color c = new Color(rgb); + float[] hsv = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); + updateAngleAndRadius(hsv); + } catch (Exception e) {} + } + + return true; + } + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementTextField.java b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementTextField.java new file mode 100644 index 000000000..91ce080cf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/GuiElementTextField.java @@ -0,0 +1,549 @@ +package at.hannibal2.skyhanni.config.core; + +import at.hannibal2.skyhanni.config.core.util.StringUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import java.awt.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; + +public class GuiElementTextField { + + public static final int SCISSOR_TEXT = 0b10000000; + public static final int DISABLE_BG = 0b1000000; + public static final int SCALE_TEXT = 0b100000; + public static final int NUM_ONLY = 0b10000; + public static final int NO_SPACE = 0b01000; + public static final int FORCE_CAPS = 0b00100; + public static final int COLOUR = 0b00010; + public static final int MULTILINE = 0b00001; + + private int searchBarYSize; + private int searchBarXSize; + private static final int searchBarPadding = 2; + + private int options; + + private boolean focus = false; + + private int x; + private int y; + + private String prependText = ""; + private int customTextColour = 0xffffffff; + + private final GuiTextField textField = new GuiTextField(0, Minecraft.getMinecraft().fontRendererObj, 0, 0, 0, 0); + + private int customBorderColour = -1; + + public GuiElementTextField(String initialText, int options) { + this(initialText, 100, 20, options); + } + + public GuiElementTextField(String initialText, int sizeX, int sizeY, int options) { + textField.setFocused(true); + textField.setCanLoseFocus(false); + textField.setMaxStringLength(999); + textField.setText(initialText); + this.searchBarXSize = sizeX; + this.searchBarYSize = sizeY; + this.options = options; + } + + public void setMaxStringLength(int len) { + textField.setMaxStringLength(len); + } + + public void setCustomBorderColour(int colour) { + this.customBorderColour = colour; + } + + public void setCustomTextColour(int colour) { + this.customTextColour = colour; + } + + public String getText() { + return textField.getText(); + } + + public String getTextDisplay() { + String textNoColour = getText(); + while (true) { + Matcher matcher = PATTERN_CONTROL_CODE.matcher(textNoColour); + if (!matcher.find()) break; + String code = matcher.group(1); + textNoColour = matcher.replaceFirst("\u00B6" + code); + } + + return textNoColour; + } + + public void setPrependText(String text) { + this.prependText = text; + } + + public void setText(String text) { + if (textField.getText() == null || !textField.getText().equals(text)) { + textField.setText(text); + } + } + + public void setSize(int searchBarXSize, int searchBarYSize) { + this.searchBarXSize = searchBarXSize; + this.searchBarYSize = searchBarYSize; + } + + public void setOptions(int options) { + this.options = options; + } + + @Override + public String toString() { + return textField.getText(); + } + + public void setFocus(boolean focus) { + this.focus = focus; + if (!focus) { + textField.setCursorPosition(textField.getCursorPosition()); + } + } + + public boolean getFocus() { + return focus; + } + + public int getHeight() { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int paddingUnscaled = searchBarPadding / scaledresolution.getScaleFactor(); + + int numLines = org.apache.commons.lang3.StringUtils.countMatches(textField.getText(), "\n") + 1; + int extraSize = (searchBarYSize - 8) / 2 + 8; + int bottomTextBox = searchBarYSize + extraSize * (numLines - 1); + + return bottomTextBox + paddingUnscaled * 2; + } + + public int getWidth() { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int paddingUnscaled = searchBarPadding / scaledresolution.getScaleFactor(); + + return searchBarXSize + paddingUnscaled * 2; + } + + private float getScaleFactor(String str) { + return Math.min(1, (searchBarXSize - 2) / (float) Minecraft.getMinecraft().fontRendererObj.getStringWidth(str)); + } + + private boolean isScaling() { + return (options & SCALE_TEXT) != 0; + } + + private static final Pattern PATTERN_CONTROL_CODE = Pattern.compile("(?i)\\u00A7([^\\u00B6]|$)(?!\\u00B6)"); + + public int getCursorPos(int mouseX, int mouseY) { + int xComp = mouseX - x; + int yComp = mouseY - y; + + int extraSize = (searchBarYSize - 8) / 2 + 8; + + String renderText = prependText + textField.getText(); + + int lineNum = Math.round(((yComp - (searchBarYSize - 8) / 2)) / extraSize); + + String text = renderText; + String textNoColour = renderText; + if ((options & COLOUR) != 0) { + while (true) { + Matcher matcher = PATTERN_CONTROL_CODE.matcher(text); + if (!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + if (code.isEmpty()) { + text = matcher.replaceFirst("\u00A7r\u00B6"); + } else { + text = matcher.replaceFirst("\u00A7" + code + "\u00B6" + code); + } + } + } + while (true) { + Matcher matcher = PATTERN_CONTROL_CODE.matcher(textNoColour); + if (!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + textNoColour = matcher.replaceFirst("\u00B6" + code); + } + + int currentLine = 0; + int cursorIndex = 0; + for (; cursorIndex < textNoColour.length(); cursorIndex++) { + if (currentLine == lineNum) break; + if (textNoColour.charAt(cursorIndex) == '\n') { + currentLine++; + } + } + + String textNC = textNoColour.substring(0, cursorIndex); + int colorCodes = org.apache.commons.lang3.StringUtils.countMatches(textNC, "\u00B6"); + String line = text.substring(cursorIndex + (((options & COLOUR) != 0) ? colorCodes * 2 : 0)).split("\n")[0]; + int padding = Math.min(5, searchBarXSize - strLenNoColor(line)) / 2; + String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(line, xComp - padding); + int linePos = strLenNoColor(trimmed); + if (linePos != strLenNoColor(line)) { + char after = line.charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if (trimmedWidth + charWidth / 2 < xComp - padding) { + linePos++; + } + } + cursorIndex += linePos; + + int pre = StringUtils.cleanColour(prependText).length(); + if (cursorIndex < pre) { + cursorIndex = 0; + } else { + cursorIndex -= pre; + } + + return cursorIndex; + } + + public void mouseClicked(int mouseX, int mouseY, int mouseButton) { + if (mouseButton == 1) { + textField.setText(""); + } else { + textField.setCursorPosition(getCursorPos(mouseX, mouseY)); + } + focus = true; + } + + public void unfocus() { + focus = false; + textField.setSelectionPos(textField.getCursorPosition()); + } + + public int strLenNoColor(String str) { + return str.replaceAll("(?i)\\u00A7.", "").length(); + } + + public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + if (focus) { + textField.setSelectionPos(getCursorPos(mouseX, mouseY)); + } + } + + public void keyTyped(char typedChar, int keyCode) { + if (focus) { + if ((options & MULTILINE) != 0) { //Carriage return + Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6\n]|$)(?!\\u00B6)"); + + String text = textField.getText(); + String textNoColour = textField.getText(); + while (true) { + Matcher matcher = patternControlCode.matcher(text); + if (!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + if (code.isEmpty()) { + text = matcher.replaceFirst("\u00A7r\u00B6"); + } else { + text = matcher.replaceFirst("\u00A7" + code + "\u00B6" + code); + } + } + while (true) { + Matcher matcher = patternControlCode.matcher(textNoColour); + if (!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + textNoColour = matcher.replaceFirst("\u00B6" + code); + } + + if (keyCode == 28) { + String before = textField.getText().substring(0, textField.getCursorPosition()); + String after = textField.getText().substring(textField.getCursorPosition()); + int pos = textField.getCursorPosition(); + textField.setText(before + "\n" + after); + textField.setCursorPosition(pos + 1); + return; + } else if (keyCode == 200) { //Up + String textNCBeforeCursor = textNoColour.substring(0, textField.getSelectionEnd()); + int colorCodes = org.apache.commons.lang3.StringUtils.countMatches(textNCBeforeCursor, "\u00B6"); + String textBeforeCursor = text.substring(0, textField.getSelectionEnd() + colorCodes * 2); + + int numLinesBeforeCursor = org.apache.commons.lang3.StringUtils.countMatches(textBeforeCursor, "\n"); + + String[] split = textBeforeCursor.split("\n"); + int textBeforeCursorWidth; + String lineBefore; + String thisLineBeforeCursor; + if (split.length == numLinesBeforeCursor && split.length > 0) { + textBeforeCursorWidth = 0; + lineBefore = split[split.length - 1]; + thisLineBeforeCursor = ""; + } else if (split.length > 1) { + thisLineBeforeCursor = split[split.length - 1]; + lineBefore = split[split.length - 2]; + textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(thisLineBeforeCursor); + } else { + return; + } + String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(lineBefore, textBeforeCursorWidth); + int linePos = strLenNoColor(trimmed); + if (linePos != strLenNoColor(lineBefore)) { + char after = lineBefore.charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if (trimmedWidth + charWidth / 2 < textBeforeCursorWidth) { + linePos++; + } + } + int newPos = textField.getSelectionEnd() - strLenNoColor(thisLineBeforeCursor) - strLenNoColor(lineBefore) - 1 + linePos; + + if (GuiScreen.isShiftKeyDown()) { + textField.setSelectionPos(newPos); + } else { + textField.setCursorPosition(newPos); + } + } else if (keyCode == 208) { //Down + String textNCBeforeCursor = textNoColour.substring(0, textField.getSelectionEnd()); + int colorCodes = org.apache.commons.lang3.StringUtils.countMatches(textNCBeforeCursor, "\u00B6"); + String textBeforeCursor = text.substring(0, textField.getSelectionEnd() + colorCodes * 2); + + int numLinesBeforeCursor = org.apache.commons.lang3.StringUtils.countMatches(textBeforeCursor, "\n"); + + String[] split = textBeforeCursor.split("\n"); + String thisLineBeforeCursor; + int textBeforeCursorWidth; + if (split.length == numLinesBeforeCursor) { + thisLineBeforeCursor = ""; + textBeforeCursorWidth = 0; + } else if (split.length > 0) { + thisLineBeforeCursor = split[split.length - 1]; + textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(thisLineBeforeCursor); + } else { + return; + } + + String[] split2 = textNoColour.split("\n"); + if (split2.length > numLinesBeforeCursor + 1) { + String lineAfter = split2[numLinesBeforeCursor + 1]; + String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(lineAfter, textBeforeCursorWidth); + int linePos = strLenNoColor(trimmed); + if (linePos != strLenNoColor(lineAfter)) { + char after = lineAfter.charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if (trimmedWidth + charWidth / 2 < textBeforeCursorWidth) { + linePos++; + } + } + int newPos = textField.getSelectionEnd() - strLenNoColor(thisLineBeforeCursor) + strLenNoColor(split2[numLinesBeforeCursor]) + 1 + linePos; + + if (GuiScreen.isShiftKeyDown()) { + textField.setSelectionPos(newPos); + } else { + textField.setCursorPosition(newPos); + } + } + } + } + + String old = textField.getText(); + if ((options & FORCE_CAPS) != 0) typedChar = Character.toUpperCase(typedChar); + if ((options & NO_SPACE) != 0 && typedChar == ' ') return; + + if (typedChar == '\u00B6') { + typedChar = '\u00A7'; + } + + textField.setFocused(true); + textField.textboxKeyTyped(typedChar, keyCode); + + if ((options & COLOUR) != 0) { + if (typedChar == '&') { + int pos = textField.getCursorPosition() - 2; + if (pos >= 0 && pos < textField.getText().length()) { + if (textField.getText().charAt(pos) == '&') { + String before = textField.getText().substring(0, pos); + String after = ""; + if (pos + 2 < textField.getText().length()) { + after = textField.getText().substring(pos + 2); + } + textField.setText(before + "\u00A7" + after); + textField.setCursorPosition(pos + 1); + } + } + } else if (typedChar == '*') { + int pos = textField.getCursorPosition() - 2; + if (pos >= 0 && pos < textField.getText().length()) { + if (textField.getText().charAt(pos) == '*') { + String before = textField.getText().substring(0, pos); + String after = ""; + if (pos + 2 < textField.getText().length()) { + after = textField.getText().substring(pos + 2); + } + textField.setText(before + "\u272A" + after); + textField.setCursorPosition(pos + 1); + } + } + } + } + + if ((options & NUM_ONLY) != 0 && textField.getText().matches("[^0-9.]")) textField.setText(old); + } + } + + public void render(int x, int y) { + this.x = x; + this.y = y; + drawTextbox(x, y, searchBarXSize, searchBarYSize, searchBarPadding, textField, focus); + } + + private void drawTextbox(int x, int y, int searchBarXSize, int searchBarYSize, int searchBarPadding, GuiTextField textField, boolean focus) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + String renderText = prependText + textField.getText(); + + GlStateManager.disableLighting(); + + /** + * Search bar + */ + int paddingUnscaled = searchBarPadding / scaledresolution.getScaleFactor(); + if (paddingUnscaled < 1) paddingUnscaled = 1; + + int numLines = org.apache.commons.lang3.StringUtils.countMatches(renderText, "\n") + 1; + int extraSize = (searchBarYSize - 8) / 2 + 8; + int bottomTextBox = y + searchBarYSize + extraSize * (numLines - 1); + + int borderColour = focus ? Color.GREEN.getRGB() : Color.WHITE.getRGB(); + if (customBorderColour != -1) { + borderColour = customBorderColour; + } + if ((options & DISABLE_BG) == 0) { + //bar background + Gui.drawRect(x - paddingUnscaled, y - paddingUnscaled, x + searchBarXSize + paddingUnscaled, bottomTextBox + paddingUnscaled, borderColour); + Gui.drawRect(x, y, x + searchBarXSize, bottomTextBox, Color.BLACK.getRGB()); + } + + //bar text + String text = renderText; + String textNoColor = renderText; + if ((options & COLOUR) != 0) { + while (true) { + Matcher matcher = PATTERN_CONTROL_CODE.matcher(text); + if (!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + if (code.isEmpty()) { + text = matcher.replaceFirst("\u00A7r\u00B6"); + } else { + text = matcher.replaceFirst("\u00A7" + code + "\u00B6" + code); + } + } + } + while (true) { + Matcher matcher = PATTERN_CONTROL_CODE.matcher(textNoColor); + if (!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + textNoColor = matcher.replaceFirst("\u00B6" + code); + } + + int xStartOffset = 5; + float scale = 1; + String[] texts = text.split("\n"); + for (int yOffI = 0; yOffI < texts.length; yOffI++) { + int yOff = yOffI * extraSize; + + if (isScaling() && Minecraft.getMinecraft().fontRendererObj.getStringWidth(texts[yOffI]) > searchBarXSize - 10) { + scale = (searchBarXSize - 2) / (float) Minecraft.getMinecraft().fontRendererObj.getStringWidth(texts[yOffI]); + if (scale > 1) scale = 1; + float newLen = Minecraft.getMinecraft().fontRendererObj.getStringWidth(texts[yOffI]) * scale; + xStartOffset = (int) ((searchBarXSize - newLen) / 2f); + + TextRenderUtils.drawStringCenteredScaledMaxWidth(texts[yOffI], Minecraft.getMinecraft().fontRendererObj, x + searchBarXSize / 2f, y + searchBarYSize / 2f + yOff, false, searchBarXSize - 2, customTextColour); + } else { + if ((options & SCISSOR_TEXT) != 0) { + GlScissorStack.push(x + 5, 0, x + searchBarXSize, scaledresolution.getScaledHeight(), scaledresolution); + Minecraft.getMinecraft().fontRendererObj.drawString(texts[yOffI], x + 5, y + (searchBarYSize - 8) / 2 + yOff, customTextColour); + GlScissorStack.pop(scaledresolution); + } else { + String toRender = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(texts[yOffI], searchBarXSize - 10); + Minecraft.getMinecraft().fontRendererObj.drawString(toRender, x + 5, y + (searchBarYSize - 8) / 2 + yOff, customTextColour); + } + } + } + + if (focus && System.currentTimeMillis() % 1000 > 500) { + String textNCBeforeCursor = textNoColor.substring(0, textField.getCursorPosition() + prependText.length()); + int colorCodes = org.apache.commons.lang3.StringUtils.countMatches(textNCBeforeCursor, "\u00B6"); + String textBeforeCursor = text.substring(0, textField.getCursorPosition() + prependText.length() + (((options & COLOUR) != 0) ? colorCodes * 2 : 0)); + + int numLinesBeforeCursor = org.apache.commons.lang3.StringUtils.countMatches(textBeforeCursor, "\n"); + int yOff = numLinesBeforeCursor * extraSize; + + String[] split = textBeforeCursor.split("\n"); + int textBeforeCursorWidth; + if (split.length <= numLinesBeforeCursor || split.length == 0) { + textBeforeCursorWidth = 0; + } else { + textBeforeCursorWidth = (int) (Minecraft.getMinecraft().fontRendererObj.getStringWidth(split[split.length - 1]) * scale); + } + Gui.drawRect(x + xStartOffset + textBeforeCursorWidth, y + (searchBarYSize - 8) / 2 - 1 + yOff, x + xStartOffset + textBeforeCursorWidth + 1, y + (searchBarYSize - 8) / 2 + 9 + yOff, Color.WHITE.getRGB()); + } + + String selectedText = textField.getSelectedText(); + if (!selectedText.isEmpty()) { + int leftIndex = Math.min(textField.getCursorPosition() + prependText.length(), textField.getSelectionEnd() + prependText.length()); + int rightIndex = Math.max(textField.getCursorPosition() + prependText.length(), textField.getSelectionEnd() + prependText.length()); + + float texX = 0; + int texY = 0; + boolean sectionSignPrev = false; + boolean ignoreNext = false; + boolean bold = false; + for (int i = 0; i < textNoColor.length(); i++) { + if (ignoreNext) { + ignoreNext = false; + continue; + } + + char c = textNoColor.charAt(i); + if (sectionSignPrev) { + if (c != 'k' && c != 'K' && c != 'm' && c != 'M' && c != 'n' && c != 'N' && c != 'o' && c != 'O') { + bold = c == 'l' || c == 'L'; + } + sectionSignPrev = false; + if (i < prependText.length()) continue; + } + if (c == '\u00B6') { + sectionSignPrev = true; + if (i < prependText.length()) continue; + } + + if (c == '\n') { + if (i >= leftIndex && i < rightIndex) { + Gui.drawRect(x + xStartOffset + (int) texX, y + (searchBarYSize - 8) / 2 - 1 + texY, x + xStartOffset + (int) texX + 3, y + (searchBarYSize - 8) / 2 + 9 + texY, Color.LIGHT_GRAY.getRGB()); + } + + texX = 0; + texY += extraSize; + continue; + } + + int len = Minecraft.getMinecraft().fontRendererObj.getStringWidth(String.valueOf(c)); + if (bold) len++; + if (i >= leftIndex && i < rightIndex) { + Gui.drawRect(x + xStartOffset + (int) texX, y + (searchBarYSize - 8) / 2 - 1 + texY, x + xStartOffset + (int) (texX + len * scale), y + (searchBarYSize - 8) / 2 + 9 + texY, Color.LIGHT_GRAY.getRGB()); + + TextRenderUtils.drawStringScaled(String.valueOf(c), Minecraft.getMinecraft().fontRendererObj, x + xStartOffset + texX, y + searchBarYSize / 2f - scale * 8 / 2f + texY, false, Color.BLACK.getRGB(), scale); + if (bold) { + TextRenderUtils.drawStringScaled(String.valueOf(c), Minecraft.getMinecraft().fontRendererObj, x + xStartOffset + texX + 1, y + searchBarYSize / 2f - scale * 8 / 2f + texY, false, Color.BLACK.getRGB(), scale); + } + } + + texX += len * scale; + } + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/GuiScreenElementWrapper.java b/src/main/java/at/hannibal2/skyhanni/config/core/GuiScreenElementWrapper.java new file mode 100644 index 000000000..3dd5f19f6 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/GuiScreenElementWrapper.java @@ -0,0 +1,34 @@ +package at.hannibal2.skyhanni.config.core; + +import java.io.IOException; +import net.minecraft.client.gui.GuiScreen; +import org.lwjgl.input.Mouse; + +public class GuiScreenElementWrapper extends GuiScreen { + + public final GuiElement element; + + public GuiScreenElementWrapper(GuiElement element) { + this.element = element; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + element.render(); + } + + @Override + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + int i = Mouse.getEventX() * this.width / this.mc.displayWidth; + int j = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1; + element.mouseInput(i, j); + } + + @Override + public void handleKeyboardInput() throws IOException { + super.handleKeyboardInput(); + element.keyboardInput(); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/Config.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/Config.java new file mode 100644 index 000000000..c9f6ea5b2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/Config.java @@ -0,0 +1,5 @@ +package at.hannibal2.skyhanni.config.core.config; +//public class Config { +// +// public void executeRunnable(String runnableId) {} +//} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/KeybindHelper.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/KeybindHelper.java new file mode 100644 index 000000000..faa5022f2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/KeybindHelper.java @@ -0,0 +1,49 @@ +package at.hannibal2.skyhanni.config.core.config; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +public class KeybindHelper { + + public static String getKeyName(int keyCode) { + if (keyCode == 0) { + return "NONE"; + } else if (keyCode < 0) { + return "Button " + (keyCode + 101); + } else { + String keyName = Keyboard.getKeyName(keyCode); + if (keyName == null) { + keyName = "???"; + } else if (keyName.equalsIgnoreCase("LMENU")) { + keyName = "LALT"; + } else if (keyName.equalsIgnoreCase("RMENU")) { + keyName = "RALT"; + } + return keyName; + } + } + + public static boolean isKeyValid(int keyCode) { + return keyCode != 0; + } + + public static boolean isKeyDown(int keyCode) { + if (!isKeyValid(keyCode)) { + return false; + } else if (keyCode < 0) { + return Mouse.isButtonDown(keyCode + 100); + } else { + return Keyboard.isKeyDown(keyCode); + } + } + + public static boolean isKeyPressed(int keyCode) { + if (!isKeyValid(keyCode)) { + return false; + } else if (keyCode < 0) { + return Mouse.getEventButtonState() && Mouse.getEventButton() == keyCode + 100; + } else { + return Keyboard.getEventKeyState() && Keyboard.getEventKey() == keyCode; + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/Position.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/Position.java new file mode 100644 index 000000000..b67139b7b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/Position.java @@ -0,0 +1,197 @@ +package at.hannibal2.skyhanni.config.core.config; + +import com.google.gson.annotations.Expose; +import net.minecraft.client.gui.ScaledResolution; + +public class Position { + + @Expose + private int x; + + @Expose + private int y; + + @Expose + private boolean centerX; + + @Expose + private boolean centerY; + + private static final int EDGE_OFFSET = 0; + + public Position(int x, int y) { + this(x, y, false, false); + } + + public Position(int x, int y, boolean centerX, boolean centerY) { + this.x = x; + this.y = y; + this.centerX = centerX; + this.centerY = centerY; + } + + public void set(Position other) { + this.x = other.x; + this.y = other.y; + this.centerX = other.centerX; + this.centerY = other.centerY; + } + + public Position clone() { + return new Position(x, y, centerX, centerY); + } + + public boolean isCenterX() { + return centerX; + } + + public boolean isCenterY() { + return centerY; + } + + public int getRawX() { + return x; + } + + public int getRawY() { + return y; + } + + public int getAbsX(ScaledResolution scaledResolution, int objWidth) { + int width = scaledResolution.getScaledWidth(); + + if (centerX) { + return width / 2 + x; + } + + int ret = x; + if (x < 0) { + ret = width + x - objWidth; + } + + if (ret < 0) ret = 0; + if (ret > width - objWidth) ret = width - objWidth; + + return ret; + } + + public int getAbsY(ScaledResolution scaledResolution, int objHeight) { + int height = scaledResolution.getScaledHeight(); + + if (centerY) { + return height / 2 + y; + } + + int ret = y; + if (y < 0) { + ret = height + y - objHeight; + } + + if (ret < 0) ret = 0; + if (ret > height - objHeight) ret = height - objHeight; + + return ret; + } + + public int moveX(int deltaX, int objWidth, ScaledResolution scaledResolution) { + int screenWidth = scaledResolution.getScaledWidth(); + boolean wasPositiveX = this.x >= 0; + this.x += deltaX; + + if (centerX) { + if (wasPositiveX) { + if (this.x > screenWidth / 2 - objWidth / 2) { + deltaX += screenWidth / 2 - objWidth / 2 - this.x; + this.x = screenWidth / 2 - objWidth / 2; + } + } else { + if (this.x < -screenWidth / 2 + objWidth / 2) { + deltaX += -screenWidth / 2 + objWidth / 2 - this.x; + this.x = -screenWidth / 2 + objWidth / 2; + } + } + return deltaX; + } + + if (wasPositiveX) { + if (this.x < EDGE_OFFSET) { + deltaX += EDGE_OFFSET - this.x; + this.x = EDGE_OFFSET; + } + if (this.x > screenWidth - EDGE_OFFSET) { + deltaX += screenWidth - EDGE_OFFSET - this.x; + this.x = screenWidth - EDGE_OFFSET; + } + } else { + if (this.x + 1 > -EDGE_OFFSET) { + deltaX += -EDGE_OFFSET - 1 - this.x; + this.x = -EDGE_OFFSET - 1; + } + if (this.x + screenWidth < EDGE_OFFSET) { + deltaX += EDGE_OFFSET - screenWidth - this.x; + this.x = EDGE_OFFSET - screenWidth; + } + } + + if (this.x >= 0 && this.x + objWidth / 2 > screenWidth / 2) { + this.x -= screenWidth - objWidth; + } + if (this.x < 0 && this.x + objWidth / 2 <= -screenWidth / 2) { + this.x += screenWidth - objWidth; + } + return deltaX; + } + + public int moveY(int deltaY, int objHeight, ScaledResolution scaledResolution) { + int screenHeight = scaledResolution.getScaledHeight(); + boolean wasPositiveY = this.y >= 0; + this.y += deltaY; + + if (centerY) { + if (wasPositiveY) { + if (this.y > screenHeight / 2 - objHeight / 2) { + deltaY += screenHeight / 2 - objHeight / 2 - this.y; + this.y = screenHeight / 2 - objHeight / 2; + } + } else { + if (this.y < -screenHeight / 2 + objHeight / 2) { + deltaY += -screenHeight / 2 + objHeight / 2 - this.y; + this.y = -screenHeight / 2 + objHeight / 2; + } + } + return deltaY; + } + + if (wasPositiveY) { + if (this.y < EDGE_OFFSET) { + deltaY += EDGE_OFFSET - this.y; + this.y = EDGE_OFFSET; + } + if (this.y > screenHeight - EDGE_OFFSET) { + deltaY += screenHeight - EDGE_OFFSET - this.y; + this.y = screenHeight - EDGE_OFFSET; + } + } else { + if (this.y + 1 > -EDGE_OFFSET) { + deltaY += -EDGE_OFFSET - 1 - this.y; + this.y = -EDGE_OFFSET - 1; + } + if (this.y + screenHeight < EDGE_OFFSET) { + deltaY += EDGE_OFFSET - screenHeight - this.y; + this.y = EDGE_OFFSET - screenHeight; + } + } + + if (this.y >= 0 && this.y - objHeight / 2 > screenHeight / 2) { + this.y -= screenHeight - objHeight; + } + if (this.y < 0 && this.y - objHeight / 2 <= -screenHeight / 2) { + this.y += screenHeight - objHeight; + } + return deltaY; + } + + public boolean rightAligned(ScaledResolution scaledResolution, int objWidth) { + return (this.getAbsX(scaledResolution, objWidth) > (scaledResolution.getScaledWidth() / 2)); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/Category.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/Category.java new file mode 100644 index 000000000..8cb74ed1e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/Category.java @@ -0,0 +1,14 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Category { + String name(); + + String desc(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigAccordionId.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigAccordionId.java new file mode 100644 index 000000000..6eb9bc938 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigAccordionId.java @@ -0,0 +1,12 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigAccordionId { + int id(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorAccordion.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorAccordion.java new file mode 100644 index 000000000..b8a446be2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorAccordion.java @@ -0,0 +1,12 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorAccordion { + int id(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorBoolean.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorBoolean.java new file mode 100644 index 000000000..bdb65e090 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorBoolean.java @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorBoolean { +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorButton.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorButton.java new file mode 100644 index 000000000..8a39660ec --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorButton.java @@ -0,0 +1,14 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorButton { + String runnableId(); + + String buttonText() default ""; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorColour.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorColour.java new file mode 100644 index 000000000..6d3da7061 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorColour.java @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorColour { +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDraggableList.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDraggableList.java new file mode 100644 index 000000000..97ebe26ed --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDraggableList.java @@ -0,0 +1,12 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorDraggableList { + String[] exampleText(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDropdown.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDropdown.java new file mode 100644 index 000000000..3b0256ee3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDropdown.java @@ -0,0 +1,14 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorDropdown { + String[] values(); + + int initialIndex() default 0; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorKeybind.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorKeybind.java new file mode 100644 index 000000000..9153b3896 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorKeybind.java @@ -0,0 +1,12 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorKeybind { + int defaultKey(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorSlider.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorSlider.java new file mode 100644 index 000000000..32ff46581 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorSlider.java @@ -0,0 +1,16 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorSlider { + float minValue(); + + float maxValue(); + + float minStep(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorStyle.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorStyle.java new file mode 100644 index 000000000..d6d2d8a46 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorStyle.java @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorStyle { +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorText.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorText.java new file mode 100644 index 000000000..2ce2277ca --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorText.java @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEditorText { +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigOption.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigOption.java new file mode 100644 index 000000000..f3df0a9f1 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigOption.java @@ -0,0 +1,16 @@ +package at.hannibal2.skyhanni.config.core.config.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigOption { + String name(); + + String desc(); + + int subcategoryId() default -1; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditor.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditor.java new file mode 100644 index 000000000..4d1c2ff0a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditor.java @@ -0,0 +1,62 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.GlStateManager; + +public abstract class GuiOptionEditor { + + protected final ConfigProcessor.ProcessedOption option; + private static final int HEIGHT = 45; + + public GuiOptionEditor(ConfigProcessor.ProcessedOption option) { + this.option = option; + } + + public void render(int x, int y, int width) { + int height = getHeight(); + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + RenderUtils.drawFloatingRectDark(x, y, width, height, true); + TextRenderUtils.drawStringCenteredScaledMaxWidth(option.name, fr, x + width / 6, y + 13, true, width / 3 - 10, 0xc0c0c0); + + int maxLines = 5; + float scale = 1; + int lineCount = fr.listFormattedStringToWidth(option.desc, width * 2 / 3 - 10).size(); + + if (lineCount <= 0) return; + + float paraHeight = 9 * lineCount - 1; + + while (paraHeight >= HEIGHT - 10) { + scale -= 1 / 8f; + lineCount = fr.listFormattedStringToWidth(option.desc, (int) (width * 2 / 3 / scale - 10)).size(); + paraHeight = (int) (9 * scale * lineCount - 1 * scale); + } + + GlStateManager.pushMatrix(); + GlStateManager.translate(x + 5 + width / 3f, y + HEIGHT / 2f - paraHeight / 2, 0); + GlStateManager.scale(scale, scale, 1); + + fr.drawSplitString(option.desc, 0, 0, (int) (width * 2 / 3 / scale - 10), 0xc0c0c0); + + GlStateManager.popMatrix(); + } + + public int getHeight() { + return HEIGHT; + } + + public abstract boolean mouseInput(int x, int y, int width, int mouseX, int mouseY); + + public abstract boolean keyboardInput(); + + public boolean mouseInputOverlay(int x, int y, int width, int mouseX, int mouseY) { + return false; + } + + public void renderOverlay(int x, int y, int width) {} +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorAccordion.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorAccordion.java new file mode 100644 index 000000000..e2f6d3a4c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorAccordion.java @@ -0,0 +1,80 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class GuiOptionEditorAccordion extends GuiOptionEditor { + + private final int accordionId; + private boolean accordionToggled; + + public GuiOptionEditorAccordion(ConfigProcessor.ProcessedOption option, int accordionId) { + super(option); + this.accordionToggled = (boolean) option.get(); + this.accordionId = accordionId; + } + + @Override + public int getHeight() { + return 20; + } + + public int getAccordionId() { + return accordionId; + } + + public boolean getToggled() { + return accordionToggled; + } + + @Override + public void render(int x, int y, int width) { + int height = getHeight(); + RenderUtils.drawFloatingRectDark(x, y, width, height, true); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + GlStateManager.enableBlend(); + GlStateManager.disableTexture2D(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + GlStateManager.color(1, 1, 1, 1); + worldrenderer.begin(GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION); + if (accordionToggled) { + worldrenderer.pos((double) x + 6, (double) y + 6, 0.0D).endVertex(); + worldrenderer.pos((double) x + 9.75f, (double) y + 13.5f, 0.0D).endVertex(); + worldrenderer.pos((double) x + 13.5f, (double) y + 6, 0.0D).endVertex(); + } else { + worldrenderer.pos((double) x + 6, (double) y + 13.5f, 0.0D).endVertex(); + worldrenderer.pos((double) x + 13.5f, (double) y + 9.75f, 0.0D).endVertex(); + worldrenderer.pos((double) x + 6, (double) y + 6, 0.0D).endVertex(); + } + tessellator.draw(); + GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + + TextRenderUtils.drawStringScaledMaxWidth(option.name, Minecraft.getMinecraft().fontRendererObj, x + 18, y + 6, false, width - 10, 0xc0c0c0); + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + if (Mouse.getEventButtonState() && mouseX > x && mouseX < x + width && mouseY > y && mouseY < y + getHeight()) { + accordionToggled = !accordionToggled; + return true; + } + + return false; + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorBoolean.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorBoolean.java new file mode 100644 index 000000000..2425c11bd --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorBoolean.java @@ -0,0 +1,37 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.GuiElementBoolean; +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; + +public class GuiOptionEditorBoolean extends GuiOptionEditor { + + private final GuiElementBoolean bool; + + public GuiOptionEditorBoolean(ConfigProcessor.ProcessedOption option) { + super(option); + bool = new GuiElementBoolean(0, 0, (boolean) option.get(), 10, option::set); + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + int height = getHeight(); + + bool.x = x + width / 6 - 24; + bool.y = y + height - 7 - 14; + bool.render(); + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + bool.x = x + width / 6 - 24; + bool.y = y + height - 7 - 14; + return bool.mouseInput(mouseX, mouseY); + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorButton.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorButton.java new file mode 100644 index 000000000..78e96abfe --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorButton.java @@ -0,0 +1,60 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import static at.hannibal2.skyhanni.config.GuiTextures.button_tex; + +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.Features; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import org.lwjgl.input.Mouse; + +public class GuiOptionEditorButton extends GuiOptionEditor { + + private final String runnableId; + private String buttonText; + private final Features config; + + public GuiOptionEditorButton(ConfigProcessor.ProcessedOption option, String runnableId, String buttonText, Features config) { + super(option); + this.runnableId = runnableId; + this.config = config; + + this.buttonText = buttonText; + if (this.buttonText != null && this.buttonText.isEmpty()) this.buttonText = null; + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + + int height = getHeight(); + + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().getTextureManager().bindTexture(button_tex); + RenderUtils.drawTexturedRect(x + width / 6 - 24, y + height - 7 - 14, 48, 16); + + if (buttonText != null) { + TextRenderUtils.drawStringCenteredScaledMaxWidth(buttonText, Minecraft.getMinecraft().fontRendererObj, x + width / 6, y + height - 7 - 6, false, 44, 0xFF303030); + } + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + if (Mouse.getEventButtonState()) { + int height = getHeight(); + if (mouseX > x + width / 6 - 24 && mouseX < x + width / 6 + 24 && mouseY > y + height - 7 - 14 && mouseY < y + height - 7 + 2) { + config.executeRunnable(runnableId); + return true; + } + } + + return false; + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorColour.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorColour.java new file mode 100644 index 000000000..96e6b55b4 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorColour.java @@ -0,0 +1,74 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import static at.hannibal2.skyhanni.config.GuiTextures.*; + +import at.hannibal2.skyhanni.config.core.ChromaColour; +import at.hannibal2.skyhanni.config.core.GuiElementColour; +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import org.lwjgl.input.Mouse; + +public class GuiOptionEditorColour extends GuiOptionEditor { + + private String chromaColour; + private GuiElementColour colourElement = null; + + public GuiOptionEditorColour(ConfigProcessor.ProcessedOption option) { + super(option); + this.chromaColour = (String) option.get(); + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + int height = getHeight(); + + int argb = ChromaColour.specialToChromaRGB(chromaColour); + int r = (argb >> 16) & 0xFF; + int g = (argb >> 8) & 0xFF; + int b = argb & 0xFF; + GlStateManager.color(r / 255f, g / 255f, b / 255f, 1); + Minecraft.getMinecraft().getTextureManager().bindTexture(button_white); + RenderUtils.drawTexturedRect(x + width / 6 - 24, y + height - 7 - 14, 48, 16); + } + + @Override + public void renderOverlay(int x, int y, int width) { + if (colourElement != null) { + colourElement.render(); + } + } + + @Override + public boolean mouseInputOverlay(int x, int y, int width, int mouseX, int mouseY) { + return colourElement != null && colourElement.mouseInput(mouseX, mouseY); + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + + if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0 && mouseX > x + width / 6 - 24 && mouseX < x + width / 6 + 24 && mouseY > y + height - 7 - 14 && mouseY < y + height - 7 + 2) { + colourElement = + new GuiElementColour( + mouseX, + mouseY, + (String) option.get(), + val -> { + option.set(val); + chromaColour = val; + }, + () -> colourElement = null + ); + } + + return false; + } + + @Override + public boolean keyboardInput() { + return colourElement != null && colourElement.keyboardInput(); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDraggableList.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDraggableList.java new file mode 100644 index 000000000..5b6313626 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDraggableList.java @@ -0,0 +1,269 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import static at.hannibal2.skyhanni.config.GuiTextures.DELETE; +import static at.hannibal2.skyhanni.config.GuiTextures.button_tex; + +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.utils.Utils; +import at.hannibal2.skyhanni.config.core.util.lerp.LerpUtils; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.EnumChatFormatting; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class GuiOptionEditorDraggableList extends GuiOptionEditor { + + private final String[] exampleText; + private final List<Integer> activeText; + private int currentDragging = -1; + private int dragStartIndex = -1; + + private long trashHoverTime = -1; + + private int dragOffsetX = -1; + private int dragOffsetY = -1; + + private boolean dropdownOpen = false; + + public GuiOptionEditorDraggableList(ConfigProcessor.ProcessedOption option, String[] exampleText) { + super(option); + this.exampleText = exampleText; + this.activeText = (List<Integer>) option.get(); + } + + @Override + public int getHeight() { + int height = super.getHeight() + 13; + + for (int strIndex : activeText) { + String str = exampleText[strIndex]; + height += 10 * str.split("\n").length; + } + + return height; + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + + int height = getHeight(); + + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().getTextureManager().bindTexture(button_tex); + RenderUtils.drawTexturedRect(x + width / 6 - 24, y + 45 - 7 - 14, 48, 16); + + TextRenderUtils.drawStringCenteredScaledMaxWidth("Add", Minecraft.getMinecraft().fontRendererObj, x + width / 6, y + 45 - 7 - 6, false, 44, 0xFF303030); + + long currentTime = System.currentTimeMillis(); + if (trashHoverTime < 0) { + float greenBlue = LerpUtils.clampZeroOne((currentTime + trashHoverTime) / 250f); + GlStateManager.color(1, greenBlue, greenBlue, 1); + } else { + float greenBlue = LerpUtils.clampZeroOne((250 + trashHoverTime - currentTime) / 250f); + GlStateManager.color(1, greenBlue, greenBlue, 1); + } + Minecraft.getMinecraft().getTextureManager().bindTexture(DELETE); + Utils.drawTexturedRect(x + width / 6 + 27, y + 45 - 7 - 13, 11, 14, GL11.GL_NEAREST); + + Gui.drawRect(x + 5, y + 45, x + width - 5, y + height - 5, 0xffdddddd); + Gui.drawRect(x + 6, y + 46, x + width - 6, y + height - 6, 0xff000000); + + int i = 0; + int yOff = 0; + for (int strIndex : activeText) { + String str = exampleText[strIndex]; + + String[] multilines = str.split("\n"); + + int ySize = multilines.length * 10; + + if (i++ != dragStartIndex) { + for (int multilineIndex = 0; multilineIndex < multilines.length; multilineIndex++) { + String line = multilines[multilineIndex]; + Utils.drawStringScaledMaxWidth(line + EnumChatFormatting.RESET, Minecraft.getMinecraft().fontRendererObj, x + 20, y + 50 + yOff + multilineIndex * 10, true, width - 20, 0xffffffff); + } + Minecraft.getMinecraft().fontRendererObj.drawString("\u2261", x + 10, y + 50 + yOff + ySize / 2 - 4, 0xffffff, true); + } + + yOff += ySize; + } + } + + @Override + public void renderOverlay(int x, int y, int width) { + super.renderOverlay(x, y, width); + + if (dropdownOpen) { + List<Integer> remaining = new ArrayList<>(); + for (int i = 0; i < exampleText.length; i++) { + remaining.add(i); + } + remaining.removeAll(activeText); + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + int dropdownWidth = Math.min(width / 2 - 10, 150); + int left = dragOffsetX; + int top = dragOffsetY; + + int dropdownHeight = -1 + 12 * remaining.size(); + + int main = 0xff202026; + int outline = 0xff404046; + Gui.drawRect(left, top, left + 1, top + dropdownHeight, outline); //Left + Gui.drawRect(left + 1, top, left + dropdownWidth, top + 1, outline); //Top + Gui.drawRect(left + dropdownWidth - 1, top + 1, left + dropdownWidth, top + dropdownHeight, outline); //Right + Gui.drawRect(left + 1, top + dropdownHeight - 1, left + dropdownWidth - 1, top + dropdownHeight, outline); //Bottom + Gui.drawRect(left + 1, top + 1, left + dropdownWidth - 1, top + dropdownHeight - 1, main); //Middle + + int dropdownY = -1; + for (int strIndex : remaining) { + String str = exampleText[strIndex]; + if (str.isEmpty()) { + str = "<NONE>"; + } + TextRenderUtils.drawStringScaledMaxWidth(str.replaceAll("(\n.*)+", " ..."), fr, left + 3, top + 3 + dropdownY, false, dropdownWidth - 6, 0xffa0a0a0); + dropdownY += 12; + } + } else if (currentDragging >= 0) { + int opacity = 0x80; + long currentTime = System.currentTimeMillis(); + if (trashHoverTime < 0) { + float greenBlue = LerpUtils.clampZeroOne((currentTime + trashHoverTime) / 250f); + opacity = (int) (opacity * greenBlue); + } else { + float greenBlue = LerpUtils.clampZeroOne((250 + trashHoverTime - currentTime) / 250f); + opacity = (int) (opacity * greenBlue); + } + + if (opacity < 20) return; + + ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + int mouseX = Mouse.getX() * scaledResolution.getScaledWidth() / Minecraft.getMinecraft().displayWidth; + int mouseY = scaledResolution.getScaledHeight() - Mouse.getY() * scaledResolution.getScaledHeight() / Minecraft.getMinecraft().displayHeight - 1; + + String str = exampleText[currentDragging]; + + String[] multilines = str.split("\n"); + + GlStateManager.enableBlend(); + for (int multilineIndex = 0; multilineIndex < multilines.length; multilineIndex++) { + String line = multilines[multilineIndex]; + Utils.drawStringScaledMaxWidth(line + EnumChatFormatting.RESET, Minecraft.getMinecraft().fontRendererObj, dragOffsetX + mouseX + 10, dragOffsetY + mouseY + multilineIndex * 10, true, width - 20, 0xffffff | (opacity << 24)); + } + + int ySize = multilines.length * 10; + + Minecraft.getMinecraft().fontRendererObj.drawString("\u2261", dragOffsetX + mouseX, dragOffsetY + mouseY + ySize / 2 - 4, 0xffffff, true); + } + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + if (!Mouse.getEventButtonState() && !dropdownOpen && dragStartIndex >= 0 && Mouse.getEventButton() == 0 && mouseX >= x + width / 6 + 27 - 3 && mouseX <= x + width / 6 + 27 + 11 + 3 && mouseY >= y + 45 - 7 - 13 - 3 && mouseY <= y + 45 - 7 - 13 + 14 + 3) { + activeText.remove(dragStartIndex); + currentDragging = -1; + dragStartIndex = -1; + return false; + } + + if (!Mouse.isButtonDown(0) || dropdownOpen) { + currentDragging = -1; + dragStartIndex = -1; + if (trashHoverTime > 0) trashHoverTime = -System.currentTimeMillis(); + } else if (currentDragging >= 0 && mouseX >= x + width / 6 + 27 - 3 && mouseX <= x + width / 6 + 27 + 11 + 3 && mouseY >= y + 45 - 7 - 13 - 3 && mouseY <= y + 45 - 7 - 13 + 14 + 3) { + if (trashHoverTime < 0) trashHoverTime = System.currentTimeMillis(); + } else { + if (trashHoverTime > 0) trashHoverTime = -System.currentTimeMillis(); + } + + if (Mouse.getEventButtonState()) { + int height = getHeight(); + + if (dropdownOpen) { + List<Integer> remaining = new ArrayList<>(); + for (int i = 0; i < exampleText.length; i++) { + remaining.add(i); + } + remaining.removeAll(activeText); + + int dropdownWidth = Math.min(width / 2 - 10, 150); + int left = dragOffsetX; + int top = dragOffsetY; + + int dropdownHeight = -1 + 12 * remaining.size(); + + if (mouseX > left && mouseX < left + dropdownWidth && mouseY > top && mouseY < top + dropdownHeight) { + int dropdownY = -1; + for (int strIndex : remaining) { + if (mouseY < top + dropdownY + 12) { + activeText.add(0, strIndex); + if (remaining.size() == 1) dropdownOpen = false; + return true; + } + + dropdownY += 12; + } + } + + dropdownOpen = false; + return true; + } + + if (activeText.size() < exampleText.length && mouseX > x + width / 6 - 24 && mouseX < x + width / 6 + 24 && mouseY > y + 45 - 7 - 14 && mouseY < y + 45 - 7 + 2) { + dropdownOpen = !dropdownOpen; + dragOffsetX = mouseX; + dragOffsetY = mouseY; + return true; + } + + if (Mouse.getEventButton() == 0 && mouseX > x + 5 && mouseX < x + width - 5 && mouseY > y + 45 && mouseY < y + height - 6) { + int yOff = 0; + int i = 0; + for (int strIndex : activeText) { + int ySize = 10 * exampleText[strIndex].split("\n").length; + if (mouseY < y + 50 + yOff + ySize) { + dragOffsetX = x + 10 - mouseX; + dragOffsetY = y + 50 + yOff - mouseY; + + currentDragging = strIndex; + dragStartIndex = i; + break; + } + yOff += ySize; + i++; + } + } + } else if (Mouse.getEventButton() == -1 && currentDragging >= 0) { + int yOff = 0; + int i = 0; + for (int strIndex : activeText) { + if (dragOffsetY + mouseY + 4 < y + 50 + yOff + 10) { + activeText.remove(dragStartIndex); + activeText.add(i, currentDragging); + + dragStartIndex = i; + break; + } + yOff += 10 * exampleText[strIndex].split("\n").length; + i++; + } + } + + return false; + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDropdown.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDropdown.java new file mode 100644 index 000000000..ebeecfe78 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDropdown.java @@ -0,0 +1,145 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import org.lwjgl.input.Mouse; + +public class GuiOptionEditorDropdown extends GuiOptionEditor { + + protected final String[] values; + private final boolean useOrdinal; + protected int selected; + protected boolean open = false; + + public GuiOptionEditorDropdown(ConfigProcessor.ProcessedOption option, String[] values, int selected, boolean useOrdinal) { + super(option); + if (selected >= values.length) selected = values.length; + this.values = values; + this.selected = selected; + this.useOrdinal = useOrdinal; + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + + if (!open) { + int height = getHeight(); + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + int dropdownWidth = Math.min(width / 3 - 10, 80); + int left = x + width / 6 - dropdownWidth / 2; + int top = y + height - 7 - 14; + + String selectedString = " - Select - "; + if (selected >= 0 && selected < values.length) { + selectedString = values[selected]; + } + + RenderUtils.drawFloatingRectDark(left, top, dropdownWidth, 14, false); + TextRenderUtils.drawStringScaled("\u25BC", fr, left + dropdownWidth - 10, y + height - 7 - 15, false, 0xffa0a0a0, 2); + + TextRenderUtils.drawStringScaledMaxWidth(selectedString, fr, left + 3, top + 3, false, dropdownWidth - 16, 0xffa0a0a0); + } + } + + @Override + public void renderOverlay(int x, int y, int width) { + if (open) { + String selectedString = " - Select - "; + if (selected >= 0 && selected < values.length) { + selectedString = values[selected]; + } + + int height = getHeight(); + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + int dropdownWidth = Math.min(width / 3 - 10, 80); + int left = x + width / 6 - dropdownWidth / 2; + int top = y + height - 7 - 14; + + int dropdownHeight = 13 + 12 * values.length; + + int main = 0xff202026; + int blue = 0xff2355ad; + Gui.drawRect(left, top, left + 1, top + dropdownHeight, blue); //Left + Gui.drawRect(left + 1, top, left + dropdownWidth, top + 1, blue); //Top + Gui.drawRect(left + dropdownWidth - 1, top + 1, left + dropdownWidth, top + dropdownHeight, blue); //Right + Gui.drawRect(left + 1, top + dropdownHeight - 1, left + dropdownWidth - 1, top + dropdownHeight, blue); //Bottom + Gui.drawRect(left + 1, top + 1, left + dropdownWidth - 1, top + dropdownHeight - 1, main); //Middle + + Gui.drawRect(left + 1, top + 14 - 1, left + dropdownWidth - 1, top + 14, blue); //Bar + + int dropdownY = 13; + for (String option : values) { + if (option.isEmpty()) { + option = "<NONE>"; + } + TextRenderUtils.drawStringScaledMaxWidth(option, fr, left + 3, top + 3 + dropdownY, false, dropdownWidth - 6, 0xffa0a0a0); + dropdownY += 12; + } + + TextRenderUtils.drawStringScaled("\u25B2", fr, left + dropdownWidth - 10, y + height - 7 - 15, false, 0xffa0a0a0, 2); + + TextRenderUtils.drawStringScaledMaxWidth(selectedString, fr, left + 3, top + 3, false, dropdownWidth - 16, 0xffa0a0a0); + } + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + + int left = x + width / 6 - 40; + int top = y + height - 7 - 14; + + if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + if (mouseX >= left && mouseX <= left + 80 && mouseY >= top && mouseY <= top + 14) { + open = !open; + return true; + } + } + + return false; + } + + @Override + public boolean mouseInputOverlay(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + + int left = x + width / 6 - 40; + int top = y + height - 7 - 14; + + if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + if (!(mouseX >= left && mouseX <= left + 80 && mouseY >= top && mouseY <= top + 14) && open) { + open = false; + if (mouseX >= left && mouseX <= left + 80) { + int dropdownY = 13; + for (int ordinal = 0; ordinal < values.length; ordinal++) { + if (mouseY >= top + 3 + dropdownY && mouseY <= top + 3 + dropdownY + 12) { + selected = ordinal; + if (useOrdinal) { + option.set(selected); + } else { + option.set(values[selected]); + } + return true; + } + dropdownY += 12; + } + } + return true; + } + } + + return false; + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorKeybind.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorKeybind.java new file mode 100644 index 000000000..53149ceb4 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorKeybind.java @@ -0,0 +1,88 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import static at.hannibal2.skyhanni.config.GuiTextures.*; + +import at.hannibal2.skyhanni.config.core.config.KeybindHelper; +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.render.RenderUtils; +import at.hannibal2.skyhanni.config.core.util.render.TextRenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class GuiOptionEditorKeybind extends GuiOptionEditor { + + private static final ResourceLocation RESET = new ResourceLocation("notenoughupdates:itemcustomize/reset.png"); + + private int keyCode; + private final int defaultKeyCode; + private boolean editingKeycode; + + public GuiOptionEditorKeybind(ConfigProcessor.ProcessedOption option, int keyCode, int defaultKeyCode) { + super(option); + this.keyCode = keyCode; + this.defaultKeyCode = defaultKeyCode; + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + + int height = getHeight(); + + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().getTextureManager().bindTexture(button_tex); + RenderUtils.drawTexturedRect(x + width / 6 - 24, y + height - 7 - 14, 48, 16); + + String keyName = KeybindHelper.getKeyName(keyCode); + String text = editingKeycode ? "> " + keyName + " <" : keyName; + TextRenderUtils.drawStringCenteredScaledMaxWidth(text, Minecraft.getMinecraft().fontRendererObj, x + width / 6, y + height - 7 - 6, false, 40, 0xFF303030); + + Minecraft.getMinecraft().getTextureManager().bindTexture(RESET); + GlStateManager.color(1, 1, 1, 1); + RenderUtils.drawTexturedRect(x + width / 6 - 24 + 48 + 3, y + height - 7 - 14 + 3, 10, 11, GL11.GL_NEAREST); + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + if (Mouse.getEventButtonState() && Mouse.getEventButton() != -1 && editingKeycode) { + editingKeycode = false; + keyCode = Mouse.getEventButton() - 100; + option.set(keyCode); + return true; + } + + if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + int height = getHeight(); + if (mouseX > x + width / 6 - 24 && mouseX < x + width / 6 + 24 && mouseY > y + height - 7 - 14 && mouseY < y + height - 7 + 2) { + editingKeycode = true; + return true; + } + if (mouseX > x + width / 6 - 24 + 48 + 3 && mouseX < x + width / 6 - 24 + 48 + 13 && mouseY > y + height - 7 - 14 + 3 && mouseY < y + height - 7 - 14 + 3 + 11) { + keyCode = defaultKeyCode; + option.set(keyCode); + return true; + } + } + + return false; + } + + @Override + public boolean keyboardInput() { + if (editingKeycode) { + editingKeycode = false; + if (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) { + keyCode = 0; + } else { + keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); + } + option.set(keyCode); + return true; + } + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorSlider.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorSlider.java new file mode 100644 index 000000000..d010c378d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorSlider.java @@ -0,0 +1,136 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.GuiElementTextField; +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.core.util.GuiElementSlider; +import net.minecraft.client.Minecraft; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +public class GuiOptionEditorSlider extends GuiOptionEditor { + + private final GuiElementSlider slider; + private final GuiElementTextField textField; + + public GuiOptionEditorSlider(ConfigProcessor.ProcessedOption option, float minValue, float maxValue, float minStep) { + super(option); + if (minStep < 0) minStep = 0.01f; + + float floatVal = ((Number) option.get()).floatValue(); + { + String strVal; + if (floatVal % 1 == 0) { + strVal = Integer.toString((int) floatVal); + } else { + strVal = Float.toString(floatVal); + } + textField = new GuiElementTextField(strVal, GuiElementTextField.NO_SPACE | GuiElementTextField.NUM_ONLY | GuiElementTextField.SCALE_TEXT); + } + + slider = + new GuiElementSlider( + 0, + 0, + 80, + minValue, + maxValue, + minStep, + floatVal, + val -> { + option.set(val); + + String strVal; + if (val % 1 == 0) { + strVal = Integer.toString(val.intValue()); + } else { + strVal = Float.toString(val); + strVal = strVal.replaceAll("(\\.\\d\\d\\d)(?:\\d)+", "$1"); + strVal = strVal.replaceAll("0+$", ""); + } + textField.setText(strVal); + } + ); + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + int height = getHeight(); + + int fullWidth = Math.min(width / 3 - 10, 80); + int sliderWidth = (fullWidth - 5) * 3 / 4; + int textFieldWidth = (fullWidth - 5) / 4; + + slider.x = x + width / 6 - fullWidth / 2; + slider.y = y + height - 7 - 14; + slider.width = sliderWidth; + slider.render(); + + if (textField.getFocus()) { + textField.setOptions(GuiElementTextField.NO_SPACE | GuiElementTextField.NUM_ONLY); + textField.setSize(Minecraft.getMinecraft().fontRendererObj.getStringWidth(textField.getText()) + 10, 16); + } else { + textField.setSize(textFieldWidth, 16); + textField.setOptions(GuiElementTextField.NO_SPACE | GuiElementTextField.NUM_ONLY | GuiElementTextField.SCALE_TEXT); + } + + textField.render(x + width / 6 - fullWidth / 2 + sliderWidth + 5, y + height - 7 - 14); + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + + int fullWidth = Math.min(width / 3 - 10, 80); + int sliderWidth = (fullWidth - 5) * 3 / 4; + int textFieldWidth = (fullWidth - 5) / 4; + + slider.x = x + width / 6 - fullWidth / 2; + slider.y = y + height - 7 - 14; + slider.width = sliderWidth; + if (slider.mouseInput(mouseX, mouseY)) { + textField.unfocus(); + return true; + } + + if (textField.getFocus()) { + textFieldWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(textField.getText()) + 10; + } + + int textFieldX = x + width / 6 - fullWidth / 2 + sliderWidth + 5; + int textFieldY = y + height - 7 - 14; + textField.setSize(textFieldWidth, 16); + + if (Mouse.getEventButtonState() && (Mouse.getEventButton() == 0 || Mouse.getEventButton() == 1)) { + if (mouseX > textFieldX && mouseX < textFieldX + textFieldWidth && mouseY > textFieldY && mouseY < textFieldY + 16) { + textField.mouseClicked(mouseX, mouseY, Mouse.getEventButton()); + return true; + } + textField.unfocus(); + } + + return false; + } + + @Override + public boolean keyboardInput() { + if (Keyboard.getEventKeyState() && textField.getFocus()) { + textField.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey()); + + try { + textField.setCustomBorderColour(0xffffffff); + float f = Float.parseFloat(textField.getText()); + if (option.set(f)) { + slider.setValue(f); + } else { + textField.setCustomBorderColour(0xff0000ff); + } + } catch (Exception e) { + textField.setCustomBorderColour(0xffff0000); + } + + return true; + } + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorStyle.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorStyle.java new file mode 100644 index 000000000..2bcdc8339 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorStyle.java @@ -0,0 +1,42 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import at.hannibal2.skyhanni.config.textures.Textures; +import java.util.stream.Collectors; +import org.lwjgl.input.Mouse; + +public class GuiOptionEditorStyle extends GuiOptionEditorDropdown { + + public GuiOptionEditorStyle(ConfigProcessor.ProcessedOption option, int selected) { + super(option, Textures.styles.stream().map(t -> t.displayName).collect(Collectors.toList()).toArray(new String[] {}), selected, true); + } + + @Override + public boolean mouseInputOverlay(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + + int left = x + width / 6 - 40; + int top = y + height - 7 - 14; + + if (Mouse.getEventButtonState() && Mouse.getEventButton() == 0) { + if (!(mouseX >= left && mouseX <= left + 80 && mouseY >= top && mouseY <= top + 14) && open) { + this.open = false; + if (mouseX >= left && mouseX <= left + 80) { + int dropdownY = 13; + for (int ordinal = 0; ordinal < values.length; ordinal++) { + if (mouseY >= top + 3 + dropdownY && mouseY <= top + 3 + dropdownY + 12) { + selected = ordinal; + option.set(selected); + Textures.setTexture(selected); + return true; + } + dropdownY += 12; + } + } + return true; + } + } + + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorText.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorText.java new file mode 100644 index 000000000..1279ee7e9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorText.java @@ -0,0 +1,78 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.GuiElementTextField; +import at.hannibal2.skyhanni.config.core.config.struct.ConfigProcessor; +import net.minecraft.client.Minecraft; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +public class GuiOptionEditorText extends GuiOptionEditor { + + private final GuiElementTextField textField; + + public GuiOptionEditorText(ConfigProcessor.ProcessedOption option) { + super(option); + textField = new GuiElementTextField((String) option.get(), 0); + } + + @Override + public void render(int x, int y, int width) { + super.render(x, y, width); + int height = getHeight(); + + int fullWidth = Math.min(width / 3 - 10, 80); + + int textFieldX = x + width / 6 - fullWidth / 2; + if (textField.getFocus()) { + fullWidth = Math.max(fullWidth, Minecraft.getMinecraft().fontRendererObj.getStringWidth(textField.getText()) + 10); + } + + textField.setSize(fullWidth, 16); + + textField.render(textFieldX, y + height - 7 - 14); + } + + @Override + public boolean mouseInput(int x, int y, int width, int mouseX, int mouseY) { + int height = getHeight(); + + int fullWidth = Math.min(width / 3 - 10, 80); + + int textFieldX = x + width / 6 - fullWidth / 2; + + if (textField.getFocus()) { + fullWidth = Math.max(fullWidth, Minecraft.getMinecraft().fontRendererObj.getStringWidth(textField.getText()) + 10); + } + + int textFieldY = y + height - 7 - 14; + textField.setSize(fullWidth, 16); + + if (Mouse.getEventButtonState() && (Mouse.getEventButton() == 0 || Mouse.getEventButton() == 1)) { + if (mouseX > textFieldX && mouseX < textFieldX + fullWidth && mouseY > textFieldY && mouseY < textFieldY + 16) { + textField.mouseClicked(mouseX, mouseY, Mouse.getEventButton()); + return true; + } + textField.unfocus(); + } + + return false; + } + + @Override + public boolean keyboardInput() { + if (Keyboard.getEventKeyState() && textField.getFocus()) { + Keyboard.enableRepeatEvents(true); + textField.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey()); + + try { + textField.setCustomBorderColour(0xffffffff); + option.set(textField.getText()); + } catch (Exception e) { + textField.setCustomBorderColour(0xffff0000); + } + + return true; + } + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.java new file mode 100644 index 000000000..7708c4ccd --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.java @@ -0,0 +1,172 @@ +package at.hannibal2.skyhanni.config.core.config.gui; + +import at.hannibal2.skyhanni.config.core.config.Position; +import at.hannibal2.skyhanni.config.utils.Utils; + +import java.io.IOException; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ScaledResolution; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +public class GuiPositionEditor extends GuiScreen { + + private final Position position; + private final Position originalPosition; + private final int elementWidth; + private final int elementHeight; + private final Runnable renderCallback; + private final Runnable positionChangedCallback; + private final Runnable closedCallback; + private boolean clicked = false; + private int grabbedX = 0; + private int grabbedY = 0; + + private int guiScaleOverride = -1; + + public GuiPositionEditor(Position position, int elementWidth, int elementHeight, Runnable renderCallback, Runnable positionChangedCallback, Runnable closedCallback) { + this.position = position; + this.originalPosition = position.clone(); + this.elementWidth = elementWidth; + this.elementHeight = elementHeight; + this.renderCallback = renderCallback; + this.positionChangedCallback = positionChangedCallback; + this.closedCallback = closedCallback; + } + + public GuiPositionEditor withScale(int scale) { + this.guiScaleOverride = scale; + return this; + } + + @Override + public void onGuiClosed() { + super.onGuiClosed(); + closedCallback.run(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + ScaledResolution scaledResolution; + if (guiScaleOverride >= 0) { + scaledResolution = Utils.pushGuiScale(guiScaleOverride); + } else { + scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + } + + this.width = scaledResolution.getScaledWidth(); + this.height = scaledResolution.getScaledHeight(); + mouseX = Mouse.getX() * width / Minecraft.getMinecraft().displayWidth; + mouseY = height - Mouse.getY() * height / Minecraft.getMinecraft().displayHeight - 1; + + drawDefaultBackground(); + + if (clicked) { + grabbedX += position.moveX(mouseX - grabbedX, elementWidth, scaledResolution); + grabbedY += position.moveY(mouseY - grabbedY, elementHeight, scaledResolution); + } + + renderCallback.run(); + + int x = position.getAbsX(scaledResolution, elementWidth); + int y = position.getAbsY(scaledResolution, elementHeight); + + if (position.isCenterX()) x -= elementWidth / 2; + if (position.isCenterY()) y -= elementHeight / 2; + Gui.drawRect(x, y, x + elementWidth, y + elementHeight, 0x80404040); + + if (guiScaleOverride >= 0) { + Utils.pushGuiScale(-1); + } + + scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + Utils.drawStringCentered("Position Editor", Minecraft.getMinecraft().fontRendererObj, scaledResolution.getScaledWidth() / 2, 8, true, 0xffffff); + Utils.drawStringCentered("R to Reset - Arrow keys/mouse to move", Minecraft.getMinecraft().fontRendererObj, scaledResolution.getScaledWidth() / 2, 18, true, 0xffffff); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + + if (mouseButton == 0) { + ScaledResolution scaledResolution; + if (guiScaleOverride >= 0) { + scaledResolution = Utils.pushGuiScale(guiScaleOverride); + } else { + scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + } + mouseX = Mouse.getX() * width / Minecraft.getMinecraft().displayWidth; + mouseY = height - Mouse.getY() * height / Minecraft.getMinecraft().displayHeight - 1; + + int x = position.getAbsX(scaledResolution, elementWidth); + int y = position.getAbsY(scaledResolution, elementHeight); + if (position.isCenterX()) x -= elementWidth / 2; + if (position.isCenterY()) y -= elementHeight / 2; + + if (mouseX >= x && mouseY >= y && mouseX <= x + elementWidth && mouseY <= y + elementHeight) { + clicked = true; + grabbedX = mouseX; + grabbedY = mouseY; + } + + if (guiScaleOverride >= 0) { + Utils.pushGuiScale(-1); + } + } + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + Keyboard.enableRepeatEvents(true); + + if (keyCode == Keyboard.KEY_R) { + position.set(originalPosition); + } else if (!clicked) { + boolean shiftHeld = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT); + int dist = shiftHeld ? 10 : 1; + if (keyCode == Keyboard.KEY_DOWN) { + position.moveY(dist, elementHeight, new ScaledResolution(Minecraft.getMinecraft())); + } else if (keyCode == Keyboard.KEY_UP) { + position.moveY(-dist, elementHeight, new ScaledResolution(Minecraft.getMinecraft())); + } else if (keyCode == Keyboard.KEY_LEFT) { + position.moveX(-dist, elementWidth, new ScaledResolution(Minecraft.getMinecraft())); + } else if (keyCode == Keyboard.KEY_RIGHT) { + position.moveX(dist, elementWidth, new ScaledResolution(Minecraft.getMinecraft())); + } + } + super.keyTyped(typedChar, keyCode); + } + + @Override + protected void mouseReleased(int mouseX, int mouseY, int state) { + super.mouseReleased(mouseX, mouseY, state); + clicked = false; + } + + @Override + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + + if (clicked) { + ScaledResolution scaledResolution; + if (guiScaleOverride >= 0) { + scaledResolution = Utils.pushGuiScale(guiScaleOverride); + } else { + scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + } + mouseX = Mouse.getX() * width / Minecraft.getMinecraft().displayWidth; + mouseY = height - Mouse.getY() * height / Minecraft.getMinecraft().displayHeight - 1; + + grabbedX += position.moveX(mouseX - grabbedX, elementWidth, scaledResolution); + grabbedY += position.moveY(mouseY - grabbedY, elementHeight, scaledResolution); + positionChangedCallback.run(); + + if (guiScaleOverride >= 0) { + Utils.pushGuiScale(-1); + } + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/struct/ConfigProcessor.java b/src/main/java/at/hannibal2/skyhanni/config/core/config/struct/ConfigProcessor.java new file mode 100644 index 000000000..cce96b5b5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/struct/ConfigProcessor.java @@ -0,0 +1,167 @@ +package at.hannibal2.skyhanni.config.core.config.struct; + +import at.hannibal2.skyhanni.config.core.config.annotations.*; +import at.hannibal2.skyhanni.config.Features; +import at.hannibal2.skyhanni.config.core.config.gui.*; + +import com.google.gson.annotations.Expose; +import java.lang.reflect.Field; +import java.util.LinkedHashMap; +import java.util.List; + +public class ConfigProcessor { + + public static class ProcessedCategory { + + public final String name; + public final String desc; + public final LinkedHashMap<String, ProcessedOption> options = new LinkedHashMap<>(); + + public ProcessedCategory(String name, String desc) { + this.name = name; + this.desc = desc; + } + } + + public static class ProcessedOption { + + public final String name; + public final String desc; + public final int subcategoryId; + public GuiOptionEditor editor; + + public int accordionId = -1; + + private final Field field; + private final Object container; + + public ProcessedOption(String name, String desc, int subcategoryId, Field field, Object container) { + this.name = name; + this.desc = desc; + this.subcategoryId = subcategoryId; + + this.field = field; + this.container = container; + } + + public Object get() { + try { + return field.get(container); + } catch (Exception e) { + return null; + } + } + + public boolean set(Object value) { + try { + if (field.getType() == int.class && value instanceof Number) { + field.set(container, ((Number) value).intValue()); + } else { + field.set(container, value); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + } + + public static LinkedHashMap<String, ProcessedCategory> create(Features config) { + LinkedHashMap<String, ProcessedCategory> processedConfig = new LinkedHashMap<>(); + for (Field categoryField : config.getClass().getDeclaredFields()) { + boolean exposePresent = categoryField.isAnnotationPresent(Expose.class); + boolean categoryPresent = categoryField.isAnnotationPresent(Category.class); + + if (exposePresent && categoryPresent) { + Object categoryObj; + try { + categoryObj = categoryField.get(config); + } catch (Exception e) { + //System.err.printf("Failed to load config category %s. Field was not accessible.\n", categoryField.getName()); + continue; + } + + Category categoryAnnotation = categoryField.getAnnotation(Category.class); + ProcessedCategory cat = new ProcessedCategory(categoryAnnotation.name(), categoryAnnotation.desc()); + processedConfig.put(categoryField.getName(), cat); + + for (Field optionField : categoryObj.getClass().getDeclaredFields()) { + boolean optionPresent = optionField.isAnnotationPresent(ConfigOption.class); + + if (optionPresent) { + ConfigOption optionAnnotation = optionField.getAnnotation(ConfigOption.class); + ProcessedOption option = new ProcessedOption(optionAnnotation.name(), optionAnnotation.desc(), optionAnnotation.subcategoryId(), optionField, categoryObj); + if (optionField.isAnnotationPresent(ConfigAccordionId.class)) { + ConfigAccordionId annotation = optionField.getAnnotation(ConfigAccordionId.class); + option.accordionId = annotation.id(); + } + + GuiOptionEditor editor = null; + Class<?> optionType = optionField.getType(); + if (optionType.isAssignableFrom(int.class) && optionField.isAnnotationPresent(ConfigEditorKeybind.class)) { + ConfigEditorKeybind configEditorAnnotation = optionField.getAnnotation(ConfigEditorKeybind.class); + editor = new GuiOptionEditorKeybind(option, (int) option.get(), configEditorAnnotation.defaultKey()); + } + if (optionField.isAnnotationPresent(ConfigEditorButton.class)) { + ConfigEditorButton configEditorAnnotation = optionField.getAnnotation(ConfigEditorButton.class); + editor = new GuiOptionEditorButton(option, configEditorAnnotation.runnableId(), configEditorAnnotation.buttonText(), config); + } + if (optionType.isAssignableFrom(boolean.class) && optionField.isAnnotationPresent(ConfigEditorBoolean.class)) { + editor = new GuiOptionEditorBoolean(option); + } + if (optionType.isAssignableFrom(boolean.class) && optionField.isAnnotationPresent(ConfigEditorAccordion.class)) { + ConfigEditorAccordion configEditorAnnotation = optionField.getAnnotation(ConfigEditorAccordion.class); + editor = new GuiOptionEditorAccordion(option, configEditorAnnotation.id()); + } + if (optionType.isAssignableFrom(int.class)) { + if (optionField.isAnnotationPresent(ConfigEditorDropdown.class)) { + ConfigEditorDropdown configEditorAnnotation = optionField.getAnnotation(ConfigEditorDropdown.class); + editor = new GuiOptionEditorDropdown(option, configEditorAnnotation.values(), (int) option.get(), true); + } else if (optionField.isAnnotationPresent(ConfigEditorStyle.class)) { + editor = new GuiOptionEditorStyle(option, (int) option.get()); + } + } + if (optionType.isAssignableFrom(List.class)) { + if (optionField.isAnnotationPresent(ConfigEditorDraggableList.class)) { + ConfigEditorDraggableList configEditorAnnotation = optionField.getAnnotation(ConfigEditorDraggableList.class); + editor = new GuiOptionEditorDraggableList(option, configEditorAnnotation.exampleText()); + } + } + if (optionType.isAssignableFrom(String.class)) { + if (optionField.isAnnotationPresent(ConfigEditorDropdown.class)) { + ConfigEditorDropdown configEditorAnnotation = optionField.getAnnotation(ConfigEditorDropdown.class); + editor = new GuiOptionEditorDropdown(option, configEditorAnnotation.values(), configEditorAnnotation.initialIndex(), false); + } else if (optionField.isAnnotationPresent(ConfigEditorColour.class)) { + editor = new GuiOptionEditorColour(option); + } else if (optionField.isAnnotationPresent(ConfigEditorText.class)) { + editor = new GuiOptionEditorText(option); + } + } + if (optionType.isAssignableFrom(int.class) || optionType.isAssignableFrom(float.class) || optionType.isAssignableFrom(double.class)) { + if (optionField.isAnnotationPresent(ConfigEditorSlider.class)) { + ConfigEditorSlider configEditorAnnotation = optionField.getAnnotation(ConfigEditorSlider.class); + editor = new GuiOptionEditorSlider(option, configEditorAnnotation.minValue(), configEditorAnnotation.maxValue(), configEditorAnnotation.minStep()); + } + } + if (optionType.isAssignableFrom(String.class)) { + if (optionField.isAnnotationPresent(ConfigEditorDropdown.class)) { + ConfigEditorDropdown configEditorAnnotation = optionField.getAnnotation(ConfigEditorDropdown.class); + editor = new GuiOptionEditorDropdown(option, configEditorAnnotation.values(), 0, false); + } + } + if (editor == null) { + //System.err.printf("Failed to load config option %s. Could not find suitable editor.\n", optionField.getName()); + continue; + } + option.editor = editor; + cat.options.put(optionField.getName(), option); + } + } + } else if (exposePresent || categoryPresent) { + //System.err.printf("Failed to load config category %s. Both @Expose and @Category must be present.\n", categoryField.getName()); + } + } + return processedConfig; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/GuiElementSlider.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/GuiElementSlider.java new file mode 100644 index 000000000..8f6c837a8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/GuiElementSlider.java @@ -0,0 +1,121 @@ +package at.hannibal2.skyhanni.config.core.util; + +import static at.hannibal2.skyhanni.config.GuiTextures.*; + +import at.hannibal2.skyhanni.config.core.GuiElement; +import at.hannibal2.skyhanni.config.utils.Utils; + +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class GuiElementSlider extends GuiElement { + + public int x; + public int y; + public int width; + private static final int HEIGHT = 16; + + private final float minValue; + private final float maxValue; + private final float minStep; + + private float value; + private final Consumer<Float> setCallback; + + private boolean clicked = false; + + public GuiElementSlider(int x, int y, int width, float minValue, float maxValue, float minStep, float value, Consumer<Float> setCallback) { + if (minStep < 0) minStep = 0.01f; + + this.x = x; + this.y = y; + this.width = width; + this.minValue = minValue; + this.maxValue = maxValue; + this.minStep = minStep; + this.value = value; + this.setCallback = setCallback; + } + + public void setValue(float value) { + this.value = value; + } + + @Override + public void render() { + final ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + int mouseX = Mouse.getX() * scaledResolution.getScaledWidth() / Minecraft.getMinecraft().displayWidth; + + float value = this.value; + if (clicked) { + value = (mouseX - x) * (maxValue - minValue) / width + minValue; + value = Math.max(minValue, Math.min(maxValue, value)); + value = Math.round(value / minStep) * minStep; + } + + float sliderAmount = Math.max(0, Math.min(1, (value - minValue) / (maxValue - minValue))); + int sliderAmountI = (int) (width * sliderAmount); + + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(slider_on_cap); + Utils.drawTexturedRect(x, y, 4, HEIGHT, GL11.GL_NEAREST); + Minecraft.getMinecraft().getTextureManager().bindTexture(slider_off_cap); + Utils.drawTexturedRect(x + width - 4, y, 4, HEIGHT, GL11.GL_NEAREST); + + if (sliderAmountI > 5) { + Minecraft.getMinecraft().getTextureManager().bindTexture(slider_on_segment); + Utils.drawTexturedRect(x + 4, y, sliderAmountI - 4, HEIGHT, GL11.GL_NEAREST); + } + + if (sliderAmountI < width - 5) { + Minecraft.getMinecraft().getTextureManager().bindTexture(slider_off_segment); + Utils.drawTexturedRect(x + sliderAmountI, y, width - 4 - sliderAmountI, HEIGHT, GL11.GL_NEAREST); + } + + for (int i = 1; i < 4; i++) { + int notchX = x + width * i / 4 - 1; + Minecraft.getMinecraft().getTextureManager().bindTexture(notchX > x + sliderAmountI ? slider_off_notch : slider_on_notch); + Utils.drawTexturedRect(notchX, y + (HEIGHT - 4f) / 2, 2, 4, GL11.GL_NEAREST); + } + + Minecraft.getMinecraft().getTextureManager().bindTexture(slider_button_new); + Utils.drawTexturedRect(x + sliderAmountI - 4, y, 8, HEIGHT, GL11.GL_NEAREST); + } + + @Override + public boolean mouseInput(int mouseX, int mouseY) { + if (!Mouse.isButtonDown(0)) { + clicked = false; + } + + if (Mouse.getEventButton() == 0) { + clicked = Mouse.getEventButtonState() && mouseX > x && mouseX < x + width && mouseY > y && mouseY < y + HEIGHT; + if (clicked) { + value = (mouseX - x) * (maxValue - minValue) / width + minValue; + value = Math.max(minValue, Math.min(maxValue, value)); + value = (float) (Math.round(value / minStep) * (double) minStep); + setCallback.accept(value); + return true; + } + } + + if (!Mouse.getEventButtonState() && Mouse.getEventButton() == -1 && clicked) { + value = (mouseX - x) * (maxValue - minValue) / width + minValue; + value = Math.max(minValue, Math.min(maxValue, value)); + value = Math.round(value / minStep) * minStep; + setCallback.accept(value); + return true; + } + + return false; + } + + @Override + public boolean keyboardInput() { + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/StringUtils.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/StringUtils.java new file mode 100644 index 000000000..9f88a7c07 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/StringUtils.java @@ -0,0 +1,8 @@ +package at.hannibal2.skyhanni.config.core.util; + +public class StringUtils { + + public static String cleanColour(String in) { + return in.replaceAll("(?i)\\u00A7.", ""); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpUtils.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpUtils.java new file mode 100644 index 000000000..9fa20b839 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpUtils.java @@ -0,0 +1,25 @@ +package at.hannibal2.skyhanni.config.core.util.lerp; + +public class LerpUtils { + + public static float clampZeroOne(float f) { + return Math.max(0, Math.min(1, f)); + } + + public static float sigmoid(float val) { + return (float) (1 / (1 + Math.exp(-val))); + } + + private static final float sigmoidStr = 8; + private static final float sigmoidA = -1 / (sigmoid(-0.5f * sigmoidStr) - sigmoid(0.5f * sigmoidStr)); + private static final float sigmoidB = sigmoidA * sigmoid(-0.5f * sigmoidStr); + + public static float sigmoidZeroOne(float f) { + f = clampZeroOne(f); + return sigmoidA * sigmoid(sigmoidStr * (f - 0.5f)) - sigmoidB; + } + + public static float lerp(float a, float b, float amount) { + return b + (a - b) * clampZeroOne(amount); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingFloat.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingFloat.java new file mode 100644 index 000000000..f76c73c9c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingFloat.java @@ -0,0 +1,68 @@ +package at.hannibal2.skyhanni.config.core.util.lerp; + +public class LerpingFloat { + + private int timeSpent; + private long lastMillis; + private final int timeToReachTarget; + + private float targetValue; + private float lerpValue; + + public LerpingFloat(float initialValue, int timeToReachTarget) { + this.targetValue = this.lerpValue = initialValue; + this.timeToReachTarget = timeToReachTarget; + } + + public LerpingFloat(int initialValue) { + this(initialValue, 200); + } + + public void tick() { + int lastTimeSpent = timeSpent; + this.timeSpent += System.currentTimeMillis() - lastMillis; + + float lastDistPercentToTarget = lastTimeSpent / (float) timeToReachTarget; + float distPercentToTarget = timeSpent / (float) timeToReachTarget; + float fac = (1 - lastDistPercentToTarget) / lastDistPercentToTarget; + + float startValue = lerpValue - (targetValue - lerpValue) / fac; + + float dist = targetValue - startValue; + if (dist == 0) return; + + float oldLerpValue = lerpValue; + if (distPercentToTarget >= 1) { + lerpValue = targetValue; + } else { + lerpValue = startValue + dist * distPercentToTarget; + } + + if (lerpValue == oldLerpValue) { + timeSpent = lastTimeSpent; + } else { + this.lastMillis = System.currentTimeMillis(); + } + } + + public void resetTimer() { + this.timeSpent = 0; + this.lastMillis = System.currentTimeMillis(); + } + + public void setTarget(float targetValue) { + this.targetValue = targetValue; + } + + public void setValue(float value) { + this.targetValue = this.lerpValue = value; + } + + public float getValue() { + return lerpValue; + } + + public float getTarget() { + return targetValue; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingInteger.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingInteger.java new file mode 100644 index 000000000..a5d7ad9eb --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingInteger.java @@ -0,0 +1,76 @@ +package at.hannibal2.skyhanni.config.core.util.lerp; + +public class LerpingInteger { + + private int timeSpent; + private long lastMillis; + private int timeToReachTarget; + + private int targetValue; + private int lerpValue; + + public LerpingInteger(int initialValue, int timeToReachTarget) { + this.targetValue = this.lerpValue = initialValue; + this.timeToReachTarget = timeToReachTarget; + } + + public LerpingInteger(int initialValue) { + this(initialValue, 200); + } + + public void tick() { + int lastTimeSpent = timeSpent; + this.timeSpent += System.currentTimeMillis() - lastMillis; + + float lastDistPercentToTarget = lastTimeSpent / (float) timeToReachTarget; + float distPercentToTarget = timeSpent / (float) timeToReachTarget; + float fac = (1 - lastDistPercentToTarget) / lastDistPercentToTarget; + + int startValue = lerpValue - (int) ((targetValue - lerpValue) / fac); + + int dist = targetValue - startValue; + if (dist == 0) return; + + int oldLerpValue = lerpValue; + if (distPercentToTarget >= 1) { + lerpValue = targetValue; + } else { + lerpValue = startValue + (int) (dist * distPercentToTarget); + } + + if (lerpValue == oldLerpValue) { + timeSpent = lastTimeSpent; + } else { + this.lastMillis = System.currentTimeMillis(); + } + } + + public int getTimeSpent() { + return timeSpent; + } + + public void resetTimer() { + this.timeSpent = 0; + this.lastMillis = System.currentTimeMillis(); + } + + public void setTimeToReachTarget(int timeToReachTarget) { + this.timeToReachTarget = timeToReachTarget; + } + + public void setTarget(int targetValue) { + this.targetValue = targetValue; + } + + public void setValue(int value) { + this.targetValue = this.lerpValue = value; + } + + public int getValue() { + return lerpValue; + } + + public int getTarget() { + return targetValue; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/render/RenderUtils.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/render/RenderUtils.java new file mode 100644 index 000000000..04d3f2ef8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/render/RenderUtils.java @@ -0,0 +1,155 @@ +package at.hannibal2.skyhanni.config.core.util.render; + +import at.hannibal2.skyhanni.config.core.BackgroundBlur; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; + +public class RenderUtils { + + public static void drawFloatingRectDark(int x, int y, int width, int height) { + drawFloatingRectDark(x, y, width, height, true); + } + + public static void drawFloatingRectDark(int x, int y, int width, int height, boolean shadow) { + int alpha = 0xf0000000; + + if (OpenGlHelper.isFramebufferEnabled()) { + ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft()); + BackgroundBlur.renderBlurredBackground(15, scaledResolution.getScaledWidth(), scaledResolution.getScaledHeight(), x, y, width, height, true); + } else { + alpha = 0xff000000; + } + + int main = alpha | 0x202026; + int light = 0xff303036; + int dark = 0xff101016; + Gui.drawRect(x, y, x + 1, y + height, light); //Left + Gui.drawRect(x + 1, y, x + width, y + 1, light); //Top + Gui.drawRect(x + width - 1, y + 1, x + width, y + height, dark); //Right + Gui.drawRect(x + 1, y + height - 1, x + width - 1, y + height, dark); //Bottom + Gui.drawRect(x + 1, y + 1, x + width - 1, y + height - 1, main); //Middle + if (shadow) { + Gui.drawRect(x + width, y + 2, x + width + 2, y + height + 2, 0x70000000); //Right shadow + Gui.drawRect(x + 2, y + height, x + width, y + height + 2, 0x70000000); //Bottom shadow + } + } + + public static void drawFloatingRect(int x, int y, int width, int height) { + drawFloatingRectWithAlpha(x, y, width, height, 0xFF, true); + } + + public static void drawFloatingRectWithAlpha(int x, int y, int width, int height, int alpha, boolean shadow) { + int main = (alpha << 24) | 0xc0c0c0; + int light = (alpha << 24) | 0xf0f0f0; + int dark = (alpha << 24) | 0x909090; + Gui.drawRect(x, y, x + 1, y + height, light); //Left + Gui.drawRect(x + 1, y, x + width, y + 1, light); //Top + Gui.drawRect(x + width - 1, y + 1, x + width, y + height, dark); //Right + Gui.drawRect(x + 1, y + height - 1, x + width - 1, y + height, dark); //Bottom + Gui.drawRect(x + 1, y + 1, x + width - 1, y + height - 1, main); //Middle + if (shadow) { + Gui.drawRect(x + width, y + 2, x + width + 2, y + height + 2, (alpha * 3 / 5) << 24); //Right shadow + Gui.drawRect(x + 2, y + height, x + width, y + height + 2, (alpha * 3 / 5) << 24); //Bottom shadow + } + } + + public static void drawTexturedModalRect(int x, int y, int textureX, int textureY, int width, int height) { + double f = 0.00390625; + double f1 = 0.00390625; + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer.pos(x + 0.0, y + height, 0.0).tex((textureX + 0.0) * f, (textureY + height) * f1).endVertex(); + worldrenderer.pos(x + width, y + height, 0.0).tex((textureX + width) * f, (textureY + height) * f1).endVertex(); + worldrenderer.pos(x + width, y + 0.0, 0.0).tex((textureX + width) * f, (textureY + 0.0) * f1).endVertex(); + worldrenderer.pos(x + 0.0, y + 0.0, 0.0).tex((textureX + 0.0) * f, (textureY + 0.0) * f1).endVertex(); + tessellator.draw(); + } + + public static void drawTexturedRect(float x, float y, float width, float height) { + drawTexturedRect(x, y, width, height, 0, 1, 0, 1); + } + + public static void drawTexturedRect(float x, float y, float width, float height, int filter) { + drawTexturedRect(x, y, width, height, 0, 1, 0, 1, filter); + } + + public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax) { + drawTexturedRect(x, y, width, height, uMin, uMax, vMin, vMax, GL11.GL_NEAREST); + } + + public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax, int filter) { + GlStateManager.enableBlend(); + GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + + drawTexturedRectNoBlend(x, y, width, height, uMin, uMax, vMin, vMax, filter); + + GlStateManager.disableBlend(); + } + + public static void drawTexturedRectNoBlend(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax, int filter) { + GlStateManager.enableTexture2D(); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer.pos(x, y + height, 0.0D).tex(uMin, vMax).endVertex(); + worldrenderer.pos(x + width, y + height, 0.0D).tex(uMax, vMax).endVertex(); + worldrenderer.pos(x + width, y, 0.0D).tex(uMax, vMin).endVertex(); + worldrenderer.pos(x, y, 0.0D).tex(uMin, vMin).endVertex(); + tessellator.draw(); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + } + + public static void drawGradientRect(int zLevel, int left, int top, int right, int bottom, int startColor, int endColor) { + float startAlpha = (float) (startColor >> 24 & 255) / 255.0F; + float startRed = (float) (startColor >> 16 & 255) / 255.0F; + float startGreen = (float) (startColor >> 8 & 255) / 255.0F; + float startBlue = (float) (startColor & 255) / 255.0F; + float endAlpha = (float) (endColor >> 24 & 255) / 255.0F; + float endRed = (float) (endColor >> 16 & 255) / 255.0F; + float endGreen = (float) (endColor >> 8 & 255) / 255.0F; + float endBlue = (float) (endColor & 255) / 255.0F; + + GlStateManager.disableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + GlStateManager.shadeModel(7425); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR); + worldrenderer.pos(right, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex(); + worldrenderer.pos(left, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex(); + worldrenderer.pos(left, bottom, zLevel).color(endRed, endGreen, endBlue, endAlpha).endVertex(); + worldrenderer.pos(right, bottom, zLevel).color(endRed, endGreen, endBlue, endAlpha).endVertex(); + tessellator.draw(); + + GlStateManager.shadeModel(7424); + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.enableTexture2D(); + } + + public static void drawInnerBox(int left, int top, int width, int height) { + Gui.drawRect(left, top, left + width, top + height, 0x6008080E); //Middle + Gui.drawRect(left, top, left + 1, top + height, 0xff08080E); //Left + Gui.drawRect(left, top, left + width, top + 1, 0xff08080E); //Top + Gui.drawRect(left + width - 1, top, left + width, top + height, 0xff28282E); //Right + Gui.drawRect(left, top + height - 1, left + width, top + height, 0xff28282E); //Bottom + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/util/render/TextRenderUtils.java b/src/main/java/at/hannibal2/skyhanni/config/core/util/render/TextRenderUtils.java new file mode 100644 index 000000000..309bdb2bf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/core/util/render/TextRenderUtils.java @@ -0,0 +1,155 @@ +package at.hannibal2.skyhanni.config.core.util.render; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; + +public class TextRenderUtils { + + public static int getCharVertLen(char c) { + if ("acegmnopqrsuvwxyz".indexOf(c) >= 0) { + return 5; + } else { + return 7; + } + } + + public static void drawStringScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len / (float) strLen; + factor = Math.min(1, factor); + + drawStringScaled(str, fr, x, y, shadow, colour, factor); + } + + public static void drawStringScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int colour, float factor) { + GlStateManager.scale(factor, factor, 1); + fr.drawString(str, x / factor, y / factor, colour, shadow); + GlStateManager.scale(1 / factor, 1 / factor, 1); + } + + public static void drawStringCenteredScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len / (float) strLen; + factor = Math.min(1, factor); + int newLen = Math.min(strLen, len); + + float fontHeight = 8 * factor; + + drawStringScaled(str, fr, x - newLen / 2, y - fontHeight / 2, shadow, colour, factor); + } + + public static void drawHoveringText(List<String> textLines, final int mouseX, final int mouseY, final int screenWidth, final int screenHeight, final int maxTextWidth, FontRenderer font) { + if (!textLines.isEmpty()) { + GlStateManager.disableRescaleNormal(); + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + int tooltipTextWidth = 0; + + for (String textLine : textLines) { + int textLineWidth = font.getStringWidth(textLine); + + if (textLineWidth > tooltipTextWidth) { + tooltipTextWidth = textLineWidth; + } + } + + boolean needsWrap = false; + + int titleLinesCount = 1; + int tooltipX = mouseX + 12; + if (tooltipX + tooltipTextWidth + 4 > screenWidth) { + tooltipX = mouseX - 16 - tooltipTextWidth; + if (tooltipX < 4) { // if the tooltip doesn't fit on the screen + if (mouseX > screenWidth / 2) { + tooltipTextWidth = mouseX - 12 - 8; + } else { + tooltipTextWidth = screenWidth - 16 - mouseX; + } + needsWrap = true; + } + } + + if (maxTextWidth > 0 && tooltipTextWidth > maxTextWidth) { + tooltipTextWidth = maxTextWidth; + needsWrap = true; + } + + if (needsWrap) { + int wrappedTooltipWidth = 0; + List<String> wrappedTextLines = new ArrayList<String>(); + for (int i = 0; i < textLines.size(); i++) { + String textLine = textLines.get(i); + List<String> wrappedLine = font.listFormattedStringToWidth(textLine, tooltipTextWidth); + if (i == 0) { + titleLinesCount = wrappedLine.size(); + } + + for (String line : wrappedLine) { + int lineWidth = font.getStringWidth(line); + if (lineWidth > wrappedTooltipWidth) { + wrappedTooltipWidth = lineWidth; + } + wrappedTextLines.add(line); + } + } + tooltipTextWidth = wrappedTooltipWidth; + textLines = wrappedTextLines; + + if (mouseX > screenWidth / 2) { + tooltipX = mouseX - 16 - tooltipTextWidth; + } else { + tooltipX = mouseX + 12; + } + } + + int tooltipY = mouseY - 12; + int tooltipHeight = 8; + + if (textLines.size() > 1) { + tooltipHeight += (textLines.size() - 1) * 10; + if (textLines.size() > titleLinesCount) { + tooltipHeight += 2; // gap between title lines and next lines + } + } + + if (tooltipY + tooltipHeight + 6 > screenHeight) { + tooltipY = screenHeight - tooltipHeight - 6; + } + + final int zLevel = 300; + final int backgroundColor = 0xF0100010; + RenderUtils.drawGradientRect(zLevel, tooltipX - 3, tooltipY - 4, tooltipX + tooltipTextWidth + 3, tooltipY - 3, backgroundColor, backgroundColor); + RenderUtils.drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 4, backgroundColor, backgroundColor); + RenderUtils.drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + RenderUtils.drawGradientRect(zLevel, tooltipX - 4, tooltipY - 3, tooltipX - 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + RenderUtils.drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 3, tooltipY - 3, tooltipX + tooltipTextWidth + 4, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + final int borderColorStart = 0x505000FF; + final int borderColorEnd = (borderColorStart & 0xFEFEFE) >> 1 | borderColorStart & 0xFF000000; + RenderUtils.drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd); + RenderUtils.drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 2, tooltipY - 3 + 1, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd); + RenderUtils.drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY - 3 + 1, borderColorStart, borderColorStart); + RenderUtils.drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 2, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, borderColorEnd, borderColorEnd); + + for (int lineNumber = 0; lineNumber < textLines.size(); ++lineNumber) { + String line = textLines.get(lineNumber); + font.drawStringWithShadow(line, (float) tooltipX, (float) tooltipY, -1); + + if (lineNumber + 1 == titleLinesCount) { + tooltipY += 2; + } + + tooltipY += 10; + } + + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + RenderHelper.enableStandardItemLighting(); + GlStateManager.enableRescaleNormal(); + } + GlStateManager.disableLighting(); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/textures/TextureObject.java b/src/main/java/at/hannibal2/skyhanni/config/textures/TextureObject.java new file mode 100644 index 000000000..1804299de --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/textures/TextureObject.java @@ -0,0 +1,37 @@ +package at.hannibal2.skyhanni.config.textures; + +import com.google.gson.JsonObject; +import java.util.Arrays; +import net.minecraft.util.ResourceLocation; + +public class TextureObject { + + public String displayName; + public ResourceLocation bars = resource("bars.png"); + public ResourceLocation mines = resource("mines.png"); + public ResourceLocation playerStats = resource("playerstats.png"); + public ResourceLocation stats = resource("stats.png"); + public ResourceLocation dungeon = resource("dungeon.png"); + public ResourceLocation dialogue = resource("dialogue.png"); + + public TextureObject(String displayName) { + this.displayName = displayName; + } + + public static TextureObject decode(JsonObject json) { + TextureObject textureObject = new TextureObject(json.get("displayName").getAsString()); + Arrays + .stream(textureObject.getClass().getDeclaredFields()) + .filter(field -> field.getType().equals(ResourceLocation.class)) + .forEach(field -> { + try { + field.set(textureObject, new ResourceLocation(json.get(field.getName()).getAsString())); + } catch (Exception ignored) {} + }); + return textureObject; + } + + private static ResourceLocation resource(String path) { + return new ResourceLocation("skyhanni", path); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/textures/Textures.java b/src/main/java/at/hannibal2/skyhanni/config/textures/Textures.java new file mode 100644 index 000000000..dc6a0bdd0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/textures/Textures.java @@ -0,0 +1,54 @@ +package at.hannibal2.skyhanni.config.textures; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.IResourceManagerReloadListener; +import net.minecraft.util.ResourceLocation; + +public class Textures implements IResourceManagerReloadListener { + + private static final TextureObject DEFAULT_TEXTURE = new TextureObject("Default"); + + private static final Gson gson = new GsonBuilder().create(); + public static final List<TextureObject> styles = Lists.newArrayList(DEFAULT_TEXTURE); + public static TextureObject texture = DEFAULT_TEXTURE; + + public static void setTexture(int selected) { + if (selected >= styles.size() || selected < 0) { + texture = DEFAULT_TEXTURE; + } else { + texture = styles.get(selected); + } + } + + @Override + public void onResourceManagerReload(IResourceManager resourceManager) { + styles.clear(); + styles.add(DEFAULT_TEXTURE); + DEFAULT_TEXTURE.displayName = "Default"; + try { + ResourceLocation stylesData = new ResourceLocation("skyhanni:data/styles.json"); + + for (IResource resource : resourceManager.getAllResources(stylesData)) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) { + JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + for (JsonElement json : jsonObject.getAsJsonArray("styles")) { + styles.add(TextureObject.decode((JsonObject) json)); + } + if (DEFAULT_TEXTURE.displayName.equals("Default") && jsonObject.has("defaultDisplayName") && jsonObject.get("defaultDisplayName").isJsonPrimitive()) { + DEFAULT_TEXTURE.displayName = jsonObject.get("defaultDisplayName").getAsString(); + } + } + } + } catch (Exception ignored) {} + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/utils/Utils.java b/src/main/java/at/hannibal2/skyhanni/config/utils/Utils.java new file mode 100644 index 000000000..e2c770a6d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/utils/Utils.java @@ -0,0 +1,367 @@ +package at.hannibal2.skyhanni.config.utils; + +import java.math.RoundingMode; +import java.nio.FloatBuffer; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.LinkedList; +import java.util.Locale; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.Entity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.BlockPos; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.fml.common.Loader; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; + +public class Utils { + + private static final LinkedList<Integer> guiScales = new LinkedList<>(); + private static ScaledResolution lastScale = new ScaledResolution(Minecraft.getMinecraft()); + //Labymod compatibility + private static final FloatBuffer projectionMatrixOld = BufferUtils.createFloatBuffer(16); + private static final FloatBuffer modelviewMatrixOld = BufferUtils.createFloatBuffer(16); + + public static String removeColor(String input) { + return input.replaceAll("(?i)\\u00A7.", ""); + } + + public static String removeWhiteSpaceAndRemoveWord(String input, String replace) { + return input.toLowerCase().replace(" ", "").replace(replace, ""); + } + + public static boolean inRangeInclusive(int value, int min, int max) { + return value <= max && value >= min; + } + + public static float lerp(float f, float g, float h) { + return g + f * (h - g); + } + + public static double lerp(double d, double e, double f) { + return e + d * (f - e); + } + + public static int lerp(float f, int g, int h) { + return (int) (g + f * (h - g)); + } + + public static NBTTagCompound getSkyBlockTag(ItemStack stack) { + if (stack == null) return null; + if (!stack.hasTagCompound()) return null; + if (!stack.getTagCompound().hasKey("ExtraAttributes")) return null; + return stack.getTagCompound().getCompoundTag("ExtraAttributes"); + } + + public static boolean isDrill(ItemStack stack) { + NBTTagCompound tag = getSkyBlockTag(stack); + return tag != null && tag.hasKey("drill_fuel"); + } + + public static int whatRomanNumeral(String roman) { + switch (roman.toLowerCase()) { + case "i": + return 1; + case "ii": + return 2; + case "iii": + return 3; + case "iv": + return 4; + case "v": + return 5; + case "vi": + return 6; + case "vii": + return 7; + case "viii": + return 8; + case "ix": + return 9; + case "x": + return 10; + default: + return 0; + } + } + + public static String intToRomanNumeral(int i) { + switch (i) { + case 1: + return "I"; + case 2: + return "II"; + case 3: + return "III"; + case 4: + return "IV"; + case 5: + return "V"; + case 6: + return "VI"; + case 7: + return "VII"; + case 8: + return "VIII"; + case 9: + return "IX"; + case 10: + return "X"; + default: + return ""; + } + } + + public static boolean overlayShouldRender(RenderGameOverlayEvent.ElementType type, boolean... booleans) { + return overlayShouldRender(false, type, RenderGameOverlayEvent.ElementType.HOTBAR, booleans); + } + + public static boolean overlayShouldRender(boolean hideOnf3, RenderGameOverlayEvent.ElementType type, RenderGameOverlayEvent.ElementType checkType, boolean... booleans) { + Minecraft mc = Minecraft.getMinecraft(); + for (boolean aBoolean : booleans) if (!aBoolean) return false; + if (hideOnf3) { + if (mc.gameSettings.showDebugInfo || (mc.gameSettings.keyBindPlayerList.isKeyDown() && (!mc.isIntegratedServerRunning() || mc.thePlayer.sendQueue.getPlayerInfoMap().size() > 1))) { + return false; + } + } + return ((type == null && Loader.isModLoaded("labymod")) || type == checkType); + } + + public static void drawStringScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len / (float) strLen; + factor = Math.min(1, factor); + + drawStringScaled(str, fr, x, y, shadow, colour, factor); + } + + public static void drawStringScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int colour, float factor) { + GlStateManager.scale(factor, factor, 1); + fr.drawString(str, x / factor, y / factor, colour, shadow); + GlStateManager.scale(1 / factor, 1 / factor, 1); + } + + public static void drawStringCenteredScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len / (float) strLen; + float fontHeight = 8 * factor; + + drawStringScaled(str, fr, x - len / 2f, y - fontHeight / 2f, shadow, colour, factor); + } + + public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax, int filter) { + GlStateManager.enableTexture2D(); + GlStateManager.enableBlend(); + GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer.pos(x, y + height, 0.0D).tex(uMin, vMax).endVertex(); + worldrenderer.pos(x + width, y + height, 0.0D).tex(uMax, vMax).endVertex(); + worldrenderer.pos(x + width, y, 0.0D).tex(uMax, vMin).endVertex(); + worldrenderer.pos(x, y, 0.0D).tex(uMin, vMin).endVertex(); + tessellator.draw(); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + + GlStateManager.disableBlend(); + } + + public static void drawTexturedRect(float x, float y, float width, float height) { + drawTexturedRect(x, y, width, height, 0, 1, 0, 1); + } + + public static void drawTexturedRect(float x, float y, float width, float height, int filter) { + drawTexturedRect(x, y, width, height, 0, 1, 0, 1, filter); + } + + public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax) { + drawTexturedRect(x, y, width, height, uMin, uMax, vMin, vMax, GL11.GL_LINEAR); + } + + public static void resetGuiScale() { + guiScales.clear(); + } + + public static ScaledResolution peekGuiScale() { + return lastScale; + } + + public static ScaledResolution pushGuiScale(int scale) { + if (guiScales.size() == 0) { + if (Loader.isModLoaded("labymod")) { + GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projectionMatrixOld); + GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelviewMatrixOld); + } + } + + if (scale < 0) { + if (guiScales.size() > 0) { + guiScales.pop(); + } + } else { + if (scale == 0) { + guiScales.push(Minecraft.getMinecraft().gameSettings.guiScale); + } else { + guiScales.push(scale); + } + } + + int newScale = guiScales.size() > 0 ? Math.max(0, Math.min(4, guiScales.peek())) : Minecraft.getMinecraft().gameSettings.guiScale; + if (newScale == 0) newScale = Minecraft.getMinecraft().gameSettings.guiScale; + + int oldScale = Minecraft.getMinecraft().gameSettings.guiScale; + Minecraft.getMinecraft().gameSettings.guiScale = newScale; + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + Minecraft.getMinecraft().gameSettings.guiScale = oldScale; + + if (guiScales.size() > 0) { + GlStateManager.viewport(0, 0, Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight); + GlStateManager.matrixMode(GL11.GL_PROJECTION); + GlStateManager.loadIdentity(); + GlStateManager.ortho(0.0D, scaledresolution.getScaledWidth_double(), scaledresolution.getScaledHeight_double(), 0.0D, 1000.0D, 3000.0D); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + GlStateManager.loadIdentity(); + GlStateManager.translate(0.0F, 0.0F, -2000.0F); + } else { + if (Loader.isModLoaded("labymod") && projectionMatrixOld.limit() > 0 && modelviewMatrixOld.limit() > 0) { + GlStateManager.matrixMode(GL11.GL_PROJECTION); + GL11.glLoadMatrix(projectionMatrixOld); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + GL11.glLoadMatrix(modelviewMatrixOld); + } else { + GlStateManager.matrixMode(GL11.GL_PROJECTION); + GlStateManager.loadIdentity(); + GlStateManager.ortho(0.0D, scaledresolution.getScaledWidth_double(), scaledresolution.getScaledHeight_double(), 0.0D, 1000.0D, 3000.0D); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + GlStateManager.loadIdentity(); + GlStateManager.translate(0.0F, 0.0F, -2000.0F); + } + } + + lastScale = scaledresolution; + return scaledresolution; + } + + public static void drawStringCentered(String str, FontRenderer fr, float x, float y, boolean shadow, int colour) { + int strLen = fr.getStringWidth(str); + + float x2 = x - strLen / 2f; + float y2 = y - fr.FONT_HEIGHT / 2f; + + GL11.glTranslatef(x2, y2, 0); + fr.drawString(str, 0, 0, colour, shadow); + GL11.glTranslatef(-x2, -y2, 0); + } + + public static void renderWaypointText(String str, BlockPos loc, float partialTicks) { + GlStateManager.alphaFunc(516, 0.1F); + + GlStateManager.pushMatrix(); + + Entity viewer = Minecraft.getMinecraft().getRenderViewEntity(); + double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * partialTicks; + double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * partialTicks; + double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * partialTicks; + + double x = loc.getX() - viewerX; + double y = loc.getY() - viewerY - viewer.getEyeHeight(); + double z = loc.getZ() - viewerZ; + + double distSq = x * x + y * y + z * z; + double dist = Math.sqrt(distSq); + if (distSq > 144) { + x *= 12 / dist; + y *= 12 / dist; + z *= 12 / dist; + } + GlStateManager.translate(x, y, z); + GlStateManager.translate(0, viewer.getEyeHeight(), 0); + + drawNametag(str); + + GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F); + GlStateManager.translate(0, -0.25f, 0); + GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F); + GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + + drawNametag(EnumChatFormatting.YELLOW.toString() + Math.round(dist) + "m"); + + GlStateManager.popMatrix(); + + GlStateManager.disableLighting(); + } + + public static void drawNametag(String str) { + FontRenderer fontrenderer = Minecraft.getMinecraft().fontRendererObj; + float f = 1.6F; + float f1 = 0.016666668F * f; + GlStateManager.pushMatrix(); + GL11.glNormal3f(0.0F, 1.0F, 0.0F); + GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F); + GlStateManager.scale(-f1, -f1, f1); + GlStateManager.disableLighting(); + GlStateManager.depthMask(false); + GlStateManager.disableDepth(); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + int i = 0; + + int j = fontrenderer.getStringWidth(str) / 2; + GlStateManager.disableTexture2D(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR); + worldrenderer.pos(-j - 1, -1 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + worldrenderer.pos(-j - 1, 8 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + worldrenderer.pos(j + 1, 8 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + worldrenderer.pos(j + 1, -1 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + tessellator.draw(); + GlStateManager.enableTexture2D(); + fontrenderer.drawString(str, -fontrenderer.getStringWidth(str) / 2, i, 553648127); + GlStateManager.depthMask(true); + + fontrenderer.drawString(str, -fontrenderer.getStringWidth(str) / 2, i, -1); + + GlStateManager.enableDepth(); + GlStateManager.enableBlend(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.popMatrix(); + } + + public static String formattedNumber(int number, int numberToFormatAt) { + DecimalFormat formatter = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.CANADA)); + formatter.setRoundingMode(RoundingMode.FLOOR); + return number > numberToFormatAt - 1 ? formatter.format((double) number / 1000) + "k" : String.valueOf(number); + } + + public static boolean equalsIgnoreCaseAnyOf(String string, String... strings) { + for (String o : strings) if (string.equalsIgnoreCase(o)) return true; + return false; + } + + public static String getItemCustomId(ItemStack stack) { + if (stack == null) return null; + if (!stack.hasTagCompound()) return null; + if (!stack.getTagCompound().hasKey("ExtraAttributes")) return null; + if (!stack.getTagCompound().getCompoundTag("ExtraAttributes").hasKey("id")) return null; + return stack.getTagCompound().getCompoundTag("ExtraAttributes").getString("id"); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonBossMessages.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonBossMessages.kt new file mode 100644 index 000000000..0dbfbd732 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonBossMessages.kt @@ -0,0 +1,51 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DungeonBossMessages { + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyblock) return + + if (!SkyHanniMod.feature.chat.dungeonBossMessages) return + + if (isBoss(event.message)) { + event.blockedReason = "dungeon_boss" + } + } + + private fun isBoss(message: String): Boolean { + when { + message.matchRegex("§([cd4])\\[BOSS] (.*)") -> { + when { + message.contains(" The Watcher§r§f: ") -> return true + message.contains(" Bonzo§r§f: ") -> return true + message.contains(" Scarf§r§f:") -> return true + message.contains("Professor§r§f") -> return true + message.contains(" Livid§r§f: ") || message.contains(" Enderman§r§f: ") -> return true + message.contains(" Thorn§r§f: ") -> return true + message.contains(" Sadan§r§f: ") -> return true + message.contains(" Maxor§r§c: ") -> return true + message.contains(" Storm§r§c: ") -> return true + message.contains(" Goldor§r§c: ") -> return true + message.contains(" Necron§r§c: ") -> return true + message.contains(" §r§4§kWither King§r§c:") -> return true + + message.endsWith(" Necron§r§c: That is enough, fool!") -> return true + message.endsWith(" Necron§r§c: Adventurers! Be careful of who you are messing with..") -> return true + message.endsWith(" Necron§r§c: Before I have to deal with you myself.") -> return true + } + } + + //M7 - Dragons + message == "§cThe Crystal withers your soul as you hold it in your hands!" -> return true + message == "§cIt doesn't seem like that is supposed to go there." -> return true + } + return false + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonChatFilter.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonChatFilter.kt new file mode 100644 index 000000000..7f1f2d938 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonChatFilter.kt @@ -0,0 +1,224 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DungeonChatFilter { + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyblock) return + + if (!SkyHanniMod.feature.chat.dungeonMessages) return + + val blockReason = block(event.message) + if (blockReason != "") { + event.blockedReason = "dungeon_$blockReason" + } + } + + private fun block(message: String): String { + when { + isPrepare(message) -> return "prepare" + isStart(message) -> return "start" + } + + if (!LorenzUtils.inDungeons) return "" + + return when { + isKey(message) -> "key" + isDoor(message) -> "door" + isPickup(message) -> "pickup" + isReminder(message) -> "reminder" + isBuff(message) -> "buff" + isNotPossible(message) -> "not_possible" + isDamage(message) -> "damage" + isAbility(message) -> "ability" + isPuzzle(message) -> "puzzle" + isEnd(message) -> "end" + + else -> "" + } + } + + private fun isDoor(message: String): Boolean = message == "§cThe §r§c§lBLOOD DOOR§r§c has been opened!" + + private fun isEnd(message: String): Boolean = when { + message.matchRegex("(.*) §r§eunlocked §r§d(.*) Essence §r§8x(.*)§r§e!") -> true + message.matchRegex(" §r§d(.*) Essence §r§8x(.*)") -> true + message.endsWith(" Experience §r§b(Team Bonus)") -> true + else -> false + } + + private fun isAbility(message: String): Boolean = when { + message == "§a§r§6Guided Sheep §r§ais now available!" -> true + message.matchRegex("§7Your Guided Sheep hit §r§c(.*) §r§7enemy for §r§c(.*) §r§7damage.") -> true + message == "§6Rapid Fire§r§a is ready to use! Press §r§6§lDROP§r§a to activate it!" -> true + message == "§6Castle of Stone§r§a is ready to use! Press §r§6§lDROP§r§a to activate it!" -> true + + + message.matchRegex("§a§lBUFF! §fYou were splashed by (.*) §fwith §r§cHealing VIII§r§f!") -> true + message.matchRegex("§aYou were healed for (.*) health by (.*)§a!") -> true + message.matchRegex("§aYou gained (.*) HP worth of absorption for 3s from §r(.*)§r§a!") -> true + message.matchRegex("§c(.*) §r§epicked up your (.*) Orb!") -> true + message.matchRegex("§cThis ability is on cooldown for (.*)s.") -> true + message.matchRegex("§a§l(.*) healed you for (.*) health!") -> true + message.matchRegex("§eYour bone plating reduced the damage you took by §r§c(.*)§r§e!") -> true + message.matchRegex("(.*) §r§eformed a tether with you!") -> true + message.matchRegex("§eYour tether with (.*) §r§ehealed you for §r§a(.*) §r§ehealth.") -> true + message.matchRegex("§7Your Implosion hit §r§c(.*) §r§7enemy for §r§c(.*) §r§7damage.") -> true + + message.matchRegex("§eYour §r§6Spirit Pet §r§ehealed (.*) §r§efor §r§a(.*) §r§ehealth!") -> true + message.matchRegex("§eYour §r§6Spirit Pet §r§ehit (.*) enemy for §r§c(.*) §r§edamage.") -> true + + message == "§dCreeper Veil §r§aActivated!" -> true + message == "§dCreeper Veil §r§cDe-activated!" -> true + message.matchRegex("§cYou need at least (.*) mana to activate this!") -> true + + message.matchRegex( + "§eYou were healed for §r§a(.*)§r§e health by §r(.*)§r§e's §r§9Healing Bow§r§e and " + "gained §r§c\\+(.*) Strength§r§e for 10 seconds." + ) -> true + message.matchRegex("(.*)§r§a granted you §r§c(.*) §r§astrength for §r§e20 §r§aseconds!") -> true + + message.matchRegex("§eYour fairy healed §r§ayourself §r§efor §r§a(.*) §r§ehealth!") -> true + message.matchRegex("§eYour fairy healed §r(.*) §r§efor §r§a(.*) §r§ehealth!") -> true + message.matchRegex("(.*) fairy healed you for §r§a(.*) §r§ehealth!") -> true + + else -> false + } + + private fun isDamage(message: String): Boolean = when { + message == "§cMute silenced you!" -> true + message.matchRegex("(.*) §r§aused §r(.*) §r§aon you!") -> true + message.matchRegex("§cThe (.*)§r§c struck you for (.*) damage!") -> true + message.matchRegex("§cThe (.*) hit you for (.*) damage!") -> true + message.matchRegex("§7(.*) struck you for §r§c(.*)§r§7 damage.") -> true + message.matchRegex("(.*) hit you for §r§c(.*)§r§7 damage.") -> true + message.matchRegex("(.*) hit you for §r§c(.*)§r§7 true damage.") -> true + message.matchRegex("§7(.*) exploded, hitting you for §r§c(.*)§r§7 damage.") -> true + message.matchRegex("(.*)§r§c hit you with §r(.*) §r§cfor (.*) damage!") -> true + message.matchRegex("(.*)§r§a struck you for §r§c(.*)§r§a damage!") -> true + message.matchRegex("(.*)§r§c struck you for (.*)!") -> true + + message.matchRegex("§7The Mage's Magma burnt you for §r§c(.*)§r§7 true damage.") -> true + + message.matchRegex("§7Your (.*) hit §r§c(.*) §r§7(enemy|enemies) for §r§c(.*) §r§7damage.") -> true + else -> false + } + + private fun isNotPossible(message: String): Boolean = when (message) { + "§cYou cannot hit the silverfish while it's moving!", + "§cYou cannot move the silverfish in that direction!", + "§cThere are blocks in the way!", + "§cThis chest has already been searched!", + "§cThis lever has already been used.", + "§cYou cannot do that in this room!", + "§cYou do not have the key for this door!", + "§cYou have already opened this dungeon chest!", + "§cYou cannot use abilities in this room!", + "§cA mystical force in this room prevents you from using that ability!" -> true + + else -> false + } + + private fun isBuff(message: String): Boolean = when { + message.matchRegex("§6§lDUNGEON BUFF! (.*) §r§ffound a §r§dBlessing of (.*)§r§f!(.*)") -> true + message.matchRegex("§6§lDUNGEON BUFF! §r§fYou found a §r§dBlessing of (.*)§r§f!(.*)") -> true + message.matchRegex("§6§lDUNGEON BUFF! §r§fA §r§dBlessing of (.*)§r§f was found! (.*)") -> true + message.matchRegex("§eA §r§a§r§dBlessing of (.*)§r§e was picked up!") -> true + message.matchRegex("(.*) §r§ehas obtained §r§a§r§dBlessing of (.*)§r§e!") -> true + message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) Strength §r§7and §r§a(.*) Crit Damage§r§7.") -> true + message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) Defense §r§7and §r§a+(.*) Damage§r§7.") -> true + message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) HP §r§7and §r§a+(.*)% §r§7health regeneration.") -> true + message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) Intelligence §r§7and §r§a+(.*)? Speed§r§7.") -> true + message.matchRegex(" §r§7Granted you §r§a+(.*) HP§r§7, §r§a(.*) Defense§r§7, §r§a(.*) Intelligence§r§7, and §r§a(.*) Strength§r§7.") -> true + message == "§a§lBUFF! §fYou have gained §r§cHealing V§r§f!" -> true + else -> false + } + + private fun isPuzzle(message: String): Boolean = when { + message.matchRegex("§a§lPUZZLE SOLVED! (.*) §r§ewasn't fooled by §r§c(.*)§r§e! §r§4G§r§co§r§6o§r§ed§r§a §r§2j§r§bo§r§3b§r§5!") -> true + message.matchRegex("§a§lPUZZLE SOLVED! (.*) §r§etied Tic Tac Toe! §r§4G§r§co§r§6o§r§ed§r§a §r§2j§r§bo§r§3b§r§5!") -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fThough I sit stationary in this prison that is §r§cThe Catacombs§r§f, my knowledge knows no bounds." -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fProve your knowledge by answering 3 questions and I shall reward you in ways that transcend time!" -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fAnswer incorrectly, and your moment of ineptitude will live on for generations." -> true + +// message == "§4[STATUE] Oruo the Omniscient§r§f: §r§f2 questions §r§fleft...and§r§f you will have proven your worth to me!" -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§f2 questions left... Then you will have proven your worth to me!" -> true + + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fOne more question!" -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fI bestow upon you all the power of a hundred years!" -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fYou've already proven enough to me! No need to press more of my buttons!" -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fI've had enough of you and your party fiddling with my buttons. Scram!" -> true + message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fEnough! My buttons are not to be pressed with such lack of grace!" -> true + message.matchRegex("§4\\[STATUE] Oruo the Omniscient§r§f: §r(.*) §r§fthinks the answer is §r§6 . §r(.*)§r§f! §r§fLock in your party's answer in my Chamber!") -> true + else -> false + } + + private fun isKey(message: String): Boolean = when { + message.matchRegex("(.*) §r§ehas obtained §r§a§r§6§r§8Wither Key§r§e!") -> true + message.matchRegex("(.*) opened a §r§8§lWITHER §r§adoor!") -> true + message.matchRegex("(.*) §r§ehas obtained §r§a§r§c§r§cBlood Key§r§e!") -> true + message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Beating Heart§r§e!") -> true + message == "§5A shiver runs down your spine..." -> true + message == "§eA §r§a§r§6§r§8Wither Key§r§e was picked up!" -> true + message == "§eA §r§a§r§c§r§cBlood Key§r§e was picked up!" -> true + + else -> false + } + + private fun isReminder(message: String): Boolean = when (message) { + "§e§lRIGHT CLICK §r§7on §r§7a §r§8WITHER §r§7door§r§7 to open it. This key can only be used to open §r§a1§r§7 door!", + "§e§lRIGHT CLICK §r§7on §r§7the §r§cBLOOD DOOR§r§7 to open it. This key can only be used to open §r§a1§r§7 door!" -> true + + else -> false + } + + private fun isPickup(message: String): Boolean = when { + message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Superboom TNT§r§e!") -> true + message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Superboom TNT §r§8x2§r§e!") -> true + message.matchRegex("§6§lRARE DROP! §r§9Hunk of Blue Ice §r§b\\(+(.*)% Magic Find!\\)") -> true + message.matchRegex("(.*) §r§ehas obtained §r§a§r§6Revive Stone§r§e!") -> true + message.matchRegex("(.*) §r§ffound a §r§dWither Essence§r§f! Everyone gains an extra essence!") -> true + message == "§fYou found a §r§dWither Essence§r§f! Everyone gains an extra essence!" -> true + message.matchRegex("§d(.*) the Fairy§r§f: You killed me! Take this §r§6Revive Stone §r§fso that my death is not in vain!") -> true + message.matchRegex("§d(.*) the Fairy§r§f: You killed me! I'll revive you so that my death is not in vain!") -> true + message.matchRegex("§d(.*) the Fairy§r§f: You killed me! I'll revive your friend §r(.*) §r§fso that my death is not in vain!") -> true + message.matchRegex("§d(.*) the Fairy§r§f: Have a great life!") -> true + message.matchRegex( + "§c(.*) §r§eYou picked up a Ability Damage Orb from (.*) §r§ehealing you for §r§c(.*) §r§eand granting you +§r§c(.*)% §r§eAbility Damage for §r§b10 §r§eseconds." + ) -> true + message.matchRegex( + "§c(.*) §r§eYou picked up a Damage Orb from (.*) §r§ehealing you for §r§c(.*) §r§eand granting you +§r§c(.*)% §r§eDamage for §r§b10 §r§eseconds." + ) -> true + message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Premium Flesh§r§e!") -> true + message.matchRegex("§6§lRARE DROP! §r§9Beating Heart §r§b(.*)") -> true + else -> false + } + + private fun isStart(message: String): Boolean = when { + message == "§e[NPC] §bMort§f: §rHere, I found this map when I first entered the dungeon." -> true + message == "§e[NPC] §bMort§f: §rYou should find it useful if you get lost." -> true + message == "§e[NPC] §bMort§f: §rGood luck." -> true + message == "§e[NPC] §bMort§f: §rTalk to me to change your class and ready up." -> true + + //§a[Berserk] §r§fMelee Damage §r§c48%§r§f -> §r§a88% + //§a[Berserk] §r§fWalk Speed §r§c38§r§f -> §r§a68 + message.matchRegex("§a(.*) §r§f(.*) §r§c(.*)§r§f -> §r§a(.*)") -> true + else -> false + } + + private fun isPrepare(message: String): Boolean = when { + message == "§aYour active Potion Effects have been paused and stored. They will be restored when you leave Dungeons! You are not allowed to use existing Potion Effects while in Dungeons." -> true + message.matchRegex("(.*) has started the dungeon countdown. The dungeon will begin in 1 minute.") -> true + message.matchRegex("§e[NPC] §bMort§f: §rTalk to me to change your class and ready up.") -> true + message.matchRegex("(.*) §a is now ready!") -> true + message.matchRegex("§aDungeon starts in (.*) seconds.") -> true + message == "§aDungeon starts in 1 second." -> true + message == "§aYou can no longer consume or splash any potions during the remainder of this Dungeon run!" -> true + else -> false + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonCleanEnd.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonCleanEnd.kt new file mode 100644 index 000000000..f668fc22d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonCleanEnd.kt @@ -0,0 +1,130 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.CheckRenderEntityEvent +import at.hannibal2.skyhanni.events.DamageIndicatorFinalBossEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.PacketEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraft.client.Minecraft +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.entity.item.EntityArmorStand +import net.minecraft.entity.monster.EntityGuardian +import net.minecraft.network.play.server.S1CPacketEntityMetadata +import net.minecraft.network.play.server.S2APacketParticles +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DungeonCleanEnd { + + private var bossDone = false + private var chestsSpawned = false + private var lastBossId: Int = -1 + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inDungeons) return + if (!SkyHanniMod.feature.dungeon.cleanEnd) return + + val message = event.message + + if (message.matchRegex("([ ]*)§r§c(The|Master Mode) Catacombs §r§8- §r§eFloor (.*)")) { + chestsSpawned = true + } + } + + private fun shouldBlock(): Boolean { + if (!LorenzUtils.inDungeons) return false + if (!SkyHanniMod.feature.dungeon.cleanEnd) return false + + if (!bossDone) return false + + return true + } + + @SubscribeEvent + fun onWorldChange(event: WorldEvent.Load) { + bossDone = false + chestsSpawned = false + lastBossId = -1 + } + + @SubscribeEvent + fun onBossDead(event: DamageIndicatorFinalBossEvent) { + if (!LorenzUtils.inDungeons) return + if (bossDone) return + + if (lastBossId == -1) { + lastBossId = event.id + } + } + + @SubscribeEvent + fun onHealthUpdatePacket(event: PacketEvent.ReceiveEvent) { + if (!LorenzUtils.inDungeons) return + if (!SkyHanniMod.feature.dungeon.cleanEnd) return + + if (bossDone) return + if (lastBossId == -1) return + + val packet = event.packet + if (packet !is S1CPacketEntityMetadata) return + if (packet.entityId != lastBossId) return + + for (watchableObject in packet.func_149376_c()) { + if (watchableObject.dataValueId == 6) { + val health = watchableObject.`object` as Float + if (health < 1) { + val dungeonFloor = DungeonData.dungeonFloor + LorenzUtils.chat("§eFloor $dungeonFloor done!") + bossDone = true + } + } + } + } + + @SubscribeEvent + fun onCheckRender(event: CheckRenderEntityEvent<*>) { + if (!shouldBlock()) return + + val entity = event.entity + + if (entity == Minecraft.getMinecraft().thePlayer) return + + if (SkyHanniMod.feature.dungeon.cleanEndF3IgnoreGuardians) { + if (DungeonData.isOneOf("F3", "M3")) { + if (entity is EntityGuardian) { + if (entity.entityId != lastBossId) { + if (Minecraft.getMinecraft().thePlayer.isSneaking) { + return + } + } + } + } + } + + if (chestsSpawned) { + if (entity is EntityArmorStand) { + if (!entity.hasCustomName()) { + return + } + } + if (entity is EntityOtherPlayerMP) { + return + } + } + + event.isCanceled = true + } + + @SubscribeEvent + fun onReceivePacket(event: PacketEvent.ReceiveEvent) { + if (!shouldBlock()) return + + + if (event.packet is S2APacketParticles) { + event.isCanceled = true + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonData.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonData.kt new file mode 100644 index 000000000..eedf664a3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonData.kt @@ -0,0 +1,46 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.events.DungeonEnterEvent +import at.hannibal2.skyhanni.misc.ScoreboardData +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +class DungeonData { + + companion object { + var dungeonFloor: String? = null + + fun isOneOf(vararg floors: String): Boolean { + for (floor in floors) { + if (dungeonFloor == floor) { + return true + } + } + + return false + } + } + + @SubscribeEvent + fun onTick(event: TickEvent.ClientTickEvent) { + if (event.phase != TickEvent.Phase.START) return + if (LorenzUtils.inDungeons) { + if (dungeonFloor == null) { + for (line in ScoreboardData.sidebarLines) { + if (line.contains("The Catacombs (")) { + dungeonFloor = line.substringAfter("(").substringBefore(")") + DungeonEnterEvent(dungeonFloor!!).postAndCatch() + break + } + } + } + } + } + + @SubscribeEvent + fun onWorldChange(event: WorldEvent.Load) { + dungeonFloor = null + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonDeathCounter.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonDeathCounter.kt new file mode 100644 index 000000000..521dbeb6d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonDeathCounter.kt @@ -0,0 +1,97 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.DungeonEnterEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.GuiRender.renderString +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraftforge.client.event.RenderGameOverlayEvent +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DungeonDeathCounter { + + private var textToRender = "" + private var deaths = 0 + + private fun isDeathMessage(message: String): Boolean = when { + message.matchRegex("§c ☠ §r§7You were killed by (.*)§r§7 and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r§7(.*) was killed by (.*) and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You were crushed and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r§7§r(.*)§r§7 was crushed and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You died to a trap and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r(.*)§r§7 died to a trap and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You burnt to death and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r(.*)§r§7 burnt to death and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You died and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r(.*)§r§7 died and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You suffocated and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r§7§r(.*)§r§7 suffocated and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You died to a mob and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r(.*)§7 died to a mob and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7You fell into a deep hole and became a ghost§r§7.") -> true + message.matchRegex("§c ☠ §r(.*)§r§7 fell into a deep hole and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§(.*)§r§7 disconnected from the Dungeon and became a ghost§r§7.") -> true + + message.matchRegex("§c ☠ §r§7(.*)§r§7 fell to their death with help from §r(.*)§r§7 and became a ghost§r§7.") -> true + + else -> false + } + + @SubscribeEvent(receiveCanceled = true) + fun onChatPacket(event: LorenzChatEvent) { + if (!isEnabled()) return + + if (isDeathMessage(event.message)) { + deaths++ + LorenzUtils.chat("§c§l$deaths. DEATH!") + update() + } + } + + private fun update() { + if (deaths == 0) { + textToRender = "" + return + } + + val color = when (deaths) { + 1, 2 -> "§e" + 3 -> "§c" + else -> "§4" + } + textToRender = color + "Deaths: $deaths" + } + + @SubscribeEvent + fun onDungeonStart(event: DungeonEnterEvent) { + deaths = 0 + update() + } + + @SubscribeEvent + fun onWorldChange(event: WorldEvent.Load) { + deaths = 0 + update() + } + + @SubscribeEvent + fun renderOverlay(event: RenderGameOverlayEvent.Post) { + if (!isEnabled()) return + + SkyHanniMod.feature.dungeon.deathCounterDisplay.renderString(DungeonMilestoneDisplay.color + textToRender) + } + + private fun isEnabled(): Boolean { + return LorenzUtils.inDungeons && SkyHanniMod.feature.dungeon.deathCounter + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonHighlightClickedBlocks.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonHighlightClickedBlocks.kt new file mode 100644 index 000000000..ff0ca6931 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonHighlightClickedBlocks.kt @@ -0,0 +1,99 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.PacketEvent +import at.hannibal2.skyhanni.utils.* +import at.hannibal2.skyhanni.utils.BlockUtils.getBlockAt +import at.hannibal2.skyhanni.utils.RenderUtils.drawColor +import at.hannibal2.skyhanni.utils.RenderUtils.drawString +import net.minecraft.init.Blocks +import net.minecraft.network.play.client.C08PacketPlayerBlockPlacement +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DungeonHighlightClickedBlocks { + + private val blocks = mutableListOf<ClickedBlock>() + private var colorIndex = 0 + private val colors = listOf(LorenzColor.YELLOW, LorenzColor.AQUA, LorenzColor.GREEN, LorenzColor.LIGHT_PURPLE) + + private fun getNextColor(): LorenzColor { + var id = colorIndex + 1 + if (id == colors.size) id = 0 + colorIndex = id + return colors[colorIndex] + } + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!SkyHanniMod.feature.dungeon.highlightClickedBlocks) return + if (!LorenzUtils.inDungeons) return + + if (event.message == "§cYou hear the sound of something opening...") { + event.blockedReason = "dungeon_highlight_clicked_block" + } + } + + @SubscribeEvent + fun onSendPacket(event: PacketEvent.SendEvent) { + if (!SkyHanniMod.feature.dungeon.highlightClickedBlocks) return + if (!LorenzUtils.inDungeons) return +// TODO add +// if (DungeonAPI.inBossRoom) return + if (event.packet !is C08PacketPlayerBlockPlacement || event.packet.stack == null) return + + val position = event.packet.position.toLorenzVec() + + if (blocks.any { it.position == position }) return + + val type: ClickedBlockType = when (position.getBlockAt()) { + Blocks.chest, Blocks.trapped_chest -> ClickedBlockType.CHEST + Blocks.lever -> ClickedBlockType.LEVER + Blocks.skull -> ClickedBlockType.WITHER_ESSENCE + else -> return + } + + if (type == ClickedBlockType.WITHER_ESSENCE) { + val text = BlockUtils.getSkinFromSkull(position.toBlocPos()) + if (text != "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQ" + + "ubmV0L3RleHR1cmUvYzRkYjRhZGZhOWJmNDhmZjVkNDE3M" + + "DdhZTM0ZWE3OGJkMjM3MTY1OWZjZDhjZDg5MzQ3NDlhZjRjY2U5YiJ9fX0=" + ) { + return + } + } + +// if (nearWaterRoom() && type == ClickedBlockType.LEVER) return + + val color = getNextColor() + val displayText = color.getChatColor() + "Clicked " + type.display + blocks.add(ClickedBlock(position, displayText, color, System.currentTimeMillis())) + } + + @SubscribeEvent + fun onWorldRender(event: RenderWorldLastEvent) { + if (!SkyHanniMod.feature.dungeon.highlightClickedBlocks) return + if (!LorenzUtils.inDungeons) return + + blocks.removeAll { System.currentTimeMillis() > it.time + 3000 } + blocks.forEach { + event.drawColor(it.position, it.color) + event.drawString(it.position.add(0.5, 0.5, 0.5), it.displayText, true) + } + } + + class ClickedBlock(val position: LorenzVec, val displayText: String, val color: LorenzColor, val time: Long) + + enum class ClickedBlockType(val display: String) { + LEVER("Lever"), + CHEST("Chest"), + WITHER_ESSENCE("Wither Essence"), + } + +// private fun nearWaterRoom(): Boolean { +// val playerLoc = +// LocationUtils.getPlayerLocation().add(LocationUtils.getPlayerLookingAtDirection().multiply(2)).add(0, 2, 0) +// return WaterBoardSolver.waterRoomDisplays.any { it.distance(playerLoc) < 3 } +// } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonMilestoneDisplay.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonMilestoneDisplay.kt new file mode 100644 index 000000000..96bfba678 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/DungeonMilestoneDisplay.kt @@ -0,0 +1,96 @@ +package at.hannibal2.skyhanni.dungeon + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.DungeonEnterEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.GuiRender.renderString +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraftforge.client.event.RenderGameOverlayEvent +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.concurrent.fixedRateTimer + +class DungeonMilestoneDisplay { + + + companion object { + private var textToRender = "" + var color = "" + var currentMilestone = 0 + var timeReached = 0L + + fun isMilestoneMessage(message: String): Boolean = when { + message.matchRegex("§e§l(.*) Milestone §r§e.§r§7: You have dealt §r§c(.*)§r§7 Total Damage so far! §r§a(.*)") -> true + message.matchRegex("§e§lArcher Milestone §r§e.§r§7: You have dealt §r§c(.*)§r§7 Ranged Damage so far! §r§a(.*)") -> true + message.matchRegex("§e§lHealer Milestone §r§e.§r§7: You have healed §r§a(.*)§r§7 Damage so far! §r§a(.*)") -> true + message.matchRegex("§e§lTank Milestone §r§e.§r§7: You have tanked and dealt §r§c(.*)§r§7 Total Damage so far! §r§a(.*)s") -> true + + else -> false + } + } + + init { + fixedRateTimer(name = "dungeon-milestone-display", period = 200) { + if (!isEnabled()) return@fixedRateTimer + checkVisibility() + } + } + + private fun checkVisibility() { + if (currentMilestone >= 3) { + if (System.currentTimeMillis() > timeReached + 3_000) + if (textToRender != "") { + textToRender = textToRender.substring(1) + } + } + } + + @SubscribeEvent(receiveCanceled = true) + fun onChatPacket(event: LorenzChatEvent) { + if (!isEnabled()) return + + if (isMilestoneMessage(event.message)) { + event.blockedReason = "dungeon_milestone" + currentMilestone++ + update() + } + } + + private fun update() { + if (currentMilestone > 3) return + if (currentMilestone == 3) { + timeReached = System.currentTimeMillis() + } + + color = when (currentMilestone) { + 0, 1 -> "§c" + 2 -> "§e" + else -> "§a" + } + textToRender = "Current Milestone: $currentMilestone" + } + + @SubscribeEvent + fun onWorldChange(event: WorldEvent.Load) { + textToRender = "" + currentMilestone = 0 + } + + @SubscribeEvent + fun onDungeonStart(event: DungeonEnterEvent) { + currentMilestone = 0 + update() + } + + @SubscribeEvent + fun renderOverlay(event: RenderGameOverlayEvent.Post) { + if (!isEnabled()) return + + SkyHanniMod.feature.dungeon.milestoneDisplayPos.renderString(color + textToRender) + } + + private fun isEnabled(): Boolean { + return LorenzUtils.inDungeons && SkyHanniMod.feature.dungeon.showMilestoneDisplay + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossDamageIndicator.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossDamageIndicator.kt new file mode 100644 index 000000000..d9e149b7b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossDamageIndicator.kt @@ -0,0 +1,181 @@ +package at.hannibal2.skyhanni.dungeon.damageindicator + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.dungeon.DungeonData +import at.hannibal2.skyhanni.events.DamageIndicatorFinalBossEvent +import at.hannibal2.skyhanni.events.DungeonEnterEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.RenderUtils +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.entity.EntityLivingBase +import net.minecraft.util.Vec3 +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.event.entity.EntityJoinWorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.text.DecimalFormat +import java.util.* +import kotlin.math.max + +class DungeonBossDamageIndicator { + + var data = mutableMapOf<EntityLivingBase, EntityData>() + private var bossFinder: DungeonBossFinder? = null + private val decimalFormat = DecimalFormat("0.0") + private val maxHealth = mutableMapOf<UUID, Int>() + + @SubscribeEvent + fun onDungeonStart(event: DungeonEnterEvent) { + bossFinder = DungeonBossFinder() + } + + @SubscribeEvent(receiveCanceled = true) + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inDungeons) return + + bossFinder?.handleChat(event.message) + } + + @SubscribeEvent + fun onWorldRender(event: RenderWorldLastEvent) { + if (!LorenzUtils.inDungeons) return + if (!SkyHanniMod.feature.dungeon.bossDamageIndicator) return + + GlStateManager.disableDepth() + GlStateManager.disableCull() + + val player = Minecraft.getMinecraft().thePlayer + + for (data in data.values) { + if (System.currentTimeMillis() > data.time + 100) continue//TODO use removeIf + if (!data.ignoreBlocks) { + if (!player.canEntityBeSeen(data.entity)) continue + } + + val entity = data.entity + + var color = data.color + var text = data.text + val delayedStart = data.delayedStart + if (delayedStart != -1L) { + if (delayedStart > System.currentTimeMillis()) { + val delay = delayedStart - System.currentTimeMillis() + color = colorForTime(delay) + var d = delay * 1.0 + d /= 1000 + text = decimalFormat.format(d) + } + } + + val partialTicks = event.partialTicks + RenderUtils.drawLabel( + Vec3( + RenderUtils.interpolate(entity.posX, entity.lastTickPosX, partialTicks), + RenderUtils.interpolate(entity.posY, entity.lastTickPosY, partialTicks) + 0.5f, + RenderUtils.interpolate(entity.posZ, entity.lastTickPosZ, partialTicks) + ), + text, + color.toColor(), + partialTicks, + true, + 6f + ) + } + GlStateManager.enableDepth() + GlStateManager.enableCull() + } + + private fun colorForTime(delayedStart: Long): LorenzColor = when { + delayedStart < 1_000 -> LorenzColor.DARK_PURPLE + delayedStart < 3_000 -> LorenzColor.LIGHT_PURPLE + + else -> LorenzColor.WHITE + } + + @SubscribeEvent + fun onRenderLivingPost(event: RenderLivingEvent.Post<*>) { + if (!LorenzUtils.inDungeons) return + + try { + val entity = event.entity + val result = bossFinder?.shouldShow(entity) ?: return + checkLastBossDead(result.finalBoss, entity.entityId) + val ignoreBlocks = result.ignoreBlocks + val delayedStart = result.delayedStart + + + var health = event.entity.health.toInt() + val maxHealth: Int + if (DungeonData.isOneOf("F4")) { + val hitPoints = when (health) { + 300_000 -> 4 + 222_000 -> 3 + 144_000 -> 2 + 66_000 -> 1 + else -> { + LorenzUtils.error("Unexpected health of thorn in F4! ($health)") + return + } + } + + health = hitPoints + maxHealth = 4 + } else { + val biggestHealth = getMaxHealthFor(event.entity) + + if (biggestHealth == 0) { + val currentMaxHealth = event.entity.baseMaxHealth.toInt() + maxHealth = max(currentMaxHealth, health) + setMaxHealth(event.entity, maxHealth) + } else { + maxHealth = biggestHealth + } + } + + val percentage = health.toDouble() / maxHealth.toDouble() + val color = when { + percentage > 0.9 -> LorenzColor.DARK_GREEN + percentage > 0.75 -> LorenzColor.GREEN + percentage > 0.5 -> LorenzColor.YELLOW + percentage > 0.25 -> LorenzColor.GOLD + else -> LorenzColor.RED + } + + data[entity] = EntityData( + entity, + NumberUtil.format(health), + color, + System.currentTimeMillis(), + ignoreBlocks, + delayedStart + ) + + } catch (e: Throwable) { + e.printStackTrace() + } + } + + private fun checkLastBossDead(finalBoss: Boolean, id: Int) { + if (finalBoss) { + DamageIndicatorFinalBossEvent(id).postAndCatch() + } + } + + private fun setMaxHealth(entity: EntityLivingBase, currentMaxHealth: Int) { + maxHealth[entity.uniqueID!!] = currentMaxHealth + } + + private fun getMaxHealthFor(entity: EntityLivingBase): Int { + return maxHealth.getOrDefault(entity.uniqueID!!, 0) + } + + @SubscribeEvent + fun onWorldRender(event: EntityJoinWorldEvent) { + bossFinder?.handleNewEntity(event.entity) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossFinder.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossFinder.kt new file mode 100644 index 000000000..1d30a9d24 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossFinder.kt @@ -0,0 +1,381 @@ +package at.hannibal2.skyhanni.dungeon.damageindicator + +import at.hannibal2.skyhanni.dungeon.DungeonData +import at.hannibal2.skyhanni.test.LorenzTest +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.getLorenzVec +import net.minecraft.client.Minecraft +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.monster.EntityGhast +import net.minecraft.entity.monster.EntityGiantZombie +import net.minecraft.entity.monster.EntityGuardian +import java.util.* + +class DungeonBossFinder { + + //F1 + private var floor1bonzo1 = false + private var floor1bonzo1SpawnTime = 0L + private var floor1bonzo2 = false + private var floor1bonzo2SpawnTime = 0L + + //F2 + private var floor2summons1 = false + private var floor2summons1SpawnTime = 0L + private var floor2summonsDiedOnce = mutableListOf<EntityOtherPlayerMP>() + private var floor2secondPhase = false + private var floor2secondPhaseSpawnTime = 0L + + //F3 + private var floor3GuardianShield = false + private var floor3GuardianShieldSpawnTime = 0L + private var guardians = mutableListOf<EntityGuardian>() + private var floor3Professor = false + private var floor3ProfessorSpawnTime = 0L + private var floor3ProfessorGuardianPrepare = false + private var floor3ProfessorGuardianPrepareSpawnTime = 0L + private var floor3ProfessorGuardian = false + private var floor3ProfessorGuardianEntity: EntityGuardian? = null + + //F5 + private var floor5lividEntity: EntityOtherPlayerMP? = null + private var floor5lividEntitySpawnTime = 0L + + //F6 + private var floor6Giants = false + private var floor6GiantsSpawnTime = 0L + private var floor6GiantsSeparateDelay = mutableMapOf<UUID, Long>() + private var floor6Sadan = false + private var floor6SadanSpawnTime = 0L + + internal fun shouldShow(entity: EntityLivingBase): EntityResult? { + if (LorenzUtils.inDungeons) { + if (DungeonData.isOneOf("F1", "M1")) { + if (floor1bonzo1) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Bonzo ") { + return EntityResult(floor1bonzo1SpawnTime) + } + } + } + if (floor1bonzo2) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Bonzo ") { + return EntityResult(floor1bonzo2SpawnTime, finalBoss = true) + } + } + } + } + + if (DungeonData.isOneOf("F2", "M2")) { + if (entity.name == "Summon ") { + if (entity is EntityOtherPlayerMP) { + if (floor2summons1) { + if (!floor2summonsDiedOnce.contains(entity)) { + if (entity.health.toInt() != 0) { + return EntityResult(floor2summons1SpawnTime) + } else { + floor2summonsDiedOnce.add(entity) + } + } + } + if (floor2secondPhase) { + return EntityResult(floor2secondPhaseSpawnTime) + } + } + } + + if (floor2secondPhase) { + if (entity is EntityOtherPlayerMP) { + //TODO only show scarf after (all/at least x) summons are dead? + val result = entity.name == "Scarf " + if (result) { + return EntityResult(floor2secondPhaseSpawnTime, finalBoss = true) + } + } + } + } + + if (DungeonData.isOneOf("F3", "M3")) { + if (entity is EntityGuardian) { + if (floor3GuardianShield) { + if (guardians.size == 4) { + var totalHealth = 0 + for (guardian in guardians) { + totalHealth += guardian.health.toInt() + } + if (totalHealth == 0) { + floor3GuardianShield = false + guardians.clear() + } + } else { + findGuardians() + } + if (guardians.contains(entity)) { + return EntityResult(floor3GuardianShieldSpawnTime, true) + } + } + } + + if (floor3Professor) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "The Professor") { + return EntityResult( + floor3ProfessorSpawnTime, + floor3ProfessorSpawnTime + 1_000 > System.currentTimeMillis() + ) + } + } + } + if (floor3ProfessorGuardianPrepare) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "The Professor") { + return EntityResult(floor3ProfessorGuardianPrepareSpawnTime, true) + } + } + } + + if (entity is EntityGuardian) { + if (floor3ProfessorGuardian) { + if (entity == floor3ProfessorGuardianEntity) { + return EntityResult(finalBoss = true) + } + } + } + } + + if (DungeonData.isOneOf("F4", "M4")) { + if (entity is EntityGhast) { + val health = LorenzUtils.formatDouble(entity.health.toDouble()) + LorenzTest.enabled = true + LorenzTest.text = "thorn has $health hp!" + return EntityResult(ignoreBlocks = true, finalBoss = true) + } + + } + + if (DungeonData.isOneOf("F5", "M5")) { + if (entity is EntityOtherPlayerMP) { + if (entity == floor5lividEntity) { + return EntityResult(floor5lividEntitySpawnTime, true, finalBoss = true) + } + } + } + + if (DungeonData.isOneOf("F6", "M6")) { + if (entity is EntityGiantZombie && !entity.isInvisible) { + if (floor6Giants && entity.posY > 68) { + val extraDelay = checkExtraF6GiantsDelay(entity) + return EntityResult( + floor6GiantsSpawnTime + extraDelay, + floor6GiantsSpawnTime + extraDelay + 1_000 > System.currentTimeMillis() + ) + } + + if (floor6Sadan) { + return EntityResult(floor6SadanSpawnTime, finalBoss = true) + } + } + } + } + + return null + } + + private fun checkExtraF6GiantsDelay(entity: EntityGiantZombie): Long { + val uuid = entity.uniqueID + + if (floor6GiantsSeparateDelay.contains(uuid)) { + return floor6GiantsSeparateDelay[uuid]!! + } + + val middle = LorenzVec(-8, 0, 56) + + val loc = entity.getLorenzVec() + + var pos = 0 + + //first + if (loc.x > middle.x && loc.z > middle.z) { + pos = 2 + } + + //second + if (loc.x > middle.x && loc.z < middle.z) { + pos = 3 + } + + //third + if (loc.x < middle.x && loc.z < middle.z) { + pos = 0 + } + + //fourth + if (loc.x < middle.x && loc.z > middle.z) { + pos = 1 + } + + val extraDelay = 900L * pos + floor6GiantsSeparateDelay[uuid] = extraDelay + + return extraDelay + } + + fun handleChat(message: String) { + when (message) { + //F1 + "§c[BOSS] Bonzo§r§f: Gratz for making it this far, but I’m basically unbeatable." -> { + floor1bonzo1 = true + floor1bonzo1SpawnTime = System.currentTimeMillis() + 11_250 + } + + "§c[BOSS] Bonzo§r§f: Oh noes, you got me.. what ever will I do?!" -> { + floor1bonzo1 = false + } + + "§c[BOSS] Bonzo§r§f: Oh I'm dead!" -> { + floor1bonzo2 = true + floor1bonzo2SpawnTime = System.currentTimeMillis() + 4_200 + } + + "§c[BOSS] Bonzo§r§f: Alright, maybe I'm just weak after all.." -> { + floor1bonzo2 = false + } + + //F2 + "§c[BOSS] Scarf§r§f: ARISE, MY CREATIONS!" -> { + floor2summons1 = true + floor2summons1SpawnTime = System.currentTimeMillis() + 3_500 + } + + "§c[BOSS] Scarf§r§f: Those toys are not strong enough I see." -> { + floor2summons1 = false + } + + "§c[BOSS] Scarf§r§f: Don't get too excited though." -> { + floor2secondPhase = true + floor2secondPhaseSpawnTime = System.currentTimeMillis() + 6_300 + } + + "§c[BOSS] Scarf§r§f: Whatever..." -> { + floor2secondPhase = false + } + + //F3 + "§c[BOSS] The Professor§r§f: I was burdened with terrible news recently..." -> { + floor3GuardianShield = true + floor3GuardianShieldSpawnTime = System.currentTimeMillis() + 16_400 + } + + "§c[BOSS] The Professor§r§f: Even if you took my barrier down, I can still fight." -> { + floor3GuardianShield = false + } + + "§c[BOSS] The Professor§r§f: Oh? You found my Guardians one weakness?" -> { + floor3Professor = true + floor3ProfessorSpawnTime = System.currentTimeMillis() + 10_300 + } + + "§c[BOSS] The Professor§r§f: I see. You have forced me to use my ultimate technique." -> { + floor3Professor = false + + floor3ProfessorGuardianPrepare = true + floor3ProfessorGuardianPrepareSpawnTime = System.currentTimeMillis() + 10_500 + } + + "§c[BOSS] The Professor§r§f: The process is irreversible, but I'll be stronger than a Wither now!" -> { + floor3ProfessorGuardian = true + } + + "§c[BOSS] The Professor§r§f: What?! My Guardian power is unbeatable!" -> { + floor3ProfessorGuardian = false + } + + + //F5 + "§c[BOSS] Livid§r§f: This Orb you see, is Thorn, or what is left of him." -> { + floor5lividEntity = findLivid() + floor5lividEntitySpawnTime = System.currentTimeMillis() + 13_000 + } + + //F6 + "§c[BOSS] Sadan§r§f: ENOUGH!" -> { + floor6Giants = true + floor6GiantsSpawnTime = System.currentTimeMillis() + 7_400 + } + + "§c[BOSS] Sadan§r§f: You did it. I understand now, you have earned my respect." -> { + floor6Giants = false + floor6Sadan = true + floor6SadanSpawnTime = System.currentTimeMillis() + 32_500 + } + + "§c[BOSS] Sadan§r§f: NOOOOOOOOO!!! THIS IS IMPOSSIBLE!!" -> { + floor6Sadan = false + } + } + + if (message.matchRegex("§c\\[BOSS] (.*) Livid§r§f: Impossible! How did you figure out which one I was\\?!")) { + floor5lividEntity = null + } + } + + fun handleNewEntity(entity: Entity) { + if (floor3ProfessorGuardian) { + if (entity is EntityGuardian) { + if (floor3ProfessorGuardianEntity == null) { + floor3ProfessorGuardianEntity = entity + floor3ProfessorGuardianPrepare = false + } + } + } + } + + private fun findGuardians() { + guardians.clear() + + for (entity in Minecraft.getMinecraft().theWorld.loadedEntityList) { + if (entity is EntityGuardian) { + + val maxHealth = entity.baseMaxHealth.toInt() + + //F3 + if (maxHealth == 1_000_000 || maxHealth == 1_200_000) { + guardians.add(entity) + } + + //F3 Derpy + if (maxHealth == 2_000_000 || maxHealth == 2_400_000) { + guardians.add(entity) + } + + //M3 + if (maxHealth == 240_000_000 || maxHealth == 280_000_000) { + guardians.add(entity) + } + + //M3 Derpy + if (maxHealth == 120_000_000 || maxHealth == 140_000_000) { + guardians.add(entity) + } + } + } + } + + private fun findLivid(): EntityOtherPlayerMP? { + for (entity in Minecraft.getMinecraft().theWorld.loadedEntityList) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Livid ") { + return entity + } + } + } + + return null + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityData.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityData.kt new file mode 100644 index 000000000..321addb8d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityData.kt @@ -0,0 +1,6 @@ +package at.hannibal2.skyhanni.dungeon.damageindicator + +import at.hannibal2.skyhanni.utils.LorenzColor +import net.minecraft.entity.EntityLivingBase + +class EntityData(val entity: EntityLivingBase, val text: String, val color: LorenzColor, val time: Long, val ignoreBlocks: Boolean, val delayedStart: Long)
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityResult.kt b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityResult.kt new file mode 100644 index 000000000..1831ebe70 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityResult.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.dungeon.damageindicator + +class EntityResult(val delayedStart: Long = -1L, val ignoreBlocks: Boolean = false, val finalBoss: Boolean = false)
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/CheckRenderEntityEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/CheckRenderEntityEvent.kt new file mode 100644 index 000000000..bd9a816cb --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/CheckRenderEntityEvent.kt @@ -0,0 +1,14 @@ +package at.hannibal2.skyhanni.events + +import net.minecraft.client.renderer.culling.ICamera +import net.minecraft.entity.Entity +import net.minecraftforge.fml.common.eventhandler.Cancelable + +@Cancelable +data class CheckRenderEntityEvent<T : Entity>( + val entity: T, + val camera: ICamera, + val camX: Double, + val camY: Double, + val camZ: Double +) : LorenzEvent()
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorFinalBossEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorFinalBossEvent.kt new file mode 100644 index 000000000..ffcde662a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorFinalBossEvent.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.events + +class DamageIndicatorFinalBossEvent(val id: Int): LorenzEvent()
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/DungeonEnterEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/DungeonEnterEvent.kt new file mode 100644 index 000000000..819ae8c71 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/DungeonEnterEvent.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.events + +class DungeonEnterEvent(dungeonFloor: String): LorenzEvent()
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/GuiContainerEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/GuiContainerEvent.kt new file mode 100644 index 000000000..4b1df77a4 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/GuiContainerEvent.kt @@ -0,0 +1,54 @@ +package at.hannibal2.skyhanni.events + +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.inventory.Container +import net.minecraft.inventory.ContainerChest +import net.minecraft.inventory.Slot +import net.minecraftforge.fml.common.eventhandler.Cancelable + +abstract class GuiContainerEvent(open val gui: GuiContainer, open val container: Container) : LorenzEvent() { + val chestName: String by lazy { + if (container !is ContainerChest) error("Container is not a chest") + return@lazy (container as ContainerChest).lowerChestInventory.displayName.unformattedText.trim() + } + + data class BackgroundDrawnEvent( + override val gui: GuiContainer, + override val container: Container, + val mouseX: Int, + val mouseY: Int, + val partialTicks: Float + ) : GuiContainerEvent(gui, container) + + @Cancelable + data class CloseWindowEvent(override val gui: GuiContainer, override val container: Container) : + GuiContainerEvent(gui, container) + + abstract class DrawSlotEvent(gui: GuiContainer, container: Container, open val slot: Slot) : + GuiContainerEvent(gui, container) { + @Cancelable + data class Pre(override val gui: GuiContainer, override val container: Container, override val slot: Slot) : + DrawSlotEvent(gui, container, slot) + + data class Post(override val gui: GuiContainer, override val container: Container, override val slot: Slot) : + DrawSlotEvent(gui, container, slot) + } + + data class ForegroundDrawnEvent( + override val gui: GuiContainer, + override val container: Container, + val mouseX: Int, + val mouseY: Int, + val partialTicks: Float + ) : GuiContainerEvent(gui, container) + + @Cancelable + data class SlotClickEvent( + override val gui: GuiContainer, + override val container: Container, + val slot: Slot?, + val slotId: Int, + val clickedButton: Int, + val clickType: Int + ) : GuiContainerEvent(gui, container) +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/GuiRenderItemEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/GuiRenderItemEvent.kt new file mode 100644 index 000000000..0741aac5c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/GuiRenderItemEvent.kt @@ -0,0 +1,23 @@ +package at.hannibal2.skyhanni.events + +import net.minecraft.client.gui.FontRenderer +import net.minecraft.item.ItemStack + +abstract class GuiRenderItemEvent : LorenzEvent() { + abstract class RenderOverlayEvent( + open val fr: FontRenderer, + open val stack: ItemStack?, + open val x: Int, + open val y: Int, + open val text: String? + ) : GuiRenderItemEvent() { + data class Post( + override val fr: FontRenderer, + override val stack: ItemStack?, + override val x: Int, + override val y: Int, + override val text: String? + ) : + RenderOverlayEvent(fr, stack, x, y, text) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/LorenzActionBarEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/LorenzActionBarEvent.kt new file mode 100644 index 000000000..e5e936fb0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/LorenzActionBarEvent.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.events + +class LorenzActionBarEvent(val message: String) : LorenzEvent()
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/LorenzChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/LorenzChatEvent.kt new file mode 100644 index 000000000..2c7db6bd7 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/LorenzChatEvent.kt @@ -0,0 +1,5 @@ +package at.hannibal2.skyhanni.events + +import net.minecraft.util.IChatComponent + +class LorenzChatEvent(val message: String, val chatComponent: IChatComponent, var blockedReason: String = "") : LorenzEvent()
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt new file mode 100644 index 000000000..cc18898ee --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt @@ -0,0 +1,20 @@ +package at.hannibal2.skyhanni.events + +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.Event + +abstract class LorenzEvent: Event() { + val eventName by lazy { + this::class.simpleName + } + + fun postAndCatch(): Boolean { + return runCatching { + MinecraftForge.EVENT_BUS.post(this) + }.onFailure { + it.printStackTrace() + LorenzUtils.chat("§cSkyHanni caught and logged an ${it::class.simpleName ?: "error"} at ${eventName}.") + }.getOrDefault(isCanceled) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/PacketEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/PacketEvent.kt new file mode 100644 index 000000000..abdb79b39 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/PacketEvent.kt @@ -0,0 +1,25 @@ +package at.hannibal2.skyhanni.events + +import net.minecraft.network.Packet +import net.minecraftforge.fml.common.eventhandler.Cancelable + +@Cancelable +open class PacketEvent(val packet: Packet<*>) : LorenzEvent() { + var direction: Direction? = null + + class ReceiveEvent(packet: Packet<*>) : PacketEvent(packet) { + init { + direction = Direction.INBOUND + } + } + + class SendEvent(packet: Packet<*>) : PacketEvent(packet) { + init { + direction = Direction.OUTBOUND + } + } + + enum class Direction { + INBOUND, OUTBOUND + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/PlayerSendChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/PlayerSendChatEvent.kt new file mode 100644 index 000000000..4ce9db1a8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/PlayerSendChatEvent.kt @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.events + +import at.hannibal2.skyhanni.chat.PlayerMessageChannel + + +class PlayerSendChatEvent( + val channel: PlayerMessageChannel, + val playerName: String, + var message: String, + var cancelledReason: String = "" +) : LorenzEvent()
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/items/HideNotClickableItems.kt b/src/main/java/at/hannibal2/skyhanni/items/HideNotClickableItems.kt new file mode 100644 index 000000000..dd533b3d2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/items/HideNotClickableItems.kt @@ -0,0 +1,440 @@ +package at.hannibal2.skyhanni.items + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.bazaar.BazaarApi +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.utils.ItemUtils +import at.hannibal2.skyhanni.utils.ItemUtils.cleanName +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.removeColorCodes +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.inventory.ContainerChest +import net.minecraft.item.ItemStack +import net.minecraftforge.event.entity.player.ItemTooltipEvent +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.lwjgl.opengl.GL11 + +class HideNotClickableItems { + + private var hideReason = "" + + private var lastClickTime = 0L + private var bypassUntil = 0L + + @SubscribeEvent + fun onBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!LorenzUtils.inSkyblock) return + if (isDisabled()) return + if (event.gui !is GuiChest) return + val guiChest = event.gui + val chest = guiChest.inventorySlots as ContainerChest + val chestName = chest.lowerChestInventory.displayName.unformattedText.trim() + + val lightingState = GL11.glIsEnabled(GL11.GL_LIGHTING) + GlStateManager.disableLighting() + GlStateManager.color(1f, 1f, 1f, 1f) + + for (slot in chest.inventorySlots) { + if (slot == null) continue + + if (slot.slotNumber == slot.slotIndex) continue + if (slot.stack == null) continue + + if (hide(chestName, slot.stack)) { + slot highlight LorenzColor.GRAY + } + } + + if (lightingState) GlStateManager.enableLighting() + } + + @SubscribeEvent + fun onDrawSlot(event: GuiContainerEvent.DrawSlotEvent.Pre) { + if (isDisabled()) return + if (event.gui !is GuiChest) return + val guiChest = event.gui + val chest = guiChest.inventorySlots as ContainerChest + val chestName = chest.lowerChestInventory.displayName.unformattedText.trim() + + val slot = event.slot + if (slot.slotNumber == slot.slotIndex) return + if (slot.stack == null) return + + val stack = slot.stack + + if (hide(chestName, stack)) { + event.isCanceled = true + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + fun onTooltip(event: ItemTooltipEvent) { + if (isDisabled()) return + if (event.toolTip == null) return + val guiChest = Minecraft.getMinecraft().currentScreen + if (guiChest !is GuiChest) return + val chest = guiChest.inventorySlots as ContainerChest + val chestName = chest.lowerChestInventory.displayName.unformattedText.trim() + + val stack = event.itemStack + if (ItemUtils.getItemsInOpenChest().contains(stack)) return + + if (hide(chestName, stack)) { + val first = event.toolTip[0] + event.toolTip.clear() + event.toolTip.add("§7" + first.removeColorCodes()) + event.toolTip.add("") + if (hideReason == "") { + event.toolTip.add("§4No hide reason!") + LorenzUtils.warning("Not hide reason for not clickable item!") + } else { + event.toolTip.add("§c$hideReason") + } + } + } + + @SubscribeEvent + fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) { + if (isDisabled()) return + if (event.gui !is GuiChest) return + val guiChest = event.gui + val chest = guiChest.inventorySlots as ContainerChest + val chestName = chest.lowerChestInventory.displayName.unformattedText.trim() + + val slot = event.slot ?: return + + if (slot.slotNumber == slot.slotIndex) return + if (slot.stack == null) return + + val stack = slot.stack + + if (hide(chestName, stack)) { + event.isCanceled = true + + if (System.currentTimeMillis() > lastClickTime + 5_000) { + lastClickTime = System.currentTimeMillis() + } + return + } + } + + private fun isDisabled(): Boolean { + if (bypassUntil > System.currentTimeMillis()) return true + + return !SkyHanniMod.feature.items.hideNotClickableItems + } + + private fun hide(chestName: String, stack: ItemStack): Boolean { + hideReason = "" + return when { + hideNpcSell(chestName, stack) -> true + hideChestBackpack(chestName, stack) -> true + hideSalvage(chestName, stack) -> true + hideTrade(chestName, stack) -> true + hideBazaarOrAH(chestName, stack) -> true + hideAccessoryBag(chestName, stack) -> true + hideSackOfSacks(chestName, stack) -> true + hideFishingBag(chestName, stack) -> true + hidePotionBag(chestName, stack) -> true + + else -> false + } + } + + private fun hidePotionBag(chestName: String, stack: ItemStack): Boolean { + if (!chestName.startsWith("Potion Bag")) return false + + val name = stack.cleanName() + if (isSkyBlockMenuItem(name)) { + hideReason = "The SkyBlock Menu cannot be put into the potion bag!" + return true + } + + if (stack.cleanName().endsWith(" Potion")) return false + + hideReason = "This item is not a potion!" + return true + } + + private fun hideFishingBag(chestName: String, stack: ItemStack): Boolean { + if (!chestName.startsWith("Fishing Bag")) return false + + val name = stack.cleanName() + if (isSkyBlockMenuItem(name)) { + hideReason = "The SkyBlock Menu cannot be put into the fishing bag!" + return true + } + + if (stack.cleanName().endsWith(" Bait")) return false + + hideReason = "This item is not a fishing bait!" + return true + } + + private fun hideSackOfSacks(chestName: String, stack: ItemStack): Boolean { + if (!chestName.startsWith("Sack of Sacks")) return false + + val name = stack.cleanName() + if (ItemUtils.isSack(name)) return false + if (isSkyBlockMenuItem(name)) return false + + hideReason = "This item is not a sack!" + return true + } + + private fun hideAccessoryBag(chestName: String, stack: ItemStack): Boolean { + if (!chestName.startsWith("Accessory Bag")) return false + + if (stack.getLore().any { it.contains("ACCESSORY") }) return false + if (isSkyBlockMenuItem(stack.cleanName())) return false + + hideReason = "This item is not an accessory!" + return true + } + + private fun hideTrade(chestName: String, stack: ItemStack): Boolean { + if (!chestName.startsWith("You ")) return false + + if (ItemUtils.isCoOpSoulBound(stack)) { + hideReason = "Coop-Soulbound items cannot be traded!" + return true + } + + val name = stack.cleanName() + + if (ItemUtils.isSack(name)) { + hideReason = "Sacks cannot be traded!" + return true + } + + if (isSkyBlockMenuItem(name)) { + hideReason = "The SkyBlock Menu cannot be traded!" + return true + } + + val result = when { + name.contains("Personal Deletor") -> true + name.contains("Day Crystal") -> true + name.contains("Night Crystal") -> true + name.contains("Cat Talisman") -> true + name.contains("Lynx Talisman") -> true + name.contains("Cheetah Talisman") -> true + else -> false + } + + if (result) hideReason = "This item cannot be traded!" + return result + } + + private fun hideNpcSell(chestName: String, stack: ItemStack): Boolean { + if (chestName != "Trades" && chestName != "Ophelia") return false + + var name = stack.cleanName() + val size = stack.stackSize + val amountText = " x$size" + if (name.endsWith(amountText)) { + name = name.substring(0, name.length - amountText.length) + } + + if (isSkyBlockMenuItem(name)) { + hideReason = "The SkyBlock Menu cannot be sold at the NPC!" + return true + } + + if (!ItemUtils.isRecombobulated(stack)) { + when (name) { + "Health Potion VIII Splash Potion" -> return false + "Stone Button" -> return false + "Revive Stone" -> return false + "Premium Flesh" -> return false + "Defuse Kit" -> return false + "White Wool" -> return false + "Enchanted Wool" -> return false + "Training Weights" -> return false + "Journal Entry" -> return false + "Twilight Arrow Poison" -> return false + "Lever" -> return false + + "Fairy's Galoshes" -> return false + } + if (name.endsWith("Gem Rune I")) return false + + if (name.startsWith("Music Disc")) return false + } + + hideReason = "This item should not be sold at the NPC!" + return true + } + + private fun hideChestBackpack(chestName: String, stack: ItemStack): Boolean { + if (!chestName.contains("Ender Chest") && !chestName.contains("Backpack")) return false + + val name = stack.cleanName() + + if (isSkyBlockMenuItem(name)) { + hideReason = "The SkyBlock Menu cannot be put into the storage!" + return true + } + if (ItemUtils.isSack(name)) { + hideReason = "Sacks cannot be put into the storage!" + return true + } + + val result = when { + name.endsWith(" New Year Cake Bag") -> true + name == "Nether Wart Pouch" -> true + name == "Basket of Seeds" -> true + name == "Builder's Wand" -> true + + else -> false + } + + if (result) hideReason = "Bags cannot be put into the storage!" + return result + } + + private fun hideSalvage(chestName: String, stack: ItemStack): Boolean { + if (chestName != "Salvage Item") return false + + if (ItemUtils.isRecombobulated(stack)) { + hideReason = "This item should not be salvaged! (Recombobulated)" + return true + } + for (line in stack.getLore()) { + if (line.contains("LEGENDARY DUNGEON")) { + hideReason = "This item should not be salvaged! (Legendary)" + return true + } + } + + val name = stack.cleanName() + + val armorSets = listOf( + "Zombie Knight", + "Heavy", + "Zombie Soldier", + "Skeleton Grunt", + "Skeleton Soldier", + "Zombie Commander", + "Skeleton Master", + "Sniper", + "Skeletor", + "Rotten", + ) + + val items = mutableListOf<String>() + for (armor in armorSets) { + items.add("$armor Helmet") + items.add("$armor Chestplate") + items.add("$armor Leggings") + items.add("$armor Boots") + } + + items.add("Zombie Soldier Cutlass") + items.add("Silent Death") + items.add("Zombie Knight Sword") + items.add("Conjuring") + items.add("Dreadlord Sword") + items.add("Soulstealer Bow") + items.add("Machine Gun Bow") + items.add("Earth Shard") + items.add("Zombie Commander Whip") + items.add("Sniper Bow") + + for (item in items) { + if (name.endsWith(" $item")) { + return false + } + } + + if (isSkyBlockMenuItem(name)) { + hideReason = "The SkyBlock Menu cannot be salvaged!" + return true + } + + hideReason = "This item cannot be salvaged!" + return true + } + + private fun hideBazaarOrAH(chestName: String, stack: ItemStack): Boolean { + val bazaarInventory = BazaarApi.isBazaarInventory(chestName) + + val auctionHouseInventory = + chestName == "Co-op Auction House" || chestName == "Auction House" || chestName == "Create BIN Auction" || chestName == "Create Auction" + if (!bazaarInventory && !auctionHouseInventory) return false + + + val displayName = stack.displayName + + if (isSkyBlockMenuItem(displayName.removeColorCodes())) { + if (bazaarInventory) hideReason = "The SkyBlock Menu is not a Bazaar Product!" + if (auctionHouseInventory) hideReason = "The SkyBlock Menu cannot be auctioned!" + return true + } + + if (bazaarInventory != BazaarApi.isBazaarItem(displayName)) { + if (bazaarInventory) hideReason = "This item is not a Bazaar Product!" + if (auctionHouseInventory) hideReason = "Bazaar Products cannot be auctioned!" + + return true + } + + if (isNotAuctionable(stack)) return true + + return false + } + + private fun isNotAuctionable(stack: ItemStack): Boolean { + if (ItemUtils.isCoOpSoulBound(stack)) { + hideReason = "Coop-Soulbound items cannot be auctioned!" + return true + } + + val name = stack.cleanName() + + if (ItemUtils.isSack(name)) { + hideReason = "Sacks cannot be auctioned!" + return true + } + + val result = when { + name.contains("Personal Deletor") -> true + name.contains("Day Crystal") -> true + name.contains("Night Crystal") -> true + + name.contains("Cat Talisman") -> true + name.contains("Lynx Talisman") -> true + name.contains("Cheetah Talisman") -> true + + name.contains("Hoe of Great Tilling") -> true + name.contains("Hoe of Greater Tilling") -> true + name.contains("InfiniDirt") -> true + name.contains("Prismapump") -> true + name.contains("Mathematical Hoe Blueprint") -> true + name.contains("Basket of Seeds") -> true + name.contains("Nether Wart Pouch") -> true + + name.contains("Carrot Hoe") -> true + name.contains("Sugar Cane Hoe") -> true + name.contains("Nether Warts Hoe") -> true + name.contains("Potato Hoe") -> true + name.contains("Melon Dicer") -> true + name.contains("Pumpkin Dicer") -> true + name.contains("Coco Chopper") -> true + name.contains("Wheat Hoe") -> true + + else -> false + } + + if (result) hideReason = "This item cannot be auctioned!" + return result + } + + private fun isSkyBlockMenuItem(name: String): Boolean = name == "SkyBlock Menu (Right Click)" +} diff --git a/src/main/java/at/hannibal2/skyhanni/items/ItemDisplayOverlayFeatures.kt b/src/main/java/at/hannibal2/skyhanni/items/ItemDisplayOverlayFeatures.kt new file mode 100644 index 000000000..6f2073969 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/items/ItemDisplayOverlayFeatures.kt @@ -0,0 +1,107 @@ +package at.hannibal2.skyhanni.items + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiRenderItemEvent +import at.hannibal2.skyhanni.utils.ItemUtils +import at.hannibal2.skyhanni.utils.ItemUtils.cleanName +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.between +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ItemDisplayOverlayFeatures { + + @SubscribeEvent + fun onRenderItemOverlayPost(event: GuiRenderItemEvent.RenderOverlayEvent.Post) { + val item = event.stack ?: return + + if (!LorenzUtils.inSkyblock || item.stackSize != 1) return + + val stackTip = getStackTip(item) + + if (stackTip.isNotEmpty()) { + GlStateManager.disableLighting() + GlStateManager.disableDepth() + GlStateManager.disableBlend() + event.fr.drawStringWithShadow( + stackTip, + (event.x + 17 - event.fr.getStringWidth(stackTip)).toFloat(), + (event.y + 9).toFloat(), + 16777215 + ) + GlStateManager.enableLighting() + GlStateManager.enableDepth() + } + + } + + private fun getStackTip(item: ItemStack): String { + val name = item.cleanName() + + if (SkyHanniMod.feature.items.displayMasterStarNumber) { + when (name) { + "First Master Star" -> return "1" + "Second Master Star" -> return "2" + "Third Master Star" -> return "3" + "Fourth Master Star" -> return "4" + "Fifth Master Star" -> return "5" + } + } + + if (SkyHanniMod.feature.items.displayMasterSkullNumber) { + if (name.matchRegex("(.*)Master Skull - Tier .")) { + return name.substring(name.length - 1) + } + } + + if (SkyHanniMod.feature.items.displayDungeonHeadFloor) { + if (name.contains("Golden ") || name.contains("Diamond ")) { + when { + name.contains("Bonzo") -> return "1" + name.contains("Scarf") -> return "2" + name.contains("Professor") -> return "3" + name.contains("Thorn") -> return "4" + name.contains("Livid") -> return "5" + name.contains("Sadan") -> return "6" + name.contains("Necron") -> return "7" + } + } + } + + if (SkyHanniMod.feature.items.displayNewYearCakeNumber) { + if (name.startsWith("New Year Cake (")) { + return "§b" + name.between("(Year ", ")") + } + } + + if (SkyHanniMod.feature.items.displayPetLevel) { + if (ItemUtils.isPet(name)) { + val level = name.between("Lvl ", "] ").toInt() + if (level != ItemUtils.maxPetLevel(name)) { + return "$level" + } + } + } + + if (SkyHanniMod.feature.items.displaySackName) { + if (ItemUtils.isSack(name)) { + val split = name.split(" ") + val sackName = split[split.size - 2] + return (if (name.contains("Enchanted")) "§5" else "") + sackName.substring(0, 2) + } + } + + if (SkyHanniMod.feature.items.displayMinionTier) { + if (name.contains(" Minion ")) { + val array = name.split(" ") + val last = array[array.size - 1] + return last.romanToDecimal().toString() + } + } + + return "" + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/ItemAbilityCooldown.kt b/src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/ItemAbilityCooldown.kt new file mode 100644 index 000000000..708b81693 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/ItemAbilityCooldown.kt @@ -0,0 +1,197 @@ +package at.hannibal2.skyhanni.items.abilitycooldown + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiRenderItemEvent +import at.hannibal2.skyhanni.events.LorenzActionBarEvent +import at.hannibal2.skyhanni.utils.ItemUtils +import at.hannibal2.skyhanni.utils.ItemUtils.cleanName +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.between +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.item.ItemStack +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +class ItemAbilityCooldown { + + var lastAbility = "" + var tick = 0 + val items = mutableMapOf<ItemStack, ItemText>() + val witherImpactDetection = WitherImpactDetection(this) + + init { + MinecraftForge.EVENT_BUS.register(witherImpactDetection) + } + + fun clickWitherImpact() { + Ability.WITHER_IMPACT.click() + } + + @SubscribeEvent + fun onActionBar(event: LorenzActionBarEvent) { + if (!isEnabled()) return + + val message: String = event.message + if (message.contains(" (§6")) { + if (message.contains("§b) ")) { + val name: String = message.between(" (§6", "§b) ") + if (name == lastAbility) return + lastAbility = name + for (ability in Ability.values()) { + if (ability.abilityName == name) { + click(ability) + return + } + } + return + } + } + lastAbility = "" + } + + private fun isEnabled(): Boolean { + return LorenzUtils.inSkyblock && SkyHanniMod.feature.items.itemAbilityCooldown + } + + private fun click(ability: Ability) { +// if (ability.isActive()) return + if (!ability.actionBarDetection) return + ability.click() + } + + @SubscribeEvent + fun onTick(event: TickEvent.ClientTickEvent) { + if (!isEnabled()) return + + tick++ + if (tick % 2 == 0) { + checkHotbar() + } + } + + private fun checkHotbar() { + items.clear() + for ((stack, slot) in ItemUtils.getItemsInInventoryWithSlots(true)) { +// val inHotbar = slot in 36..43 + + val itemName: String = stack.cleanName() + val ability = hasAbility(itemName) + if (ability != null) { + + if (ability.isOnCooldown()) { + val duration: Long = ability.lastClick + ability.getCooldown() - System.currentTimeMillis() + val color = if (duration < 600) LorenzColor.RED else LorenzColor.YELLOW + items[stack] = ItemText(color, ability.getDurationText(), true) + } else { + items[stack] = ItemText(LorenzColor.GREEN, "R", false) + } + } + } + + } + + @SubscribeEvent + fun onRenderItemOverlayPost(event: GuiRenderItemEvent.RenderOverlayEvent.Post) { + if (!isEnabled()) return + + val item = event.stack ?: return + if (item.stackSize != 1) return + + var stackTip = "" + + val guiOpen = Minecraft.getMinecraft().currentScreen != null + val itemText = items.filter { it.key == item } + .firstNotNullOfOrNull { it.value } ?: return + if (guiOpen && !itemText.onCooldown) return + + stackTip = itemText.color.getChatColor() + itemText.text + + if (stackTip.isNotEmpty()) { + GlStateManager.disableLighting() + GlStateManager.disableDepth() + GlStateManager.disableBlend() + event.fr.drawStringWithShadow( + stackTip, + (event.x + 17 - event.fr.getStringWidth(stackTip)).toFloat(), + (event.y + 9).toFloat(), + 16777215 + ) + GlStateManager.enableLighting() + GlStateManager.enableDepth() + } + } + + private fun hasAbility(itemName: String): Ability? { + for (ability in Ability.values()) { + for (name in ability.itemNames) { + if (itemName.contains(name)) { + return ability + } + } + } + return null + } + + enum class Ability( + val abilityName: String, + val cooldownInSeconds: Long, + vararg val itemNames: String, + var lastClick: Long = 0L, + val actionBarDetection: Boolean = true + ) { + ATOMSPLIT("Soulcry", 4, "Atomsplit Katana", "Vorpal Katana", "Voidedge Katana"), + WITHER_IMPACT("Wither Impact", 5, "Hyperion", "Scylla", "Valkyrie", "Astrea", actionBarDetection = false), + + HEAL_1("Small Heal", 7, "Wand of Healing"), + HEAL_2("Medium Heal", 7, "Wand of Mending"), + HEAL_3("Big Heal", 7, "Wand of Restoration"), + HEAL_4("Huge Heal", 7, "Wand of Atonement"), + + ICE_SPRAY("Ice Spray", 5, "Ice Spray Wand"), + GYRO("Gravity Storm", 30, "Gyrokinetic Wand"), + GIANTS_SWORD("Giant's Slam", 30, "Giant's Sword"), + + STAR_FALL("Starfall", 2, "Starlight Wand"), + VODOO_DOLL("Acupuncture", 5, "Voodoo Doll"), + INK_WAND("Ink Bomb", 30, "Ink Wand"), + GOLEM_SWORD("Iron Punch", 3, "Golem Sword"), + EMBER_ROD("Fire Blast", 30, "Ember Rod"), + ENDER_BOW("Ender Warp", 30, "Ender Bow"), + + LIVID_DAGGER("Throw", 5, "Livid Dagger"), + WEIRD_TUBA("Howl", 20, "Weird Tuba"), + + ENDSTONE_SWORD("Extreme Focus", 5, "End Stone Sword"), + PIGMAN_SWORD("Burning Souls", 5, "Pigman Sword"), + + SOULWARD("Soulward", 20, "Soul Esoward"); + + fun click() { + lastClick = System.currentTimeMillis() + } + + fun isOnCooldown(): Boolean = lastClick + getCooldown() > System.currentTimeMillis() + + fun getCooldown(): Long = cooldownInSeconds * 1000 + + fun getDurationText(): String { + var duration: Long = lastClick + getCooldown() - System.currentTimeMillis() + return if (duration < 1600) { + duration /= 100 + var d = duration.toDouble() + d /= 10.0 + LorenzUtils.formatDouble(d) + } else { + duration /= 1000 + duration++ + LorenzUtils.formatInteger(duration.toInt()) + } + } + + } + + class ItemText(val color: LorenzColor, val text: String, val onCooldown: Boolean) +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/WitherImpactDetection.kt b/src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/WitherImpactDetection.kt new file mode 100644 index 000000000..52caa59d0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/WitherImpactDetection.kt @@ -0,0 +1,65 @@ +package at.hannibal2.skyhanni.items.abilitycooldown + +import at.hannibal2.skyhanni.events.PacketEvent +import at.hannibal2.skyhanni.utils.ItemUtil.asStringSet +import at.hannibal2.skyhanni.utils.ItemUtil.getExtraAttributes +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraft.client.Minecraft +import net.minecraft.init.Items +import net.minecraft.network.play.client.C08PacketPlayerBlockPlacement +import net.minecraft.network.play.server.S1CPacketEntityMetadata +import net.minecraft.network.play.server.S2APacketParticles +import net.minecraft.util.EnumParticleTypes +import net.minecraftforge.common.util.Constants +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +/** + * Taken from Skytils under AGPL 3.0 + * Modified + * https://github.com/Skytils/SkytilsMod/blob/1.x/LICENSE.md + * @author Skytils + */ +class WitherImpactDetection(private val itemAbilityCooldown: ItemAbilityCooldown) { + + val S2APacketParticles.type: EnumParticleTypes + get() = this.particleType + var lastShieldUse = -1L + var lastShieldClick = 0L + + @SubscribeEvent + fun onReceivePacket(event: PacketEvent.ReceiveEvent) { + val mc = Minecraft.getMinecraft() + if (!LorenzUtils.inSkyblock || mc.theWorld == null) return + + event.packet.apply { + + if (this is S1CPacketEntityMetadata && lastShieldClick != -1L && entityId == mc.thePlayer?.entityId && System.currentTimeMillis() - lastShieldClick <= 500 && func_149376_c()?.any { it.dataValueId == 17 } == true) { + lastShieldUse = System.currentTimeMillis() + lastShieldClick = -1 + itemAbilityCooldown.clickWitherImpact() + } + } + } + + @SubscribeEvent + fun onSendPacket(event: PacketEvent.SendEvent) { + val mc = Minecraft.getMinecraft() + if (!LorenzUtils.inSkyblock || lastShieldUse != -1L || mc.thePlayer?.heldItem == null) return + if (event.packet is C08PacketPlayerBlockPlacement && mc.thePlayer.heldItem.item == Items.iron_sword && getExtraAttributes( + mc.thePlayer.heldItem + )?.getTagList("ability_scroll", Constants.NBT.TAG_STRING)?.asStringSet() + ?.contains("WITHER_SHIELD_SCROLL") == true + ) { + lastShieldClick = System.currentTimeMillis() + } + } + + @SubscribeEvent + fun onTick(event: TickEvent.ClientTickEvent) { + if (lastShieldUse != -1L) { + val diff = ((lastShieldUse + 5000 - System.currentTimeMillis()) / 1000f) + if (diff < 0) lastShieldUse = -1 + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/misc/ButtonOnPause.kt b/src/main/java/at/hannibal2/skyhanni/misc/ButtonOnPause.kt new file mode 100644 index 000000000..c42c304db --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/misc/ButtonOnPause.kt @@ -0,0 +1,51 @@ +package at.hannibal2.skyhanni.misc + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.config.ConfigEditor +import at.hannibal2.skyhanni.config.core.GuiScreenElementWrapper +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraft.client.gui.GuiButton +import net.minecraft.client.gui.GuiIngameMenu +import net.minecraftforge.client.event.GuiScreenEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ButtonOnPause { + private val buttonId = System.nanoTime().toInt() + + @SubscribeEvent + fun onGuiAction(event: GuiScreenEvent.ActionPerformedEvent.Post) { + if (!LorenzUtils.isOnHypixel) return + + if (SkyHanniMod.feature.misc.configButtonOnPause && event.gui is GuiIngameMenu && event.button.id == buttonId) { + SkyHanniMod.screenToOpen = GuiScreenElementWrapper( + ConfigEditor( + SkyHanniMod.feature + ) + ) + } + } + + @SubscribeEvent + fun onGuiInitPost(event: GuiScreenEvent.InitGuiEvent.Post) { + if (!LorenzUtils.isOnHypixel) return + + if (SkyHanniMod.feature.misc.configButtonOnPause && event.gui is GuiIngameMenu) { + val x = event.gui.width - 105 + val x2 = x + 100 + var y = event.gui.height - 22 + var y2 = y + 20 + val sorted = event.buttonList.sortedWith { a, b -> b.yPosition + b.height - a.yPosition + a.height } + for (button in sorted) { + val otherX = button.xPosition + val otherX2 = button.xPosition + button.width + val otherY = button.yPosition + val otherY2 = button.yPosition + button.height + if (otherX2 > x && otherX < x2 && otherY2 > y && otherY < y2) { + y = otherY - 20 - 2 + y2 = y + 20 + } + } + event.buttonList.add(GuiButton(buttonId, x, 0.coerceAtLeast(y), 100, 20, "SkyHanni")) + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/misc/CurrentPetDisplay.kt b/src/main/java/at/hannibal2/skyhanni/misc/CurrentPetDisplay.kt new file mode 100644 index 000000000..5adf570b5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/misc/CurrentPetDisplay.kt @@ -0,0 +1,53 @@ +package at.hannibal2.skyhanni.misc + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.GuiRender.renderString +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.between +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import net.minecraftforge.client.event.RenderGameOverlayEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class CurrentPetDisplay { + + companion object { + var currentPet: String = "" + } + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyblock) return + + var blocked = false + + val message = event.message + if (message.matchRegex("§aYou summoned your §r(.*)§r§a!")) { + currentPet = message.between("your §r", "§r§a") + blocked = true + } + if (message.matchRegex("§cAutopet §eequipped your §7(.*)§e! §a§lVIEW RULE")) { + currentPet = message.between("] ", "§e!") + blocked = true + } + if (message.matchRegex("§aYou despawned your §r(.*)§r§a!")) { + currentPet = "" + blocked = true + } + + if (blocked && SkyHanniMod.feature.misc.petDisplay) { + event.blockedReason = "pets" + } + } + + + @SubscribeEvent + fun renderOverlay(event: RenderGameOverlayEvent.Post) { + if (!LorenzUtils.inSkyblock) return + + if (!SkyHanniMod.feature.misc.petDisplay) return + if (currentPet == "") return + + SkyHanniMod.feature.misc.petDisplayPos.renderString(currentPet) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/misc/ExpBottleOnGroundHider.kt b/src/main/java/at/hannibal2/skyhanni/misc/ExpBottleOnGroundHider.kt new file mode 100644 index 000000000..119160c4f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/misc/ExpBottleOnGroundHider.kt @@ -0,0 +1,19 @@ +package at.hannibal2.skyhanni.misc + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.CheckRenderEntityEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraft.entity.item.EntityXPOrb +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ExpBottleOnGroundHider { + @SubscribeEvent + fun onCheckRender(event: CheckRenderEntityEvent<*>) { + if (!LorenzUtils.inSkyblock) return + if (!SkyHanniMod.feature.misc.hideExpBottles) return + + if (event.entity is EntityXPOrb) { + event.isCanceled = true + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/misc/HypixelData.kt b/src/main/java/at/hannibal2/skyhanni/misc/HypixelData.kt new file mode 100644 index 000000000..29fa2a242 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/misc/HypixelData.kt @@ -0,0 +1,63 @@ +package at.hannibal2.skyhanni.misc + +import at.hannibal2.skyhanni.events.PacketEvent +import net.minecraft.client.Minecraft +import net.minecraft.network.play.server.S38PacketPlayerListItem +import net.minecraft.network.play.server.S3DPacketDisplayScoreboard +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.network.FMLNetworkEvent + +class HypixelData { + + companion object { + var hypixel = false + var skyblock = false + var dungeon = false +} + + @SubscribeEvent + fun onConnect(event: FMLNetworkEvent.ClientConnectedToServerEvent) { + hypixel = Minecraft.getMinecraft().runCatching { + !event.isLocal && (thePlayer?.clientBrand?.lowercase()?.contains("hypixel") + ?: currentServerData?.serverIP?.lowercase()?.contains("hypixel") ?: false) + }.onFailure { it.printStackTrace() }.getOrDefault(false) + } + + @SubscribeEvent + fun onScoreboardChange(event: PacketEvent.ReceiveEvent) { + if (!hypixel || skyblock || event.packet !is S3DPacketDisplayScoreboard) return + if (event.packet.func_149371_c() != 1) return + skyblock = event.packet.func_149370_d() == "SBScoreboard" + } + + val areaRegex = Regex("§r§b§l(?<area>[\\w]+): §r§7(?<loc>[\\w ]+)§r") + + @SubscribeEvent + fun onTabUpdate(event: PacketEvent.ReceiveEvent) { + if (dungeon || !hypixel || event.packet !is S38PacketPlayerListItem || + (event.packet.action != S38PacketPlayerListItem.Action.UPDATE_DISPLAY_NAME && + event.packet.action != S38PacketPlayerListItem.Action.ADD_PLAYER) + ) return + event.packet.entries.forEach { playerData -> + val name = playerData?.displayName?.formattedText ?: playerData?.profile?.name ?: return@forEach + areaRegex.matchEntire(name)?.let { result -> + dungeon = skyblock && result.groups["area"]?.value == "Dungeon" + return@forEach + } + } + } + + @SubscribeEvent + fun onWorldChange(event: WorldEvent.Load) { + skyblock = false + dungeon = false + } + + @SubscribeEvent + fun onDisconnect(event: FMLNetworkEvent.ClientDisconnectionFromServerEvent) { + hypixel = false + skyblock = false + dungeon = false + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/misc/ScoreboardData.kt b/src/main/java/at/hannibal2/skyhanni/misc/ScoreboardData.kt new file mode 100644 index 000000000..166b6ce17 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/misc/ScoreboardData.kt @@ -0,0 +1,45 @@ +package at.hannibal2.skyhanni.misc + +import at.hannibal2.skyhanni.utils.LorenzUtils.removeColorCodes +import net.minecraft.client.Minecraft +import net.minecraft.scoreboard.Score +import net.minecraft.scoreboard.ScorePlayerTeam +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +class ScoreboardData { + + companion object { + var sidebarLines: List<String> = emptyList() + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + fun onTick(event: TickEvent.ClientTickEvent) { + if (event.phase != TickEvent.Phase.START) return + + sidebarLines = fetchScoreboardLines().map { cleanSB(it) } + } + + private fun cleanSB(scoreboard: String): String { + return scoreboard.removeColorCodes().toCharArray().filter { it.code in 21..126 }.joinToString(separator = "") + } + + fun fetchScoreboardLines(): List<String> { + val scoreboard = Minecraft.getMinecraft().theWorld?.scoreboard ?: return emptyList() + val objective = scoreboard.getObjectiveInDisplaySlot(1) ?: return emptyList() + var scores = scoreboard.getSortedScores(objective) + val list = scores.filter { input: Score? -> + input != null && input.playerName != null && !input.playerName + .startsWith("#") + } + scores = if (list.size > 15) { + list.drop(15) + } else { + list + } + return scores.map { + ScorePlayerTeam.formatPlayerName(scoreboard.getPlayersTeam(it.playerName), it.playerName) + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/mixinhooks/GuiContainerHook.kt b/src/main/java/at/hannibal2/skyhanni/mixinhooks/GuiContainerHook.kt new file mode 100644 index 000000000..6f2c5c03e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixinhooks/GuiContainerHook.kt @@ -0,0 +1,61 @@ +package at.hannibal2.skyhanni.mixinhooks + +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.GuiContainerEvent.CloseWindowEvent +import at.hannibal2.skyhanni.events.GuiContainerEvent.SlotClickEvent +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.inventory.Slot +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo + +class GuiContainerHook(guiAny: Any) { + + val gui: GuiContainer + + init { + gui = guiAny as GuiContainer + } + + fun closeWindowPressed(ci: CallbackInfo) { + if (CloseWindowEvent(gui, gui.inventorySlots).postAndCatch()) ci.cancel() + } + + fun backgroundDrawn(mouseX: Int, mouseY: Int, partialTicks: Float, ci: CallbackInfo) { + GuiContainerEvent.BackgroundDrawnEvent( + gui, + gui.inventorySlots, + mouseX, + mouseY, + partialTicks + ).postAndCatch() + } + + fun foregroundDrawn(mouseX: Int, mouseY: Int, partialTicks: Float, ci: CallbackInfo) { + GuiContainerEvent.ForegroundDrawnEvent(gui, gui.inventorySlots, mouseX, mouseY, partialTicks).postAndCatch() + } + + fun onDrawSlot(slot: Slot, ci: CallbackInfo) { + if (GuiContainerEvent.DrawSlotEvent.Pre( + gui, + gui.inventorySlots, + slot + ).postAndCatch() + ) ci.cancel() + } + + fun onDrawSlotPost(slot: Slot, ci: CallbackInfo) { + GuiContainerEvent.DrawSlotEvent.Post(gui, gui.inventorySlots, slot).postAndCatch() + } + + fun onMouseClick(slot: Slot?, slotId: Int, clickedButton: Int, clickType: Int, ci: CallbackInfo) { + if ( + SlotClickEvent( + gui, + gui.inventorySlots, + slot, + slotId, + clickedButton, + clickType + ).postAndCatch() + ) ci.cancel() + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/mixinhooks/NetHandlerPlayClientHook.kt b/src/main/java/at/hannibal2/skyhanni/mixinhooks/NetHandlerPlayClientHook.kt new file mode 100644 index 000000000..57511d618 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixinhooks/NetHandlerPlayClientHook.kt @@ -0,0 +1,9 @@ +package at.hannibal2.skyhanni.mixinhooks + +import at.hannibal2.skyhanni.events.PacketEvent +import net.minecraft.network.Packet +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo + +fun onSendPacket(packet: Packet<*>, ci: CallbackInfo) { + if (PacketEvent.SendEvent(packet).postAndCatch()) ci.cancel() +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/mixinhooks/NetworkManagerHook.kt b/src/main/java/at/hannibal2/skyhanni/mixinhooks/NetworkManagerHook.kt new file mode 100644 index 000000000..44746cfbf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixinhooks/NetworkManagerHook.kt @@ -0,0 +1,10 @@ +package at.hannibal2.skyhanni.mixinhooks + +import at.hannibal2.skyhanni.events.PacketEvent +import io.netty.channel.ChannelHandlerContext +import net.minecraft.network.Packet +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo + +fun onReceivePacket(context: ChannelHandlerContext, packet: Packet<*>, ci: CallbackInfo) { + if (PacketEvent.ReceiveEvent(packet).postAndCatch()) ci.cancel() +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderItemHook.kt b/src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderItemHook.kt new file mode 100644 index 000000000..3b8dd925d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderItemHook.kt @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.mixinhooks + +import at.hannibal2.skyhanni.events.GuiRenderItemEvent +import net.minecraft.client.gui.FontRenderer +import net.minecraft.item.ItemStack +import net.minecraft.util.ResourceLocation +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo + +val RES_ITEM_GLINT = ResourceLocation("textures/misc/enchanted_item_glint.png") + +var skipGlint = false + +fun renderItemOverlayPost( + fr: FontRenderer, + stack: ItemStack?, + xPosition: Int, + yPosition: Int, + text: String?, + ci: CallbackInfo +) { + GuiRenderItemEvent.RenderOverlayEvent.Post( + fr, + stack, + xPosition, + yPosition, + text + ).postAndCatch() +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderManagerHook.kt b/src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderManagerHook.kt new file mode 100644 index 000000000..a183a0caa --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderManagerHook.kt @@ -0,0 +1,25 @@ +package at.hannibal2.skyhanni.mixinhooks + +import at.hannibal2.skyhanni.events.CheckRenderEntityEvent +import net.minecraft.client.renderer.culling.ICamera +import net.minecraft.entity.Entity +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable + +fun shouldRender( + entityIn: Entity, + camera: ICamera, + camX: Double, + camY: Double, + camZ: Double, + cir: CallbackInfoReturnable<Boolean> +) { + if ( + CheckRenderEntityEvent( + entityIn, + camera, + camX, + camY, + camZ + ).postAndCatch() + ) cir.returnValue = false +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/MixinGuiContainer.java b/src/main/java/at/hannibal2/skyhanni/mixins/MixinGuiContainer.java new file mode 100644 index 000000000..18997c33b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixins/MixinGuiContainer.java @@ -0,0 +1,48 @@ +package at.hannibal2.skyhanni.mixins; + +import at.hannibal2.skyhanni.mixinhooks.GuiContainerHook; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Slot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GuiContainer.class) +public abstract class MixinGuiContainer extends GuiScreen { + + @Unique + private final GuiContainerHook hook = new GuiContainerHook(this); + + @Inject(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityPlayerSP;closeScreen()V", shift = At.Shift.BEFORE), cancellable = true) + private void closeWindowPressed(CallbackInfo ci) { + hook.closeWindowPressed(ci); + } + + @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;color(FFFF)V", ordinal = 1)) + private void backgroundDrawn(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { + hook.backgroundDrawn(mouseX, mouseY, partialTicks, ci); + } + + @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/inventory/GuiContainer;drawGuiContainerForegroundLayer(II)V", shift = At.Shift.AFTER)) + private void onForegroundDraw(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { + hook.foregroundDrawn(mouseX, mouseY, partialTicks, ci); + } + + @Inject(method = "drawSlot", at = @At("HEAD"), cancellable = true) + private void onDrawSlot(Slot slot, CallbackInfo ci) { + hook.onDrawSlot(slot, ci); + } + + @Inject(method = "drawSlot", at = @At("RETURN"), cancellable = true) + private void onDrawSlotPost(Slot slot, CallbackInfo ci) { + hook.onDrawSlotPost(slot, ci); + } + + @Inject(method = "handleMouseClick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/PlayerControllerMP;windowClick(IIIILnet/minecraft/entity/player/EntityPlayer;)Lnet/minecraft/item/ItemStack;"), cancellable = true) + private void onMouseClick(Slot slot, int slotId, int clickedButton, int clickType, CallbackInfo ci) { + hook.onMouseClick(slot, slotId, clickedButton, clickType, ci); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/MixinNetHandlerPlayClient.java b/src/main/java/at/hannibal2/skyhanni/mixins/MixinNetHandlerPlayClient.java new file mode 100644 index 000000000..3e91bdfe3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixins/MixinNetHandlerPlayClient.java @@ -0,0 +1,24 @@ +package at.hannibal2.skyhanni.mixins; + +import at.hannibal2.skyhanni.mixinhooks.NetHandlerPlayClientHookKt; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.network.Packet; +import net.minecraft.network.play.INetHandlerPlayClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = NetHandlerPlayClient.class, priority = 1001) +public abstract class MixinNetHandlerPlayClient implements INetHandlerPlayClient { + + @Shadow + private WorldClient clientWorldController; + + @Inject(method = "addToSendQueue", at = @At("HEAD"), cancellable = true) + private void onSendPacket(Packet<?> packet, CallbackInfo ci) { + NetHandlerPlayClientHookKt.onSendPacket(packet, ci); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/MixinNetworkManager.java b/src/main/java/at/hannibal2/skyhanni/mixins/MixinNetworkManager.java new file mode 100644 index 000000000..83f7f51bb --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixins/MixinNetworkManager.java @@ -0,0 +1,20 @@ +package at.hannibal2.skyhanni.mixins; + +import at.hannibal2.skyhanni.mixinhooks.NetworkManagerHookKt; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.Packet; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = NetworkManager.class, priority = 1001) +public abstract class MixinNetworkManager extends SimpleChannelInboundHandler<Packet<?>> { + + @Inject(method = "channelRead0", at = @At("HEAD"), cancellable = true) + private void onReceivePacket(ChannelHandlerContext context, Packet<?> packet, CallbackInfo ci) { + NetworkManagerHookKt.onReceivePacket(context, packet, ci); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderItem.java b/src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderItem.java new file mode 100644 index 000000000..e2dc8926c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderItem.java @@ -0,0 +1,41 @@ +package at.hannibal2.skyhanni.mixins; + +import at.hannibal2.skyhanni.mixinhooks.RenderItemHookKt; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(RenderItem.class) +public abstract class MixinRenderItem { + + // @Inject(method = "renderItemIntoGUI", at = @At("HEAD")) + // private void renderRarity(ItemStack stack, int x, int y, CallbackInfo ci) { + // RenderItemHookKt.renderRarity(stack, x, y, ci); + // } + + @Inject(method = "renderItemOverlayIntoGUI", at = @At("RETURN")) + private void renderItemOverlayPost(FontRenderer fr, ItemStack stack, int xPosition, int yPosition, String text, CallbackInfo ci) { + RenderItemHookKt.renderItemOverlayPost(fr, stack, xPosition, yPosition, text, ci); + } + + // @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/resources/model/IBakedModel;)V", at = @At(value = "INVOKE", target = "net/minecraft/client/renderer/GlStateManager.scale(FFF)V", shift = At.Shift.AFTER)) + // private void renderItemPre(ItemStack stack, IBakedModel model, CallbackInfo ci) { + // RenderItemHookKt.renderItemPre(stack, model, ci); + // } + // + // @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/resources/model/IBakedModel;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RenderItem;renderEffect(Lnet/minecraft/client/resources/model/IBakedModel;)V", shift = At.Shift.BEFORE), cancellable = true) + // private void modifyGlintRendering(ItemStack stack, IBakedModel model, CallbackInfo ci) { + // RenderItemHookKt.modifyGlintRendering(stack, model, ci); + // } + + @Inject(method = "renderEffect", at = @At("HEAD"), cancellable = true) + public void onRenderEffect(CallbackInfo ci) { + if (RenderItemHookKt.getSkipGlint()) { + ci.cancel(); + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderManager.java b/src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderManager.java new file mode 100644 index 000000000..f57786a9b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderManager.java @@ -0,0 +1,19 @@ +package at.hannibal2.skyhanni.mixins; + +import at.hannibal2.skyhanni.mixinhooks.RenderManagerHookKt; +import net.minecraft.client.renderer.culling.ICamera; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(RenderManager.class) +public class MixinRenderManager { + + @Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true) + private void shouldRender(Entity entityIn, ICamera camera, double camX, double camY, double camZ, CallbackInfoReturnable<Boolean> cir) { + RenderManagerHookKt.shouldRender(entityIn, camera, camX, camY, camZ, cir); + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/test/LorenzTest.kt b/src/main/java/at/hannibal2/skyhanni/test/LorenzTest.kt new file mode 100644 index 000000000..9cde01cc6 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/test/LorenzTest.kt @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.test + +import at.hannibal2.skyhanni.SkyHanniMod +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import at.hannibal2.skyhanni.utils.GuiRender.renderString +import at.hannibal2.skyhanni.utils.LorenzLogger +import net.minecraftforge.client.event.RenderGameOverlayEvent + +class LorenzTest { + + var log = LorenzLogger("debug/packets") + + companion object { + var enabled = false + var text = "" + + val debugLogger = LorenzLogger("debug/test") + } + + @SubscribeEvent + fun renderOverlay(event: RenderGameOverlayEvent.Post) { + if (!SkyHanniMod.feature.debug.enabled) return + + if (enabled) { + SkyHanniMod.feature.debug.testPos.renderString(text) + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt new file mode 100644 index 000000000..c321cf2d5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt @@ -0,0 +1,48 @@ +package at.hannibal2.skyhanni.utils + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import org.apache.http.client.config.RequestConfig +import org.apache.http.client.methods.HttpGet +import org.apache.http.impl.client.HttpClientBuilder +import org.apache.http.impl.client.HttpClients +import org.apache.http.message.BasicHeader +import org.apache.http.util.EntityUtils + + +object APIUtil { + private val parser = JsonParser() + + val builder: HttpClientBuilder = + HttpClients.custom().setUserAgent("SkyHanni") + .setDefaultHeaders( + mutableListOf( + BasicHeader("Pragma", "no-cache"), + BasicHeader("Cache-Control", "no-cache") + ) + ) + .setDefaultRequestConfig( + RequestConfig.custom() + .build() + ) + .useSystemProperties() + + fun getJSONResponse(urlString: String): JsonObject { + val client = builder.build() + try { + client.execute(HttpGet(urlString)).use { response -> + val entity = response.entity + if (entity != null) { + val retSrc = EntityUtils.toString(entity) + return parser.parse(retSrc) as JsonObject + } + } + } catch (ex: Throwable) { + ex.printStackTrace() + LorenzUtils.error("SkyHanni ran into an ${ex::class.simpleName ?: "error"} whilst fetching a resource. See logs for more details.") + } finally { + client.close() + } + return JsonObject() + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/BlockUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/BlockUtils.kt new file mode 100644 index 000000000..368b06daf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/BlockUtils.kt @@ -0,0 +1,23 @@ +package at.hannibal2.skyhanni.utils + +import net.minecraft.block.Block +import net.minecraft.client.Minecraft +import net.minecraft.tileentity.TileEntitySkull +import net.minecraft.util.BlockPos +import net.minecraftforge.common.util.Constants + +object BlockUtils { + + fun LorenzVec.getBlockAt(): Block = + Minecraft.getMinecraft().theWorld.getBlockState(toBlocPos()).block + + fun LorenzVec.isInLoadedChunk(): Boolean = + Minecraft.getMinecraft().theWorld.chunkProvider.provideChunk(toBlocPos()).isLoaded + + fun getSkinFromSkull(position: BlockPos?): String? { + val entity = Minecraft.getMinecraft().theWorld.getTileEntity(position) as TileEntitySkull + val serializeNBT = entity.serializeNBT() + return serializeNBT.getCompoundTag("Owner").getCompoundTag("Properties") + .getTagList("textures", Constants.NBT.TAG_COMPOUND).getCompoundTagAt(0).getString("Value") + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/GuiRender.kt b/src/main/java/at/hannibal2/skyhanni/utils/GuiRender.kt new file mode 100644 index 000000000..221cbd9a2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/GuiRender.kt @@ -0,0 +1,32 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.config.core.config.Position +import at.hannibal2.skyhanni.utils.LorenzUtils.removeColorCodes +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.renderer.GlStateManager + +object GuiRender { + + fun Position.renderString(string: String) { + val textToRender = "§f$string" + + GlStateManager.pushMatrix() + val resolution = ScaledResolution(Minecraft.getMinecraft()) + + val renderer = Minecraft.getMinecraft().renderManager.fontRenderer + + val offsetX = (200 - renderer.getStringWidth(textToRender.removeColorCodes())) / 2 + + val x = getAbsX(resolution, 200) + offsetX + val y = getAbsY(resolution, 16) + + + + GlStateManager.translate(x + 1.0, y + 1.0, 0.0) + renderer.drawStringWithShadow(textToRender, 0f, 0f, 0) + + + GlStateManager.popMatrix() + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtil.kt new file mode 100644 index 000000000..d10adc06c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtil.kt @@ -0,0 +1,213 @@ +package at.hannibal2.skyhanni.utils + +import net.minecraft.init.Items +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.NBTTagList +import net.minecraft.nbt.NBTTagString +import net.minecraftforge.common.util.Constants +import java.util.* + +object ItemUtil { + private val PET_PATTERN = "§7\\[Lvl \\d+] (?<color>§[0-9a-fk-or]).+".toRegex() + const val NBT_INTEGER = 3 + private const val NBT_STRING = 8 + private const val NBT_LIST = 9 + private const val NBT_COMPOUND = 10 + + /** + * Returns the display name of a given item + * @author Mojang + * @param item the Item to get the display name of + * @return the display name of the item + */ + @JvmStatic + fun getDisplayName(item: ItemStack): String { + var s = item.item.getItemStackDisplayName(item) + if (item.tagCompound != null && item.tagCompound.hasKey("display", 10)) { + val nbtTagCompound = item.tagCompound.getCompoundTag("display") + if (nbtTagCompound.hasKey("Name", 8)) { + s = nbtTagCompound.getString("Name") + } + } + return s + } + + /** + * Returns the Skyblock Item ID of a given Skyblock item + * + * @author BiscuitDevelopment + * @param item the Skyblock item to check + * @return the Skyblock Item ID of this item or `null` if this isn't a valid Skyblock item + */ + @JvmStatic + fun getSkyBlockItemID(item: ItemStack?): String? { + if (item == null) { + return null + } + val extraAttributes = getExtraAttributes(item) ?: return null + return if (!extraAttributes.hasKey("id", NBT_STRING)) { + null + } else extraAttributes.getString("id") + } + + /** + * Returns the `ExtraAttributes` compound tag from the item's NBT data. + * + * @author BiscuitDevelopment + * @param item the item to get the tag from + * @return the item's `ExtraAttributes` compound tag or `null` if the item doesn't have one + */ + @JvmStatic + fun getExtraAttributes(item: ItemStack?): NBTTagCompound? { + return if (item == null || !item.hasTagCompound()) { + null + } else item.getSubCompound("ExtraAttributes", false) + } + + /** + * Returns the Skyblock Item ID of a given Skyblock Extra Attributes NBT Compound + * + * @author BiscuitDevelopment + * @param extraAttributes the NBT to check + * @return the Skyblock Item ID of this item or `null` if this isn't a valid Skyblock NBT + */ + @JvmStatic + fun getSkyBlockItemID(extraAttributes: NBTTagCompound?): String? { + if (extraAttributes != null) { + val itemId = extraAttributes.getString("id") + if (itemId.isNotEmpty()) { + return itemId + } + } + return null + } + + /** + * Returns a string list containing the nbt lore of an ItemStack, or + * an empty list if this item doesn't have a lore. The returned lore + * list is unmodifiable since it has been converted from an NBTTagList. + * + * @author BiscuitDevelopment + * @param itemStack the ItemStack to get the lore from + * @return the lore of an ItemStack as a string list + */ + @JvmStatic + fun getItemLore(itemStack: ItemStack): List<String> { + if (itemStack.hasTagCompound() && itemStack.tagCompound.hasKey("display", NBT_COMPOUND)) { + val display = itemStack.tagCompound.getCompoundTag("display") + if (display.hasKey("Lore", NBT_LIST)) { + val lore = display.getTagList("Lore", NBT_STRING) + val loreAsList = ArrayList<String>(lore.tagCount()) + for (lineNumber in 0 until lore.tagCount()) { + loreAsList.add(lore.getStringTagAt(lineNumber)) + } + return Collections.unmodifiableList(loreAsList) + } + } + return emptyList() + } + +// @JvmStatic +// fun hasRightClickAbility(itemStack: ItemStack): Boolean { +// for (line in getItemLore(itemStack)) { +// val stripped = line.stripControlCodes() +// if (stripped.startsWith("Item Ability:") && stripped.endsWith("RIGHT CLICK")) return true +// } +// return false +// } + +// /** +// * Returns the rarity of a given Skyblock item +// * Modified +// * @author BiscuitDevelopment +// * @param item the Skyblock item to check +// * @return the rarity of the item if a valid rarity is found, `null` if no rarity is found, `null` if item is `null` +// */ +// fun getRarity(item: ItemStack?): ItemRarity { +// if (item == null || !item.hasTagCompound()) { +// return ItemRarity.NONE +// } +// val display = item.getSubCompound("display", false) +// if (display == null || !display.hasKey("Lore")) { +// return ItemRarity.NONE +// } +// val lore = display.getTagList("Lore", Constants.NBT.TAG_STRING) +// val name = display.getString("Name") +// +// // Determine the item's rarity +// for (i in (lore.tagCount() - 1) downTo 0) { +// val currentLine = lore.getStringTagAt(i) +// val rarityMatcher = RARITY_PATTERN.find(currentLine) +// if (rarityMatcher != null) { +// val rarity = rarityMatcher.groups["rarity"]?.value ?: continue +// ItemRarity.values().find { +// it.rarityName == rarity.stripControlCodes().substringAfter("SHINY ") +// }?.let { +// return it +// } +// } +// } +// val petRarityMatcher = PET_PATTERN.find(name) +// if (petRarityMatcher != null) { +// val color = petRarityMatcher.groupValues.getOrNull(1) ?: return ItemRarity.NONE +// return ItemRarity.byBaseColor(color) ?: ItemRarity.NONE +// } +// +// // If the item doesn't have a valid rarity, return null +// return ItemRarity.NONE +// } + + fun isPet(item: ItemStack?): Boolean { + if (item == null || !item.hasTagCompound()) { + return false + } + val display = item.getSubCompound("display", false) + if (display == null || !display.hasKey("Lore")) { + return false + } + val name = display.getString("Name") + + return PET_PATTERN.matches(name) + } + + fun setSkullTexture(item: ItemStack, texture: String, SkullOwner: String): ItemStack { + val textureTagCompound = NBTTagCompound() + textureTagCompound.setString("Value", texture) + + val textures = NBTTagList() + textures.appendTag(textureTagCompound) + + val properties = NBTTagCompound() + properties.setTag("textures", textures) + + val skullOwner = NBTTagCompound() + skullOwner.setString("Id", SkullOwner) + skullOwner.setTag("Properties", properties) + + val nbtTag = NBTTagCompound() + nbtTag.setTag("SkullOwner", skullOwner) + + item.tagCompound = nbtTag + return item + } + + fun getSkullTexture(item: ItemStack): String? { + if (item.item != Items.skull) return null + val nbt = item.tagCompound + if (!nbt.hasKey("SkullOwner")) return null + return nbt.getCompoundTag("SkullOwner").getCompoundTag("Properties") + .getTagList("textures", Constants.NBT.TAG_COMPOUND).getCompoundTagAt(0).getString("Value") + } + + fun ItemStack.setLore(lines: List<String>): ItemStack { + setTagInfo("display", getSubCompound("display", true).apply { + setTag("Lore", NBTTagList().apply { + for (line in lines) appendTag(NBTTagString(line)) + }) + }) + return this + } + + fun NBTTagList.asStringSet() = (0..tagCount()).mapTo(hashSetOf()) { getStringTagAt(it) } +}
\ 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 new file mode 100644 index 000000000..b077c4121 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt @@ -0,0 +1,72 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.utils.LorenzUtils.matchRegex +import at.hannibal2.skyhanni.utils.LorenzUtils.removeColorCodes +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.item.ItemStack + +object ItemUtils { + + fun ItemStack.cleanName() = this.displayName.removeColorCodes() + + fun getItemsInOpenChest(): List<ItemStack> { + val list = mutableListOf<ItemStack>() + val guiChest = Minecraft.getMinecraft().currentScreen as GuiChest + val inventorySlots = guiChest.inventorySlots.inventorySlots + val skipAt = inventorySlots.size - 9 * 4 + var i = 0 + for (slot in inventorySlots) { + val stack = slot.stack + if (stack != null) { + list.add(stack) + } + i++ + if (i == skipAt) break + } + return list + } + + fun isSack(name: String): Boolean = name.endsWith(" Sack") + + fun ItemStack.getLore() = ItemUtil.getItemLore(this) + + fun isCoOpSoulBound(stack: ItemStack): Boolean = stack.getLore().any { it.contains("Co-op Soulbound") } + + fun isRecombobulated(stack: ItemStack): Boolean = stack.getLore().any { it.contains("§k") } + + fun isPet(name: String): Boolean = name.matchRegex("\\[Lvl (.*)] (.*)") && !listOf( + "Archer", + "Berserk", + "Mage", + "Tank", + "Healer", + "➡", + ).any { name.contains(it) } + + fun maxPetLevel(name: String) = if (name.contains("Golden Dragon")) 200 else 100 + + fun getItemsInInventoryWithSlots(withCursorItem: Boolean = false): Map<ItemStack, Int> { + val map: LinkedHashMap<ItemStack, Int> = LinkedHashMap() + val player = Minecraft.getMinecraft().thePlayer + if (player == null) { + LorenzUtils.warning("getItemsInInventoryWithSlots: player is null!") + return map + } + for (slot in player.openContainer.inventorySlots) { + if (slot.hasStack) { + map[slot.stack] = slot.slotNumber + } + } + + if (withCursorItem) { + if (player.inventory != null) { + if (player.inventory.itemStack != null) { + map[player.inventory.itemStack] = -1 + } + } + } + + return map + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt new file mode 100644 index 000000000..961ed57ec --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt @@ -0,0 +1,27 @@ +package at.hannibal2.skyhanni.utils + +import java.awt.Color + +enum class LorenzColor(private var chatColorCode: Char, private val color: Color) { + BLACK('0', Color(0, 0, 0)), + DARK_BLUE('1', Color(0, 0, 170)), + DARK_GREEN('2', Color(0, 170, 0)), + DARK_AQUA('3', Color(0, 170, 170)), + DARK_RED('4', Color(170, 0, 0)), + DARK_PURPLE('5', Color(170, 0, 170)), + GOLD('6', Color(255, 170, 0)), + GRAY('7', Color(170, 170, 170)), + DARK_GRAY('8', Color(85, 85, 85)), + BLUE('9', Color(85, 85, 255)), + GREEN('a', Color(85, 255, 85)), + AQUA('b', Color(85, 255, 255)), + RED('c', Color(255, 85, 85)), + LIGHT_PURPLE('d', Color(255, 85, 255)), + YELLOW('e', Color(255, 255, 85)), + WHITE('f', Color(255, 255, 255)), + ; + + fun getChatColor(): String = "§$chatColorCode" + + fun toColor(): Color = color +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzDebug.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzDebug.kt new file mode 100644 index 000000000..5212dfc95 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzDebug.kt @@ -0,0 +1,16 @@ +package at.hannibal2.skyhanni.utils + +object LorenzDebug { + + private val logger = LorenzLogger("debug") + + fun log(text: String) { + logger.log(text) + println("debug logger: $text") + } + + fun writeAndLog(text: String) { + LorenzUtils.debug(text) + log(text) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzLogger.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzLogger.kt new file mode 100644 index 000000000..23ca8df25 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzLogger.kt @@ -0,0 +1,70 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.utils.LorenzUtils.formatCurrentTime +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.logging.FileHandler +import java.util.logging.Formatter +import java.util.logging.LogRecord +import java.util.logging.Logger + +class LorenzLogger(filePath: String) { + private val format = SimpleDateFormat("HH:mm:ss") + private val fileName = "$PREFIX_PATH$filePath.log" + + companion object { + private var PREFIX_PATH: String + + init { + val format = SimpleDateFormat("yyyy_MM_dd/HH_mm_ss").formatCurrentTime() + PREFIX_PATH = "mods/SkyHanni/logs/$format/" + } + } + + private lateinit var logger: Logger + + private fun getLogger(): Logger { + if (::logger.isInitialized) { + return logger + } + + val initLogger = initLogger() + this.logger = initLogger + return initLogger + } + + private fun initLogger(): Logger { + val logger = Logger.getLogger("" + System.nanoTime()) + try { + createParent(File(fileName)) + val handler = FileHandler(fileName) + handler.encoding ="utf-8" + logger.addHandler(handler) + handler.formatter = object : Formatter() { + override fun format(logRecord: LogRecord): String { + val message = logRecord.message + return format.formatCurrentTime() + " $message\n" + } + } + } catch (e: SecurityException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + return logger + } + + private fun createParent(file: File) { + val parent = file.parentFile + if (parent != null) { + if (!parent.isDirectory) { + parent.mkdirs() + } + } + } + + fun log(text: String?) { + getLogger().info(text) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt new file mode 100644 index 000000000..13afe8ac2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -0,0 +1,113 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.misc.HypixelData +import net.minecraft.client.Minecraft +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.SharedMonsterAttributes +import net.minecraft.util.ChatComponentText +import org.intellij.lang.annotations.Language +import java.text.DecimalFormat +import java.text.SimpleDateFormat + +object LorenzUtils { + + val isOnHypixel: Boolean + get() = HypixelData.hypixel + + val inSkyblock: Boolean + get() = HypixelData.hypixel && HypixelData.skyblock + + val inDungeons: Boolean + get() = HypixelData.hypixel && HypixelData.skyblock && HypixelData.dungeon + + const val DEBUG_PREFIX = "[Debug] §7" + + fun debug(message: String) { + internalChat(DEBUG_PREFIX + message) + } + + fun warning(message: String) { + internalChat("§cWarning! $message") + } + + fun error(message: String) { + internalChat("§4$message") + } + + fun chat(message: String) { + internalChat(message) + } + + private fun internalChat(message: String) { + val minecraft = Minecraft.getMinecraft() + if (minecraft == null) { + println(message) + return + } + + val thePlayer = minecraft.thePlayer + if (thePlayer == null) { + println(message) + return + } + + thePlayer.addChatMessage(ChatComponentText(message)) + } + + fun String.matchRegex(@Language("RegExp") regex: String): Boolean = regex.toRegex().matches(this) + + fun String.removeColorCodes(): String { + val builder = StringBuilder() + var skipNext = false + for (c in this.toCharArray()) { + if (c == '§') { + skipNext = true + continue + } + if (skipNext) { + skipNext = false + continue + } + builder.append(c) + } + + return builder.toString() + } + + fun SimpleDateFormat.formatCurrentTime(): String = this.format(System.currentTimeMillis()) + + fun stripVanillaMessage(originalMessage: String): String { + var message = originalMessage + + while (message.startsWith("§r")) { + message = message.substring(2) + } + while (message.endsWith("§r")) { + message = message.substring(0, message.length - 2) + } + return message + } + + fun Double.round(decimals: Int): Double { + var multiplier = 1.0 + repeat(decimals) { multiplier *= 10 } + return kotlin.math.round(this * multiplier) / multiplier + } + + fun String.between(start: String, end: String): String = this.split(start, end)[1] + + val EntityLivingBase.baseMaxHealth: Double + get() = this.getEntityAttribute(SharedMonsterAttributes.maxHealth).baseValue + + fun formatPercentage(percentage: Double): String = formatPercentage(percentage, "0.00") + + fun formatPercentage(percentage: Double, format: String?): String = + DecimalFormat(format).format(percentage * 100).replace(',', '.') + "%" + + fun formatInteger(i: Int): String = DecimalFormat("#,##0").format(i.toLong()).replace(',', '.') + + fun formatDouble(d: Double, format: String?): String = + DecimalFormat(format).format(d).replace(',', 'x').replace('.', ',').replace('x', '.') + + fun formatDouble(d: Double): String = formatDouble(d, "#,##0.0") +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt new file mode 100644 index 000000000..bfc5f7228 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt @@ -0,0 +1,61 @@ +package at.hannibal2.skyhanni.utils + +import net.minecraft.entity.Entity +import net.minecraft.util.BlockPos +import net.minecraft.util.Vec3 +import kotlin.math.pow + +data class LorenzVec( + val x: Double, + val y: Double, + val z: Double +) { + constructor(x: Int, y: Int, z: Int) : this(x.toDouble(), y.toDouble(), z.toDouble()) + + fun toBlocPos(): BlockPos = BlockPos(x, y, z) + + fun toVec3(): Vec3 = Vec3(x, y, z) + + fun distance(other: LorenzVec): Double = distanceSq(other).pow(0.5) + + fun distanceSq(x: Double, y: Double, z: Double): Double = distanceSq(LorenzVec(x, y, z)) + + fun distance(x: Double, y: Double, z: Double): Double = distance(LorenzVec(x, y, z)) + + fun distanceSq(other: LorenzVec): Double { + val dx = (other.x - x) + val dy = (other.y - y) + val dz = (other.z - z) + return (dx * dx + dy * dy + dz * dz) + } + + fun add(x: Double, y: Double, z: Double): LorenzVec = LorenzVec(this.x + x, this.y + y, this.z + z) + + fun add(x: Int, y: Int, z: Int): LorenzVec = LorenzVec(this.x + x, this.y + y, this.z + z) + + override fun toString(): String { + return "LorenzVec{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + '}' + } + + fun multiply(d: Double): LorenzVec = LorenzVec(x multiplyZeroSave d, y multiplyZeroSave d, z multiplyZeroSave d) + + fun multiply(d: Int): LorenzVec = + LorenzVec(x multiplyZeroSave d.toDouble(), y multiplyZeroSave d.toDouble(), z multiplyZeroSave d.toDouble()) + + fun add(other: LorenzVec) = LorenzVec(x + other.x, y + other.y, z + other.z) +} + +private infix fun Double.multiplyZeroSave(other: Double): Double { + val result = this * other + return if (result == -0.0) 0.0 else result +} + +fun BlockPos.toLorenzVec(): LorenzVec = LorenzVec(x, y, z) + +fun Entity.getLorenzVec(): LorenzVec = LorenzVec(posX, posY, posZ) + +fun Vec3.toLorenzVec(): LorenzVec = LorenzVec(xCoord, yCoord, zCoord)
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt new file mode 100644 index 000000000..891826a91 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -0,0 +1,152 @@ +package at.hannibal2.skyhanni.utils + +import java.text.NumberFormat +import java.util.* +import kotlin.math.pow +import kotlin.math.roundToInt + +object NumberUtil { + @JvmField + val nf: NumberFormat = NumberFormat.getInstance(Locale.US) + private val suffixes = TreeMap<Long, String>().apply { + this[1000L] = "k" + this[1000000L] = "M" + this[1000000000L] = "B" + this[1000000000000L] = "T" + this[1000000000000000L] = "P" + this[1000000000000000000L] = "E" + } + private val romanSymbols = TreeMap( + mapOf( + 1000 to "M", + 900 to "CM", + 500 to "D", + 400 to "CD", + 100 to "C", + 90 to "XC", + 50 to "L", + 40 to "XL", + 10 to "X", + 9 to "IX", + 5 to "V", + 4 to "IV", + 1 to "I", + ) + ) + + /** + * This code was unmodified and taken under CC BY-SA 3.0 license + * @link https://stackoverflow.com/a/30661479 + * @author assylias + */ + @JvmStatic + fun format(value: Number): String { + @Suppress("NAME_SHADOWING") + val value = value.toLong() + //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here + if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1) + if (value < 0) return "-" + format(-value) + if (value < 1000) return value.toString() //deal with easy case + val (divideBy, suffix) = suffixes.floorEntry(value) + val truncated = value / (divideBy / 10) //the number part of the output times 10 + val hasDecimal = truncated < 100 && truncated / 10.0 != (truncated / 10).toDouble() + return if (hasDecimal) (truncated / 10.0).toString() + suffix else (truncated / 10).toString() + suffix + } + + @JvmStatic + fun unformat(value: String): Long { + val suffix = value.filter { !it.isDigit() }.lowercase() + val num = value.filter { it.isDigit() }.toLong() + return num * (suffixes.entries.find { it.value.lowercase() == suffix }?.key ?: 1) + } + + /** + * This code was unmodified and taken under CC BY-SA 3.0 license + * @link https://stackoverflow.com/a/22186845 + * @author jpdymond + */ + fun Double.roundToPrecision(precision: Int): Double { + val scale = 10.0.pow(precision).toInt() + return (this * scale).roundToInt().toDouble() / scale + } + + /** + * This code was unmodified and taken under CC BY-SA 3.0 license + * @link https://stackoverflow.com/a/22186845 + * @author jpdymond + */ + fun Float.roundToPrecision(precision: Int): Float { + val scale = 10.0.pow(precision).toInt() + return (this * scale).roundToInt().toFloat() / scale + } + + fun Number.addSuffix(): String { + val long = this.toLong() + if (long in 11..13) return "${this}th" + return when (long % 10) { + 1L -> "${this}st" + 2L -> "${this}nd" + 3L -> "${this}rd" + else -> "${this}th" + } + } + + /** + * This code was converted to Kotlin and taken under CC BY-SA 3.0 license + * @link https://stackoverflow.com/a/9073310 + */ + fun String.romanToDecimal(): Int { + var decimal = 0 + var lastNumber = 0 + val romanNumeral = this.uppercase() + for (x in romanNumeral.length - 1 downTo 0) { + when (romanNumeral[x]) { + 'M' -> { + decimal = processDecimal(1000, lastNumber, decimal) + lastNumber = 1000 + } + 'D' -> { + decimal = processDecimal(500, lastNumber, decimal) + lastNumber = 500 + } + 'C' -> { + decimal = processDecimal(100, lastNumber, decimal) + lastNumber = 100 + } + 'L' -> { + decimal = processDecimal(50, lastNumber, decimal) + lastNumber = 50 + } + 'X' -> { + decimal = processDecimal(10, lastNumber, decimal) + lastNumber = 10 + } + 'V' -> { + decimal = processDecimal(5, lastNumber, decimal) + lastNumber = 5 + } + 'I' -> { + decimal = processDecimal(1, lastNumber, decimal) + lastNumber = 1 + } + } + } + return decimal + } + + fun Int.toRoman(): String { + if (this <= 0) error("$this must be positive!") + val l = romanSymbols.floorKey(this) + return if (this == l) { + romanSymbols[this]!! + } else romanSymbols[l] + (this - l).toRoman() + } + + private fun processDecimal(decimal: Int, lastNumber: Int, lastDecimal: Int): Int { + return if (lastNumber > decimal) { + lastDecimal - decimal + } else { + lastDecimal + decimal + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt new file mode 100644 index 000000000..763e4288e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -0,0 +1,355 @@ +package at.hannibal2.skyhanni.utils + +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Gui +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.vertex.DefaultVertexFormats +import net.minecraft.inventory.Slot +import net.minecraft.util.AxisAlignedBB +import net.minecraft.util.MathHelper +import net.minecraft.util.ResourceLocation +import net.minecraft.util.Vec3 +import net.minecraftforge.client.event.RenderWorldLastEvent +import org.lwjgl.opengl.GL11 +import java.awt.Color +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +object RenderUtils { + + val beaconBeam = ResourceLocation("textures/entity/beacon_beam.png") + + infix fun Slot.highlight(color: LorenzColor) { + Gui.drawRect( + this.xDisplayPosition, + this.yDisplayPosition, + this.xDisplayPosition + 16, + this.yDisplayPosition + 16, + color.toColor().rgb + ) + } + + fun RenderWorldLastEvent.drawColor(location: LorenzVec, color: LorenzColor, beacon: Boolean = false) { + val (viewerX, viewerY, viewerZ) = getViewerPos(partialTicks) + val x = location.x - viewerX + val y = location.y - viewerY + val z = location.z - viewerZ + val distSq = x * x + y * y + z * z + GlStateManager.disableDepth() + GlStateManager.disableCull() + drawFilledBoundingBox( + AxisAlignedBB(x, y, z, x + 1, y + 1, z + 1).expandBlock(), + color.toColor(), + (0.1f + 0.005f * distSq.toFloat()).coerceAtLeast(0.2f) + ) + GlStateManager.disableTexture2D() + if (distSq > 5 * 5 && beacon) renderBeaconBeam(x, y + 1, z, color.toColor().rgb, 1.0f, partialTicks) + GlStateManager.disableLighting() + GlStateManager.enableTexture2D() + GlStateManager.enableDepth() + GlStateManager.enableCull() + } + + + fun getViewerPos(partialTicks: Float): LorenzVec { + val viewer = Minecraft.getMinecraft().renderViewEntity + val viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * partialTicks + val viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * partialTicks + val viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * partialTicks + return LorenzVec(viewerX, viewerY, viewerZ) + } + + /** + * Taken from NotEnoughUpdates under Creative Commons Attribution-NonCommercial 3.0 + * https://github.com/Moulberry/NotEnoughUpdates/blob/master/LICENSE + * @author Moulberry + * @author Mojang + */ + private fun drawFilledBoundingBox(aabb: AxisAlignedBB, c: Color, alphaMultiplier: Float = 1f) { + GlStateManager.enableBlend() + GlStateManager.disableLighting() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + GlStateManager.disableTexture2D() + val tessellator = Tessellator.getInstance() + val worldRenderer = tessellator.worldRenderer + GlStateManager.color(c.red / 255f, c.green / 255f, c.blue / 255f, c.alpha / 255f * alphaMultiplier) + + //vertical + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + tessellator.draw() + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + tessellator.draw() + GlStateManager.color( + c.red / 255f * 0.8f, + c.green / 255f * 0.8f, + c.blue / 255f * 0.8f, + c.alpha / 255f * alphaMultiplier + ) + + //x + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + tessellator.draw() + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + tessellator.draw() + GlStateManager.color( + c.red / 255f * 0.9f, + c.green / 255f * 0.9f, + c.blue / 255f * 0.9f, + c.alpha / 255f * alphaMultiplier + ) + //z + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + tessellator.draw() + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + tessellator.draw() + GlStateManager.enableTexture2D() + GlStateManager.disableBlend() + } + + fun AxisAlignedBB.expandBlock() = expand(0.0020000000949949026, 0.0020000000949949026, 0.0020000000949949026) + + /** + * Taken from NotEnoughUpdates under Creative Commons Attribution-NonCommercial 3.0 + * https://github.com/Moulberry/NotEnoughUpdates/blob/master/LICENSE + * @author Moulberry + * @author Mojang + */ + fun renderBeaconBeam(x: Double, y: Double, z: Double, rgb: Int, alphaMultiplier: Float, partialTicks: Float) { + val height = 300 + val bottomOffset = 0 + val topOffset = bottomOffset + height + val tessellator = Tessellator.getInstance() + val worldrenderer = tessellator.worldRenderer + Minecraft.getMinecraft().textureManager.bindTexture(beaconBeam) + GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, 10497.0f) + GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, 10497.0f) + GlStateManager.disableLighting() + GlStateManager.enableCull() + GlStateManager.enableTexture2D() + GlStateManager.tryBlendFuncSeparate(770, 1, 1, 0) + GlStateManager.enableBlend() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + val time = Minecraft.getMinecraft().theWorld.totalWorldTime + partialTicks.toDouble() + val d1 = MathHelper.func_181162_h( + -time * 0.2 - MathHelper.floor_double(-time * 0.1) + .toDouble() + ) + val r = (rgb shr 16 and 0xFF) / 255f + val g = (rgb shr 8 and 0xFF) / 255f + val b = (rgb and 0xFF) / 255f + val d2 = time * 0.025 * -1.5 + val d4 = 0.5 + cos(d2 + 2.356194490192345) * 0.2 + val d5 = 0.5 + sin(d2 + 2.356194490192345) * 0.2 + val d6 = 0.5 + cos(d2 + Math.PI / 4.0) * 0.2 + val d7 = 0.5 + sin(d2 + Math.PI / 4.0) * 0.2 + val d8 = 0.5 + cos(d2 + 3.9269908169872414) * 0.2 + val d9 = 0.5 + sin(d2 + 3.9269908169872414) * 0.2 + val d10 = 0.5 + cos(d2 + 5.497787143782138) * 0.2 + val d11 = 0.5 + sin(d2 + 5.497787143782138) * 0.2 + val d14 = -1.0 + d1 + val d15 = height.toDouble() * 2.5 + d14 + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR) + worldrenderer.pos(x + d4, y + topOffset, z + d5).tex(1.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d4, y + bottomOffset, z + d5).tex(1.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d6, y + bottomOffset, z + d7).tex(0.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d6, y + topOffset, z + d7).tex(0.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d10, y + topOffset, z + d11).tex(1.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d10, y + bottomOffset, z + d11).tex(1.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d8, y + bottomOffset, z + d9).tex(0.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d8, y + topOffset, z + d9).tex(0.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d6, y + topOffset, z + d7).tex(1.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d6, y + bottomOffset, z + d7).tex(1.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d10, y + bottomOffset, z + d11).tex(0.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d10, y + topOffset, z + d11).tex(0.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d8, y + topOffset, z + d9).tex(1.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + d8, y + bottomOffset, z + d9).tex(1.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d4, y + bottomOffset, z + d5).tex(0.0, d14).color(r, g, b, 1.0f).endVertex() + worldrenderer.pos(x + d4, y + topOffset, z + d5).tex(0.0, d15).color(r, g, b, 1.0f * alphaMultiplier) + .endVertex() + tessellator.draw() + GlStateManager.disableCull() + val d12 = -1.0 + d1 + val d13 = height + d12 + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR) + worldrenderer.pos(x + 0.2, y + topOffset, z + 0.2).tex(1.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.2, y + bottomOffset, z + 0.2).tex(1.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.8, y + bottomOffset, z + 0.2).tex(0.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.8, y + topOffset, z + 0.2).tex(0.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.8, y + topOffset, z + 0.8).tex(1.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.8, y + bottomOffset, z + 0.8).tex(1.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.2, y + bottomOffset, z + 0.8).tex(0.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.2, y + topOffset, z + 0.8).tex(0.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.8, y + topOffset, z + 0.2).tex(1.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.8, y + bottomOffset, z + 0.2).tex(1.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.8, y + bottomOffset, z + 0.8).tex(0.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.8, y + topOffset, z + 0.8).tex(0.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.2, y + topOffset, z + 0.8).tex(1.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + worldrenderer.pos(x + 0.2, y + bottomOffset, z + 0.8).tex(1.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.2, y + bottomOffset, z + 0.2).tex(0.0, d12).color(r, g, b, 0.25f).endVertex() + worldrenderer.pos(x + 0.2, y + topOffset, z + 0.2).tex(0.0, d13).color(r, g, b, 0.25f * alphaMultiplier) + .endVertex() + tessellator.draw() + } + + fun RenderWorldLastEvent.drawString(location: LorenzVec, text: String, seeThroughBlocks: Boolean = false) { + GlStateManager.alphaFunc(516, 0.1f) + GlStateManager.pushMatrix() + val viewer = Minecraft.getMinecraft().renderViewEntity + val renderManager = Minecraft.getMinecraft().renderManager + var x = location.x - renderManager.viewerPosX + var y = location.y - renderManager.viewerPosY - viewer.eyeHeight + var z = location.z - renderManager.viewerPosZ + val distSq = x * x + y * y + z * z + val dist = sqrt(distSq) + if (distSq > 144) { + x *= 12 / dist + y *= 12 / dist + z *= 12 / dist + } + + if (seeThroughBlocks) { + GlStateManager.disableDepth() + GlStateManager.disableCull() + } + + GlStateManager.translate(x, y, z) + GlStateManager.translate(0f, viewer.eyeHeight, 0f) + drawNametag(text) + GlStateManager.rotate(-renderManager.playerViewY, 0.0f, 1.0f, 0.0f) + GlStateManager.rotate(renderManager.playerViewX, 1.0f, 0.0f, 0.0f) + GlStateManager.translate(0f, -0.25f, 0f) + GlStateManager.rotate(-renderManager.playerViewX, 1.0f, 0.0f, 0.0f) + GlStateManager.rotate(renderManager.playerViewY, 0.0f, 1.0f, 0.0f) +// RenderUtil.drawNametag(EnumChatFormatting.YELLOW.toString() + dist.roundToInt() + "m") + GlStateManager.popMatrix() + GlStateManager.disableLighting() + + + if (seeThroughBlocks) { + GlStateManager.enableDepth() + GlStateManager.enableCull() + } + } + + /** + * @author Mojang + */ + fun drawNametag(str: String?) { + val fontRenderer = Minecraft.getMinecraft().fontRendererObj + val f1 = 0.02666667f + GlStateManager.pushMatrix() + GL11.glNormal3f(0.0f, 1.0f, 0.0f) + GlStateManager.rotate(-Minecraft.getMinecraft().renderManager.playerViewY, 0.0f, 1.0f, 0.0f) + GlStateManager.rotate( + Minecraft.getMinecraft().renderManager.playerViewX, + 1.0f, + 0.0f, + 0.0f + ) + GlStateManager.scale(-f1, -f1, f1) + GlStateManager.disableLighting() + GlStateManager.depthMask(false) + GlStateManager.enableBlend() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + val tessellator = Tessellator.getInstance() + val worldrenderer = tessellator.worldRenderer + val i = 0 + val j = fontRenderer.getStringWidth(str) / 2 + GlStateManager.disableTexture2D() + worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR) + worldrenderer.pos((-j - 1).toDouble(), (-1 + i).toDouble(), 0.0).color(0.0f, 0.0f, 0.0f, 0.25f).endVertex() + worldrenderer.pos((-j - 1).toDouble(), (8 + i).toDouble(), 0.0).color(0.0f, 0.0f, 0.0f, 0.25f).endVertex() + worldrenderer.pos((j + 1).toDouble(), (8 + i).toDouble(), 0.0).color(0.0f, 0.0f, 0.0f, 0.25f).endVertex() + worldrenderer.pos((j + 1).toDouble(), (-1 + i).toDouble(), 0.0).color(0.0f, 0.0f, 0.0f, 0.25f).endVertex() + tessellator.draw() + GlStateManager.enableTexture2D() + fontRenderer.drawString(str, -j, i, 553648127) + GlStateManager.depthMask(true) + fontRenderer.drawString(str, -j, i, -1) + GlStateManager.enableBlend() + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) + GlStateManager.popMatrix() + } + + /** + * @author Mojang + */ + fun drawLabel( + pos: Vec3, + text: String, + color: Color, + partialTicks: Float, + shadow: Boolean = false, + scale: Float = 1f + ) { + var mc = Minecraft.getMinecraft() + val player = mc.thePlayer + val x = + pos.xCoord - player.lastTickPosX + (pos.xCoord - player.posX - (pos.xCoord - player.lastTickPosX)) * partialTicks + val y = + pos.yCoord - player.lastTickPosY + (pos.yCoord - player.posY - (pos.yCoord - player.lastTickPosY)) * partialTicks + val z = + pos.zCoord - player.lastTickPosZ + (pos.zCoord - player.posZ - (pos.zCoord - player.lastTickPosZ)) * partialTicks + val renderManager = mc.renderManager + val f1 = 0.0266666688 + val width = mc.fontRendererObj.getStringWidth(text) / 2 + GlStateManager.pushMatrix() + GlStateManager.translate(x, y, z) + GL11.glNormal3f(0f, 1f, 0f) + GlStateManager.rotate(-renderManager.playerViewY, 0f, 1f, 0f) + GlStateManager.rotate(renderManager.playerViewX, 1f, 0f, 0f) + GlStateManager.scale(-f1, -f1, -f1) + GlStateManager.scale(scale, scale, scale) + GlStateManager.enableBlend() + GlStateManager.disableLighting() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + GlStateManager.enableTexture2D() + mc.fontRendererObj.drawString(text, (-width).toFloat(), 0f, color.rgb, shadow) + GlStateManager.disableBlend() + GlStateManager.popMatrix() + } + + fun interpolate(currentValue: Double, lastValue: Double, multiplier: Float): Double { + return lastValue + (currentValue - lastValue) * multiplier + } +}
\ No newline at end of file |