aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/at/hannibal2')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java119
-rw-r--r--src/main/java/at/hannibal2/skyhanni/bazaar/BazaarApi.kt59
-rw-r--r--src/main/java/at/hannibal2/skyhanni/bazaar/BazaarData.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/bazaar/BazaarDataGrabber.kt113
-rw-r--r--src/main/java/at/hannibal2/skyhanni/bazaar/BazaarOrderHelper.kt87
-rw-r--r--src/main/java/at/hannibal2/skyhanni/chat/ChatFilter.kt284
-rw-r--r--src/main/java/at/hannibal2/skyhanni/chat/ChatManager.kt51
-rw-r--r--src/main/java/at/hannibal2/skyhanni/chat/PlayerChatFilter.kt90
-rw-r--r--src/main/java/at/hannibal2/skyhanni/chat/PlayerMessageChannel.kt10
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/Features.java274
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/GuiTextures.java32
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/Commands.java26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/SimpleCommand.java60
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/config/ConfigEditor.java602
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/BackgroundBlur.java249
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/ChromaColour.java93
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/GlScissorStack.java86
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/GuiElement.java12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/GuiElementBoolean.java118
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/GuiElementColour.java370
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/GuiElementTextField.java549
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/GuiScreenElementWrapper.java34
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/Config.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/KeybindHelper.java49
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/Position.java197
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/Category.java14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigAccordionId.java12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorAccordion.java12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorBoolean.java11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorButton.java14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorColour.java11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDraggableList.java12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorDropdown.java14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorKeybind.java12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorSlider.java16
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorStyle.java11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigEditorText.java11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/annotations/ConfigOption.java16
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditor.java62
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorAccordion.java80
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorBoolean.java37
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorButton.java60
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorColour.java74
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDraggableList.java269
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorDropdown.java145
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorKeybind.java88
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorSlider.java136
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorStyle.java42
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiOptionEditorText.java78
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.java172
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/struct/ConfigProcessor.java167
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/GuiElementSlider.java121
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/StringUtils.java8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpUtils.java25
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingFloat.java68
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/lerp/LerpingInteger.java76
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/render/RenderUtils.java155
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/util/render/TextRenderUtils.java155
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/textures/TextureObject.java37
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/textures/Textures.java54
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/utils/Utils.java367
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonBossMessages.kt51
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonChatFilter.kt224
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonCleanEnd.kt130
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonData.kt46
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonDeathCounter.kt97
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonHighlightClickedBlocks.kt99
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/DungeonMilestoneDisplay.kt96
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossDamageIndicator.kt181
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/DungeonBossFinder.kt381
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityData.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/dungeon/damageindicator/EntityResult.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/CheckRenderEntityEvent.kt14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorFinalBossEvent.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/DungeonEnterEvent.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/GuiContainerEvent.kt54
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/GuiRenderItemEvent.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/LorenzActionBarEvent.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/LorenzChatEvent.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt20
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/PacketEvent.kt25
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/PlayerSendChatEvent.kt11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/items/HideNotClickableItems.kt440
-rw-r--r--src/main/java/at/hannibal2/skyhanni/items/ItemDisplayOverlayFeatures.kt107
-rw-r--r--src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/ItemAbilityCooldown.kt197
-rw-r--r--src/main/java/at/hannibal2/skyhanni/items/abilitycooldown/WitherImpactDetection.kt65
-rw-r--r--src/main/java/at/hannibal2/skyhanni/misc/ButtonOnPause.kt51
-rw-r--r--src/main/java/at/hannibal2/skyhanni/misc/CurrentPetDisplay.kt53
-rw-r--r--src/main/java/at/hannibal2/skyhanni/misc/ExpBottleOnGroundHider.kt19
-rw-r--r--src/main/java/at/hannibal2/skyhanni/misc/HypixelData.kt63
-rw-r--r--src/main/java/at/hannibal2/skyhanni/misc/ScoreboardData.kt45
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixinhooks/GuiContainerHook.kt61
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixinhooks/NetHandlerPlayClientHook.kt9
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixinhooks/NetworkManagerHook.kt10
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderItemHook.kt28
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixinhooks/RenderManagerHook.kt25
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixins/MixinGuiContainer.java48
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixins/MixinNetHandlerPlayClient.java24
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixins/MixinNetworkManager.java20
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderItem.java41
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixins/MixinRenderManager.java19
-rw-r--r--src/main/java/at/hannibal2/skyhanni/test/LorenzTest.kt28
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt48
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/BlockUtils.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/GuiRender.kt32
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ItemUtil.kt213
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt72
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt27
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzDebug.kt16
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzLogger.kt70
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt113
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt61
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt152
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt355
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