aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/at/lorenz/mod/GuiContainerHook.kt61
-rw-r--r--src/main/java/at/lorenz/mod/HideNotClickableItems.kt444
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt59
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarData.kt3
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt116
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt87
-rw-r--r--src/main/java/at/lorenz/mod/chat/ChatFilter.kt279
-rw-r--r--src/main/java/at/lorenz/mod/chat/ChatManager.kt47
-rw-r--r--src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt78
-rw-r--r--src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt10
-rw-r--r--src/main/java/at/lorenz/mod/config/LorenzConfig.kt12
-rw-r--r--src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt259
-rw-r--r--src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt54
-rw-r--r--src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt5
-rw-r--r--src/main/java/at/lorenz/mod/events/LorenzEvent.kt20
-rw-r--r--src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt11
-rw-r--r--src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java50
-rw-r--r--src/main/java/at/lorenz/mod/utils/APIUtil.kt116
-rw-r--r--src/main/java/at/lorenz/mod/utils/ItemUtil.kt211
-rw-r--r--src/main/java/at/lorenz/mod/utils/ItemUtils.kt39
-rw-r--r--src/main/java/at/lorenz/mod/utils/LorenzColor.kt27
-rw-r--r--src/main/java/at/lorenz/mod/utils/LorenzLogger.kt70
-rw-r--r--src/main/java/at/lorenz/mod/utils/LorenzUtils.kt91
-rw-r--r--src/main/java/at/lorenz/mod/utils/RenderUtil.kt20
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/ComponentHandler.java123
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/DevModeConstants.java6
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/GuiTextures.java33
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/SkyblockHud.java195
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/KillTracking.java61
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/LeaderboardGetter.java65
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/LocationChangeEvent.java15
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileJoinedEvent.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileSwitchedEvent.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarLineUpdateEvent.java22
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPostEvent.java21
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPreGetEvent.java18
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/events/SkyBlockEntityKilled.java18
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/item/IAbility.java7
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeHelper.java36
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeRegistry.java27
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/SkyBlockEntity.java27
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/commands/Commands.java105
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleCommand.java60
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleSubCommand.java61
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/config/KeyBindings.java8
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfig.java436
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfigEditor.java602
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/BackgroundBlur.java249
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/ChromaColour.java93
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/GlScissorStack.java86
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/GuiElement.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementBoolean.java118
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementColour.java370
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementTextField.java549
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/GuiScreenElementWrapper.java34
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/Config.java6
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/KeybindHelper.java49
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/Position.java197
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/Category.java14
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigAccordionId.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorAccordion.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorBoolean.java11
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorButton.java14
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorColour.java11
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDraggableList.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDropdown.java14
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorKeybind.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorSlider.java16
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorStyle.java11
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorText.java11
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigOption.java16
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditor.java62
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorAccordion.java80
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorBoolean.java37
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorButton.java60
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorColour.java81
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDraggableList.java268
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDropdown.java145
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorKeybind.java88
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorSlider.java136
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorStyle.java42
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorText.java78
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiPositionEditor.java171
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/config/struct/ConfigProcessor.java166
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/GuiElementSlider.java120
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/StringUtils.java8
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpUtils.java25
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingFloat.java68
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingInteger.java76
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/render/RenderUtils.java155
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/core/util/render/TextRenderUtils.java155
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/dungeons/Classes.java48
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonHandler.java195
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonPlayer.java32
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/BossbarHandler.java36
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/CooldownHandler.java121
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/CrystalWaypoints.java196
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/CurrencyHandler.java86
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/HeldItemHandler.java31
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/MapHandler.java206
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/NpcDialogue.java131
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/SlayerHandler.java130
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/TimeHandler.java28
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/WarpHandler.java179
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/DwarvenIcons.java39
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/HubIcons.java55
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/EndIslandHandler.java53
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/FarmHouseHandler.java41
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/FarmingIslandHandler.java28
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/IslandHandler.java67
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/LocationCategory.java54
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/LocationHandler.java44
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/Locations.java161
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/MinesHandler.java192
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/location/ParkIslandHandler.java29
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/mixins/GuiChestAccessor.java12
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinEntityArrow.java26
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinGuiIngameForge.java113
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinItemStack.java42
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinNetHandlerPlayClient.java67
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinRenderItem.java63
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/overlay/DungeonOverlay.java149
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/overlay/GenericOverlays.java42
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/overlay/MiningHud.java76
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/overlay/OverlayHud.java331
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/overlay/RPGHud.java112
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/playerstats/ActionBarParsing.java152
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/seasons/Season.java53
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/seasons/SeasonDateHandler.java73
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/textures/TextureObject.java37
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/textures/Textures.java58
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerFileLoader.java136
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerHandler.java111
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerObject.java97
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/utils/ComponentBuilder.java54
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/utils/SpecialColour.java93
-rw-r--r--src/main/java/com/thatgravyboat/skyblockhud/utils/Utils.java380
137 files changed, 12548 insertions, 0 deletions
diff --git a/src/main/java/at/lorenz/mod/GuiContainerHook.kt b/src/main/java/at/lorenz/mod/GuiContainerHook.kt
new file mode 100644
index 000000000..55b30e964
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/GuiContainerHook.kt
@@ -0,0 +1,61 @@
+package at.lorenz.mod
+
+import at.lorenz.mod.events.GuiContainerEvent
+import at.lorenz.mod.events.GuiContainerEvent.CloseWindowEvent
+import at.lorenz.mod.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/lorenz/mod/HideNotClickableItems.kt b/src/main/java/at/lorenz/mod/HideNotClickableItems.kt
new file mode 100644
index 000000000..3f94a1479
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/HideNotClickableItems.kt
@@ -0,0 +1,444 @@
+package at.lorenz.mod
+
+import at.lorenz.mod.bazaar.BazaarApi
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.utils.LorenzUtils.Companion.removeColorCodes
+import at.lorenz.mod.events.GuiContainerEvent
+import at.lorenz.mod.utils.ItemUtils
+import at.lorenz.mod.utils.ItemUtils.Companion.cleanName
+import at.lorenz.mod.utils.ItemUtils.Companion.getLore
+import at.lorenz.mod.utils.LorenzColor
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.RenderUtil.Companion.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.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 (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
+ 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
+// SoundQueue.addToQueue("note.bass", 0.5f, 1f)
+
+ if (System.currentTimeMillis() > lastClickTime + 5_000) {
+ lastClickTime = System.currentTimeMillis()
+// EssentialAPI.getNotifications()
+// .push(
+// "§cThis item cannot be moved!",
+// "§eClick here §fto disable this feature for 10 seconds!\n" +
+// "§fOr disable it forever: §6/st §f+ '§6Hide Not Clickable Items§f'.",
+// 5f,
+// action = {
+// bypassUntil = System.currentTimeMillis() + 10_000
+// })
+ }
+ return
+ }
+ }
+
+ private fun isDisabled(): Boolean {
+ if (bypassUntil > System.currentTimeMillis()) return true
+
+ return !LorenzConfig.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
+
+ "Fairy's Galoshes" -> 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
+
+ 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")
+
+ for (item in items) {
+ if (name.endsWith(" $item")) {
+
+ if (ItemUtils.isRecombobulated(stack)) {
+ hideReason = "This item should not be salvaged! (Recombobulated)"
+ return true
+ }
+// val rarity = stack.getSkyBlockRarity()
+// if (rarity == ItemRarity.LEGENDARY || rarity == ItemRarity.MYTHIC) {
+// hideReason = "This item should not be salvaged! (Rarity)"
+// return true
+// }
+
+ 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/lorenz/mod/bazaar/BazaarApi.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt
new file mode 100644
index 000000000..28ce228e2
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt
@@ -0,0 +1,59 @@
+package at.lorenz.mod.bazaar
+
+import at.lorenz.mod.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/lorenz/mod/bazaar/BazaarData.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarData.kt
new file mode 100644
index 000000000..a9f75370c
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarData.kt
@@ -0,0 +1,3 @@
+package at.lorenz.mod.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/lorenz/mod/bazaar/BazaarDataGrabber.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt
new file mode 100644
index 000000000..78341e05c
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt
@@ -0,0 +1,116 @@
+package at.lorenz.mod.bazaar
+
+import at.lorenz.mod.utils.APIUtil
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.LorenzUtils.Companion.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 = "lorenz-bazaar-update", period = 1000L) {
+ //TODO add
+// 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) {
+// LorenzUtils.warning("Bazaar buy order for $itemName not found!")
+ 0.0
+ }
+ val buyPrice: Double = try {
+ itemData["buy_summary"].asJsonArray[0].asJsonObject["pricePerUnit"].asDouble.round(1)
+ } catch (e: Exception) {
+// LorenzUtils.warning("Bazaar sell offers for $itemName not found!")
+ 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/lorenz/mod/bazaar/BazaarOrderHelper.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt
new file mode 100644
index 000000000..0daa12b7d
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt
@@ -0,0 +1,87 @@
+package at.lorenz.mod.bazaar
+
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.events.GuiContainerEvent
+import at.lorenz.mod.utils.ItemUtils.Companion.getLore
+import at.lorenz.mod.utils.LorenzColor
+import at.lorenz.mod.utils.RenderUtil.Companion.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 (!LorenzConfig.lorenzBazaarOrderHelper) 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/lorenz/mod/chat/ChatFilter.kt b/src/main/java/at/lorenz/mod/chat/ChatFilter.kt
new file mode 100644
index 000000000..8e2160928
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/ChatFilter.kt
@@ -0,0 +1,279 @@
+package at.lorenz.mod.chat
+
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.LorenzUtils.Companion.matchRegex
+import at.lorenz.mod.events.LorenzChatEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class ChatFilter {
+
+ @SubscribeEvent
+ fun onChatMessage(event: LorenzChatEvent) {
+ if (!LorenzConfig.chatFilter) 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) -> "lobby"
+ empty(message) -> "empty"
+ warping(message) -> "warping"
+ welcome(message) -> "welcome"
+ guild(message) -> "guild"
+ PlayerChatFilter.shouldBlock(message) -> "player_chat"
+ killCombo(message) -> "kill_combo"
+ bazaarAndAHMiniMessages(message) -> "bz_ah_minis"
+ watchdogAnnouncement(message) -> "watchdog"
+ slayer(message) -> "slayer"
+ slayerDrop(message) -> "slayer_drop"
+ uselessDrop(message) -> "useless_drop"
+ uselessNotification(message) -> "useless_notification"
+ party(message) -> "party"
+ money(message) -> "money"
+ winterIsland(message) -> "winter_island"
+ uselessWarning(message) -> "useless_warning"
+ friendJoin(message) -> "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
+
+ 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/lorenz/mod/chat/ChatManager.kt b/src/main/java/at/lorenz/mod/chat/ChatManager.kt
new file mode 100644
index 000000000..a85643e1b
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/ChatManager.kt
@@ -0,0 +1,47 @@
+package at.lorenz.mod.chat
+
+import at.lorenz.mod.utils.LorenzLogger
+import at.lorenz.mod.events.LorenzChatEvent
+import at.lorenz.mod.utils.LorenzUtils
+import net.minecraftforge.client.event.ClientChatReceivedEvent
+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: ClientChatReceivedEvent) {
+ val messageComponent = event.message
+
+ val message = LorenzUtils.stripVanillaMessage(messageComponent.formattedText)
+ if (event.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/lorenz/mod/chat/PlayerChatFilter.kt b/src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt
new file mode 100644
index 000000000..427b25e37
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt
@@ -0,0 +1,78 @@
+package at.lorenz.mod.chat
+
+import at.lorenz.mod.utils.LorenzLogger
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.LorenzUtils.Companion.removeColorCodes
+import at.lorenz.mod.events.PlayerSendChatEvent
+
+class PlayerChatFilter {
+
+ companion object {
+ 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/lorenz/mod/chat/PlayerMessageChannel.kt b/src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt
new file mode 100644
index 000000000..0e44b88f0
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt
@@ -0,0 +1,10 @@
+package at.lorenz.mod.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/lorenz/mod/config/LorenzConfig.kt b/src/main/java/at/lorenz/mod/config/LorenzConfig.kt
new file mode 100644
index 000000000..67268b6ca
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/config/LorenzConfig.kt
@@ -0,0 +1,12 @@
+package at.lorenz.mod.config
+
+class LorenzConfig {
+
+ companion object {
+ val chatFilter = true
+ val dungeonHideAnnoyingMessages = true
+ val hideNotClickableItems = true
+
+ val lorenzBazaarOrderHelper = true
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt b/src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt
new file mode 100644
index 000000000..86fc67cfd
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt
@@ -0,0 +1,259 @@
+package at.lorenz.mod.dungeon
+
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.events.LorenzChatEvent
+import at.lorenz.mod.utils.LorenzUtils.Companion.matchRegex
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class DungeonChatFilter {
+
+ @SubscribeEvent
+ fun onChatMessage(event: LorenzChatEvent) {
+ if (!LorenzConfig.dungeonHideAnnoyingMessages) 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"
+ }
+
+ //TODO add
+// if (!LorenzUtils.inDungeon) 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"
+ isBoss(message) -> "boss"
+ isEnd(message) -> "end"
+ //TODO add
+// DungeonMilestoneDisplay.isMilestoneMessage(message) -> "milestone"
+
+ else -> ""
+ }
+ }
+
+ private fun isDoor(message: String): Boolean = message == "§cThe §r§c§lBLOOD DOOR§r§c has been opened!"
+
+ 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
+ }
+
+ 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 == "§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
+ 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
+
+ //TODO abstract if more "burnt" messages are found
+ 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/lorenz/mod/events/GuiContainerEvent.kt b/src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt
new file mode 100644
index 000000000..4f43ab505
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt
@@ -0,0 +1,54 @@
+package at.lorenz.mod.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/lorenz/mod/events/LorenzChatEvent.kt b/src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt
new file mode 100644
index 000000000..3b0350918
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt
@@ -0,0 +1,5 @@
+package at.lorenz.mod.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/lorenz/mod/events/LorenzEvent.kt b/src/main/java/at/lorenz/mod/events/LorenzEvent.kt
new file mode 100644
index 000000000..facb18e2a
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/LorenzEvent.kt
@@ -0,0 +1,20 @@
+package at.lorenz.mod.events
+
+import at.lorenz.mod.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("§cLorenz Mod 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/lorenz/mod/events/PlayerSendChatEvent.kt b/src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt
new file mode 100644
index 000000000..dd015e1cb
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt
@@ -0,0 +1,11 @@
+package at.lorenz.mod.events
+
+import at.lorenz.mod.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/lorenz/mod/mixins/MixinGuiContainer.java b/src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java
new file mode 100644
index 000000000..c9dc12fc5
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java
@@ -0,0 +1,50 @@
+package at.lorenz.mod.mixins;
+
+import at.lorenz.mod.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/lorenz/mod/utils/APIUtil.kt b/src/main/java/at/lorenz/mod/utils/APIUtil.kt
new file mode 100644
index 000000000..88d459ada
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/APIUtil.kt
@@ -0,0 +1,116 @@
+package at.lorenz.mod.utils
+
+import com.google.gson.JsonArray
+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
+import scala.util.parsing.json.JSONArray
+import scala.util.parsing.json.JSONObject
+import java.awt.image.BufferedImage
+import java.net.HttpURLConnection
+import java.net.URL
+import java.security.cert.X509Certificate
+import javax.imageio.ImageIO
+
+
+object APIUtil {
+ private val parser = JsonParser()
+
+// val sslContext = SSLContexts.custom()
+// .loadTrustMaterial { chain, authType ->
+// isValidCert(chain, authType)
+// }
+// .build()
+// val sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
+// .setSslContext(sslContext)
+// .build()
+
+// val cm = PoolingHttpClientConnectionManagerBuilder.create()
+// .setSSLSocketFactory(sslSocketFactory)
+
+ val builder: HttpClientBuilder =
+ HttpClients.custom().setUserAgent("LorenzMod")
+// .setConnectionManagerShared(true)
+// .setConnectionManager(cm.build())
+ .setDefaultHeaders(
+ mutableListOf(
+ BasicHeader("Pragma", "no-cache"),
+ BasicHeader("Cache-Control", "no-cache")
+ )
+ )
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+// .setConnectTimeout(Timeout.ofMinutes(1))
+// .setResponseTimeout(Timeout.ofMinutes(1))
+ .build()
+ )
+ .useSystemProperties()
+
+ /**
+ * Taken from Elementa under MIT License
+ * @link https://github.com/Sk1erLLC/Elementa/blob/master/LICENSE
+ */
+ fun URL.getImage(): BufferedImage {
+ val connection = this.openConnection() as HttpURLConnection
+
+ connection.requestMethod = "GET"
+ connection.useCaches = true
+ connection.addRequestProperty("User-Agent", "LorenzMod")
+ connection.doOutput = true
+
+ return ImageIO.read(connection.inputStream)
+ }
+
+ 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
+ // parsing JSON
+// val result = JSONObject(retSrc) //Convert String to JSON Object
+// val tokenList: JSONArray = result.getJSONArray("names")
+// val oj: JSONObject = tokenList.getJSONObject(0)
+// val token: String = oj.getString("name")
+ }
+ }
+ } catch (ex: Throwable) {
+ ex.printStackTrace()
+ LorenzUtils.error("Skytils ran into an ${ex::class.simpleName ?: "error"} whilst fetching a resource. See logs for more details.")
+ } finally {
+ client.close()
+ }
+ return JsonObject()
+ }
+
+// fun getArrayResponse(urlString: String): JsonArray {
+// val client = builder.build()
+// try {
+// client.execute(HttpGet(urlString)).use { response ->
+//// response.entity.content
+// response.entity.content { entity ->
+// val obj = parser.parse(EntityUtils.toString(entity)).asJsonArray
+// EntityUtils.consume(entity)
+// return obj
+// }
+// }
+// } catch (ex: Throwable) {
+// LorenzUtils.error("Skytils ran into an ${ex::class.simpleName ?: "error"} whilst fetching a resource. See logs for more details.")
+// ex.printStackTrace()
+// } finally {
+// client.close()
+// }
+// return JsonArray()
+// }
+
+ private fun isValidCert(chain: Array<X509Certificate>, authType: String): Boolean {
+ return chain.any { it.issuerDN.name == "CN=R3, O=Let's Encrypt, C=US" }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/ItemUtil.kt b/src/main/java/at/lorenz/mod/utils/ItemUtil.kt
new file mode 100644
index 000000000..fc0409e31
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/ItemUtil.kt
@@ -0,0 +1,211 @@
+package at.lorenz.mod.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
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/ItemUtils.kt b/src/main/java/at/lorenz/mod/utils/ItemUtils.kt
new file mode 100644
index 000000000..697e57393
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/ItemUtils.kt
@@ -0,0 +1,39 @@
+package at.lorenz.mod.utils
+
+import at.lorenz.mod.utils.LorenzUtils.Companion.removeColorCodes
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.item.ItemStack
+
+class ItemUtils {
+
+ companion object {
+ 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") }
+
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/LorenzColor.kt b/src/main/java/at/lorenz/mod/utils/LorenzColor.kt
new file mode 100644
index 000000000..e60d6d8d1
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/LorenzColor.kt
@@ -0,0 +1,27 @@
+package at.lorenz.mod.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/lorenz/mod/utils/LorenzLogger.kt b/src/main/java/at/lorenz/mod/utils/LorenzLogger.kt
new file mode 100644
index 000000000..1b7337224
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/LorenzLogger.kt
@@ -0,0 +1,70 @@
+package at.lorenz.mod.utils
+
+import at.lorenz.mod.utils.LorenzUtils.Companion.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/LorenzAddons/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/lorenz/mod/utils/LorenzUtils.kt b/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt
new file mode 100644
index 000000000..021c055d7
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt
@@ -0,0 +1,91 @@
+package at.lorenz.mod.utils
+
+import net.minecraft.client.Minecraft
+import net.minecraft.util.ChatComponentText
+import org.intellij.lang.annotations.Language
+import java.text.SimpleDateFormat
+
+class LorenzUtils {
+
+ companion object {
+ const val DEBUG_PREFIX = "[Debug] §7"
+
+ fun debug(message: String) {
+ internaChat(DEBUG_PREFIX + message)
+ }
+
+ fun warning(message: String) {
+ internaChat("§cWarning! $message")
+ }
+
+ fun error(message: String) {
+ internaChat("§4$message")
+ }
+
+ fun chat(message: String) {
+ internaChat(message)
+ }
+
+ private fun internaChat(message: String) {
+ val thePlayer = Minecraft.getMinecraft().thePlayer
+ 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)
+ }
+
+// if (!message.startsWith(LorenzUtils.DEBUG_PREFIX + "chat api got (123)")) {
+// if (message.matchRegex("(.*)§r§7 \\((.{1,3})\\)")) {
+// val indexOf = message.lastIndexOf("(")
+//// LorenzAddons.testLogger.log("chat api got (123)!")
+//// LorenzAddons.testLogger.log("before: '$message'")
+// message = message.substring(0, indexOf - 5)
+//// LorenzAddons.testLogger.log("after: '$message'")
+//// LorenzAddons.testLogger.log("")
+//// LorenzUtils.debug("chat api got (123)")
+//// } else if (message.endsWith("§r§7 (2)")) {
+////// LorenzAddons.testLogger.log("other variant: '$message'")
+////// LorenzAddons.testLogger.log("")
+//// LorenzUtils.debug("chat api got WRONG (123)")
+// }
+// }
+
+ return message
+ }
+
+ fun Double.round(decimals: Int): Double {
+ var multiplier = 1.0
+ repeat(decimals) { multiplier *= 10 }
+ return kotlin.math.round(this * multiplier) / multiplier
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/RenderUtil.kt b/src/main/java/at/lorenz/mod/utils/RenderUtil.kt
new file mode 100644
index 000000000..e633461a4
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/RenderUtil.kt
@@ -0,0 +1,20 @@
+package at.lorenz.mod.utils
+
+import net.minecraft.client.gui.Gui
+import net.minecraft.inventory.Slot
+
+class RenderUtil {
+
+ companion object {
+
+ infix fun Slot.highlight(color: LorenzColor) {
+ Gui.drawRect(
+ this.xDisplayPosition,
+ this.yDisplayPosition,
+ this.xDisplayPosition + 16,
+ this.yDisplayPosition + 16,
+ color.toColor().rgb
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/ComponentHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/ComponentHandler.java
new file mode 100644
index 000000000..83b2c0973
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/ComponentHandler.java
@@ -0,0 +1,123 @@
+package com.thatgravyboat.skyblockhud;
+
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
+import com.thatgravyboat.skyblockhud.dungeons.DungeonHandler;
+import com.thatgravyboat.skyblockhud.location.*;
+import com.thatgravyboat.skyblockhud.seasons.SeasonDateHandler;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.network.NetworkPlayerInfo;
+import net.minecraft.scoreboard.ScorePlayerTeam;
+import net.minecraft.world.WorldSettings;
+import net.minecraftforge.client.GuiIngameForge;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+
+public class ComponentHandler {
+
+ public static final Pattern SCOREBOARD_CHARACTERS = Pattern.compile("[^]\\[a-z A-Z:0-9/'.()+\\d-§?]");
+ private static final Ordering<NetworkPlayerInfo> sortingList = Ordering.from(new PlayerComparator());
+ private static int ticksExisted = 0;
+
+ @SubscribeEvent
+ public void onClientTick(TickEvent.ClientTickEvent event) {
+ Minecraft mc = Minecraft.getMinecraft();
+ ticksExisted++;
+ boolean eventPass = false;
+ if (mc.theWorld != null) {
+ List<NetworkPlayerInfo> players = sortingList.sortedCopy(mc.thePlayer.sendQueue.getPlayerInfoMap());
+ GuiIngameForge.renderObjective = !SkyblockHud.hasSkyblockScoreboard() || !SkyblockHud.config.misc.hideScoreboard;
+ if (players != null && SkyblockHud.hasSkyblockScoreboard()) {
+ if (ticksExisted % 60 == 0) {
+ for (NetworkPlayerInfo player : players) {
+ if (player.getDisplayName() != null) {
+ String formattedTabListPlayer = SCOREBOARD_CHARACTERS.matcher(Utils.removeColor(player.getDisplayName().getFormattedText())).replaceAll("");
+ if (LocationHandler.getCurrentLocation().equals(Locations.CATACOMBS)) {
+ if (formattedTabListPlayer.toLowerCase().contains("secrets found:")) DungeonHandler.parseTotalSecrets(formattedTabListPlayer);
+ if (formattedTabListPlayer.toLowerCase().contains("deaths:")) DungeonHandler.parseDeaths(formattedTabListPlayer);
+ if (formattedTabListPlayer.toLowerCase().contains("crypts:")) DungeonHandler.parseCrypts(formattedTabListPlayer);
+ } else if (LocationHandler.getCurrentLocation().getCategory().isMiningCategory()) {
+ if (formattedTabListPlayer.toLowerCase().contains("mithril powder:")) {
+ MinesHandler.parseMithril(formattedTabListPlayer);
+ }
+ if (formattedTabListPlayer.toLowerCase().contains("gemstone powder:")) {
+ MinesHandler.parseGemstone(formattedTabListPlayer);
+ }
+ } else if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.MUSHROOMDESERT)) {
+ if (formattedTabListPlayer.toLowerCase().contains("pelts:")) {
+ try {
+ FarmingIslandHandler.pelts = Integer.parseInt(formattedTabListPlayer.toLowerCase().replace("pelts:", "").trim());
+ } catch (Exception ignored) {}
+ }
+ }
+ }
+ }
+ if (players.size() > 80) {
+ for (int i = 61; i <= 80; i++) {
+ if (players.get(i).getDisplayName() != null) {
+ String formattedTabListPlayer = SCOREBOARD_CHARACTERS.matcher(Utils.removeColor(players.get(i).getDisplayName().getFormattedText())).replaceAll("");
+ if (formattedTabListPlayer.toLowerCase().contains("event:")) {
+ if (i < 80) {
+ if (players.get(i + 1).getDisplayName() != null) {
+ String secondLine = SCOREBOARD_CHARACTERS.matcher(Utils.removeColor(players.get(i + 1).getDisplayName().getFormattedText())).replaceAll("");
+ if (!formattedTabListPlayer.contains("N/A")) {
+ SeasonDateHandler.setCurrentEvent(formattedTabListPlayer.replace("Event:", ""), secondLine);
+ eventPass = true;
+ }
+ }
+ }
+ }
+ }
+ if (i == 80 && !eventPass) {
+ SeasonDateHandler.setCurrentEvent("", "");
+ }
+ }
+ }
+ }
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.PARK)) {
+ if (players.size() >= 80) {
+ for (int i = 41; i <= 60; i++) {
+ if (players.get(i).getDisplayName() != null) {
+ String formattedTabListPlayer = SCOREBOARD_CHARACTERS.matcher(Utils.removeColor(players.get(i).getDisplayName().getFormattedText())).replaceAll("");
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.PARK)) {
+ if (formattedTabListPlayer.toLowerCase().contains("rain:")) {
+ ParkIslandHandler.parseRain(formattedTabListPlayer.toLowerCase());
+ }
+ }
+ }
+ }
+ }
+ } else if (ParkIslandHandler.isRaining()) {
+ ParkIslandHandler.parseRain(null);
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onStatusBar(ClientChatReceivedEvent event) {
+ if (event.type == 2) {
+ if (LocationHandler.getCurrentLocation().equals(Locations.CATACOMBS)) DungeonHandler.parseSecrets(event.message.getFormattedText());
+ }
+ }
+
+ @SideOnly(Side.CLIENT)
+ static class PlayerComparator implements Comparator<NetworkPlayerInfo> {
+
+ private PlayerComparator() {}
+
+ public int compare(NetworkPlayerInfo p_compare_1_, NetworkPlayerInfo p_compare_2_) {
+ ScorePlayerTeam scoreplayerteam = p_compare_1_.getPlayerTeam();
+ ScorePlayerTeam scoreplayerteam1 = p_compare_2_.getPlayerTeam();
+ return ComparisonChain.start().compareTrueFirst(p_compare_1_.getGameType() != WorldSettings.GameType.SPECTATOR, p_compare_2_.getGameType() != WorldSettings.GameType.SPECTATOR).compare(scoreplayerteam != null ? scoreplayerteam.getRegisteredName() : "", scoreplayerteam1 != null ? scoreplayerteam1.getRegisteredName() : "").compare(p_compare_1_.getGameProfile().getName(), p_compare_2_.getGameProfile().getName()).result();
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/DevModeConstants.java b/src/main/java/com/thatgravyboat/skyblockhud/DevModeConstants.java
new file mode 100644
index 000000000..89df438d7
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/DevModeConstants.java
@@ -0,0 +1,6 @@
+package com.thatgravyboat.skyblockhud;
+
+public class DevModeConstants {
+
+ public static boolean mobDeathLogging = false;
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/GuiTextures.java b/src/main/java/com/thatgravyboat/skyblockhud/GuiTextures.java
new file mode 100644
index 000000000..bb49c9c9e
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/GuiTextures.java
@@ -0,0 +1,33 @@
+package com.thatgravyboat.skyblockhud;
+
+import net.minecraft.util.ResourceLocation;
+
+public class GuiTextures {
+
+ private GuiTextures() {}
+
+ public static final ResourceLocation DISCORD = new ResourceLocation("skyblockhud:discord.png");
+ public static final ResourceLocation TWITTER = new ResourceLocation("skyblockhud:twitter.png");
+
+ public static final ResourceLocation button_tex = new ResourceLocation("skyblockhud:button.png");
+
+ public static final ResourceLocation button_white = new ResourceLocation("skyblockhud:button_white.png");
+
+ public static final ResourceLocation BAR = new ResourceLocation("skyblockhud:core/bar.png");
+ public static final ResourceLocation OFF = new ResourceLocation("skyblockhud:core/toggle_off.png");
+ public static final ResourceLocation ONE = new ResourceLocation("skyblockhud:core/toggle_1.png");
+ public static final ResourceLocation TWO = new ResourceLocation("skyblockhud:core/toggle_2.png");
+ public static final ResourceLocation THREE = new ResourceLocation("skyblockhud:core/toggle_3.png");
+ public static final ResourceLocation ON = new ResourceLocation("skyblockhud:core/toggle_on.png");
+ public static final ResourceLocation DELETE = new ResourceLocation("skyblockhud:core/delete.png");
+
+ public static final ResourceLocation slider_off_cap = new ResourceLocation("skyblockhud:core/slider/slider_off_cap.png");
+ public static final ResourceLocation slider_off_notch = new ResourceLocation("skyblockhud:core/slider/slider_off_notch.png");
+ public static final ResourceLocation slider_off_segment = new ResourceLocation("skyblockhud:core/slider/slider_off_segment.png");
+ public static final ResourceLocation slider_on_cap = new ResourceLocation("skyblockhud:core/slider/slider_on_cap.png");
+ public static final ResourceLocation slider_on_notch = new ResourceLocation("skyblockhud:core/slider/slider_on_notch.png");
+ public static final ResourceLocation slider_on_segment = new ResourceLocation("skyblockhud:core/slider/slider_on_segment.png");
+ public static final ResourceLocation slider_button_new = new ResourceLocation("skyblockhud:core/slider/slider_button.png");
+
+ public static final ResourceLocation mapOverlay = new ResourceLocation("skyblockhud", "maps/map_overlay.png");
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/SkyblockHud.java b/src/main/java/com/thatgravyboat/skyblockhud/SkyblockHud.java
new file mode 100644
index 000000000..fa55cf3ad
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/SkyblockHud.java
@@ -0,0 +1,195 @@
+package com.thatgravyboat.skyblockhud;
+
+import at.lorenz.mod.bazaar.BazaarApi;
+import at.lorenz.mod.HideNotClickableItems;
+import at.lorenz.mod.bazaar.BazaarOrderHelper;
+import at.lorenz.mod.chat.ChatFilter;
+import at.lorenz.mod.chat.ChatManager;
+import at.lorenz.mod.dungeon.DungeonChatFilter;
+import com.thatgravyboat.skyblockhud.config.SBHConfig;
+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 java.io.File;
+
+@Mod(modid = SkyblockHud.MODID, version = SkyblockHud.VERSION)
+public class SkyblockHud {
+
+ public static final String MODID = "lorenzmod";
+ public static final String VERSION = "0.1";
+
+ public static SBHConfig config;
+
+// private File configFile;
+
+// private static final Set<String> SKYBLOCK_IN_ALL_LANGUAGES = Sets.newHashSet("SKYBLOCK", "\u7A7A\u5C9B\u751F\u5B58");
+
+// 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(new BazaarOrderHelper());
+ MinecraftForge.EVENT_BUS.register(new ChatManager());
+ MinecraftForge.EVENT_BUS.register(new ChatFilter());
+ MinecraftForge.EVENT_BUS.register(new DungeonChatFilter());
+ MinecraftForge.EVENT_BUS.register(new HideNotClickableItems());
+
+
+// MinecraftForge.EVENT_BUS.register(new LeaderboardGetter());
+// MinecraftForge.EVENT_BUS.register(new SeasonDateHandler());
+// MinecraftForge.EVENT_BUS.register(new LocationHandler());
+// MinecraftForge.EVENT_BUS.register(new IslandHandler());
+// MinecraftForge.EVENT_BUS.register(new TimeHandler());
+// MinecraftForge.EVENT_BUS.register(new CurrencyHandler());
+// MinecraftForge.EVENT_BUS.register(new SlayerHandler());
+// MinecraftForge.EVENT_BUS.register(new DungeonHandler());
+// MinecraftForge.EVENT_BUS.register(new MinesHandler());
+// MinecraftForge.EVENT_BUS.register(new FarmingIslandHandler());
+//
+// MinecraftForge.EVENT_BUS.register(new TrackerHandler());
+// MinecraftForge.EVENT_BUS.register(new KillTracking());
+//
+// MinecraftForge.EVENT_BUS.register(new HeldItemHandler());
+//
+// ClientRegistry.registerKeyBinding(KeyBindings.map);
+//
+// MinecraftForge.EVENT_BUS.register(new ComponentHandler());
+// MinecraftForge.EVENT_BUS.register(new ActionBarParsing());
+// MinecraftForge.EVENT_BUS.register(new CrystalWaypoints());
+// MinecraftForge.EVENT_BUS.register(new FarmHouseHandler());
+// MinecraftForge.EVENT_BUS.register(new WarpHandler());
+// MinecraftForge.EVENT_BUS.register(new CooldownHandler());
+// Commands.init();
+//
+// ((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(new NpcDialogue());
+// ((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(new Textures());
+//
+// configDirectory = new File(event.getModConfigurationDirectory(), "skyblockhud");
+// try {
+// configDirectory.mkdir();
+// } catch (Exception ignored) {}
+//
+// configFile = new File(configDirectory, "sbh-config.json");
+//
+// if (configFile.exists()) {
+// try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8))) {
+// config = gson.fromJson(reader, SBHConfig.class);
+// } catch (Exception ignored) {}
+// }
+//
+// if (config == null) {
+// config = new SBHConfig();
+// saveConfig();
+// }
+//
+// Textures.setTexture(config.misc.style);
+//
+// if (WarpHandler.load()) {
+// WarpHandler.save();
+// }
+//
+// Runtime.getRuntime().addShutdownHook(new Thread(this::saveConfig));
+// Runtime.getRuntime().addShutdownHook(new Thread(TrackerFileLoader::saveTrackerStatsFile));
+ }
+
+// public void saveConfig() {
+// try {
+// configFile.createNewFile();
+//
+// try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(configFile), StandardCharsets.UTF_8))) {
+// writer.write(gson.toJson(config));
+// }
+// } catch (IOException ignored) {}
+// }
+
+// @EventHandler
+// public void postInit(FMLPostInitializationEvent event) {
+// MinecraftForge.EVENT_BUS.register(new OverlayHud());
+// MinecraftForge.EVENT_BUS.register(new RPGHud());
+// MinecraftForge.EVENT_BUS.register(new DungeonOverlay());
+// MinecraftForge.EVENT_BUS.register(new BossbarHandler());
+// MinecraftForge.EVENT_BUS.register(new MapHandler());
+// MinecraftForge.EVENT_BUS.register(new MiningHud());
+// MinecraftForge.EVENT_BUS.register(new NpcDialogue());
+// }
+
+// @EventHandler
+// public void loadComplete(FMLLoadCompleteEvent event) {
+// TrackerFileLoader.loadTrackersFile();
+//
+// if (TrackerFileLoader.loadTrackerStatsFile()) {
+// TrackerFileLoader.saveTrackerStatsFile();
+// }
+// }
+
+// @SubscribeEvent
+// public void onLeaveServer(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) {
+// TrackerFileLoader.saveTrackerStatsFile();
+// }
+
+ public static boolean hasSkyblockScoreboard() {
+// Minecraft mc = Minecraft.getMinecraft();
+//
+// if (mc != null && mc.theWorld != null) {
+// Scoreboard scoreboard = mc.theWorld.getScoreboard();
+// ScoreObjective sidebarObjective = scoreboard.getObjectiveInDisplaySlot(1);
+// if (sidebarObjective != null) {
+// String objectiveName = sidebarObjective.getDisplayName().replaceAll("(?i)\\u00A7.", "");
+// for (String skyblock : SKYBLOCK_IN_ALL_LANGUAGES) {
+// if (objectiveName.startsWith(skyblock)) {
+// return true;
+// }
+// }
+// }
+// }
+
+ return false;
+ }
+
+// @SubscribeEvent
+// public void onTooltip(ItemTooltipEvent event) {
+// if (event.itemStack != null && Keyboard.isKeyDown(Keyboard.KEY_BACKSLASH)) {
+// try {
+// StringSelection clipboard = new StringSelection(event.itemStack.serializeNBT().toString());
+// Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboard, clipboard);
+// } catch (Exception ignored) {}
+// }
+// }
+
+// @SubscribeEvent(priority = EventPriority.HIGHEST)
+// public void onStatusBar(ClientChatReceivedEvent event) {
+// String message = Utils.removeColor(event.message.getUnformattedText()).toLowerCase().trim();
+//
+// if (message.startsWith("your profile was changed to:")) {
+// String stripped = message.replace("your profile was changed to:", "").replace("(co-op)", "").trim();
+// MinecraftForge.EVENT_BUS.post(new ProfileSwitchedEvent(stripped));
+// }
+// if (message.startsWith("you are playing on profile:")) {
+// String stripped = message.replace("you are playing on profile:", "").replace("(co-op)", "").trim();
+// MinecraftForge.EVENT_BUS.post(new ProfileJoinedEvent(stripped));
+// }
+// }
+
+ 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/com/thatgravyboat/skyblockhud/api/KillTracking.java b/src/main/java/com/thatgravyboat/skyblockhud/api/KillTracking.java
new file mode 100644
index 000000000..bc98672d8
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/KillTracking.java
@@ -0,0 +1,61 @@
+package com.thatgravyboat.skyblockhud.api;
+
+import com.thatgravyboat.skyblockhud.DevModeConstants;
+import com.thatgravyboat.skyblockhud.api.events.SkyBlockEntityKilled;
+import com.thatgravyboat.skyblockhud.api.sbentities.EntityTypeRegistry;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import net.minecraft.client.Minecraft;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.DamageSource;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.entity.EntityJoinWorldEvent;
+import net.minecraftforge.event.entity.living.LivingDeathEvent;
+import net.minecraftforge.event.entity.player.AttackEntityEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class KillTracking {
+
+ public static final Set<UUID> attackedEntities = new HashSet<>();
+
+ @SubscribeEvent
+ public void onAttack(AttackEntityEvent event) {
+ if (event.target != null) {
+ attackedEntities.add(event.target.getUniqueID());
+ }
+ }
+
+ @SubscribeEvent
+ public void onDeath(LivingDeathEvent event) {
+ if (DevModeConstants.mobDeathLogging) {
+ //Used for testing
+ System.out.println("----------------------------------------------------------------------------------------------------------------");
+ System.out.println("Name : " + event.entity.getName());
+ System.out.println("UUID : " + event.entity.getUniqueID());
+ NBTTagCompound compound = new NBTTagCompound();
+ event.entity.writeToNBT(compound);
+ System.out.println("Tag : " + compound);
+ System.out.println("Damage : " + getDamageSourceString(event.source));
+ System.out.println("SBH Entity ID: " + EntityTypeRegistry.getEntityId(event.entity));
+ System.out.println("----------------------------------------------------------------------------------------------------------------");
+ }
+ if (attackedEntities.contains(event.entity.getUniqueID())) {
+ if (EntityTypeRegistry.getEntityId(event.entity) != null) {
+ MinecraftForge.EVENT_BUS.post(new SkyBlockEntityKilled(EntityTypeRegistry.getEntityId(event.entity), event.entity));
+ }
+ attackedEntities.remove(event.entity.getUniqueID());
+ }
+ }
+
+ public static String getDamageSourceString(DamageSource source) {
+ return ("{ " + source.getDamageType() + ", " + source.isDamageAbsolute() + ", " + source.isDifficultyScaled() + ", " + source.isFireDamage() + ", " + source.isProjectile() + ", " + source.isUnblockable() + ", " + source.isExplosion() + ", " + source.isMagicDamage() + ", " + source.isCreativePlayer() + ", " + source.getSourceOfDamage() + " }");
+ }
+
+ @SubscribeEvent
+ public void onWorldChange(EntityJoinWorldEvent event) {
+ if (event.entity == Minecraft.getMinecraft().thePlayer) {
+ attackedEntities.clear();
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/LeaderboardGetter.java b/src/main/java/com/thatgravyboat/skyblockhud/api/LeaderboardGetter.java
new file mode 100644
index 000000000..3c5936057
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/LeaderboardGetter.java
@@ -0,0 +1,65 @@
+package com.thatgravyboat.skyblockhud.api;
+
+import static com.thatgravyboat.skyblockhud.ComponentHandler.SCOREBOARD_CHARACTERS;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarPostEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarPreGetEvent;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.*;
+import java.util.stream.Collectors;
+import net.minecraft.client.Minecraft;
+import net.minecraft.scoreboard.Score;
+import net.minecraft.scoreboard.ScoreObjective;
+import net.minecraft.scoreboard.ScorePlayerTeam;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+public class LeaderboardGetter {
+
+ private static Map<Integer, String> cachedScores = new HashMap<>();
+ private static List<String> cachedScoresList = new ArrayList<>();
+
+ private static int ticks = 0;
+
+ public static List<String> getCachedScores() {
+ return cachedScoresList;
+ }
+
+ @SubscribeEvent
+ public void onClientUpdate(TickEvent.ClientTickEvent event) {
+ if (event.phase.equals(TickEvent.Phase.START)) return;
+ ticks++;
+ if (ticks % 5 != 0) return;
+
+ Minecraft mc = Minecraft.getMinecraft();
+ if (mc.theWorld != null && SkyblockHud.hasSkyblockScoreboard()) {
+ Scoreboard scoreboard = mc.theWorld.getScoreboard();
+ ScoreObjective sidebarObjective = scoreboard.getObjectiveInDisplaySlot(1);
+
+ if (sidebarObjective != null && !MinecraftForge.EVENT_BUS.post(new SidebarPreGetEvent(scoreboard, sidebarObjective))) {
+ Collection<Score> scoreList = sidebarObjective.getScoreboard().getSortedScores(sidebarObjective);
+ Map<Integer, String> scores = scoreList.stream().collect(Collectors.toMap(Score::getScorePoints, this::getLine, (s1, s2) -> s1));
+
+ if (!cachedScores.equals(scores)) {
+ scores.forEach((score, name) -> {
+ if (cachedScores.get(score) == null || !cachedScores.get(score).equals(name)) {
+ MinecraftForge.EVENT_BUS.post(new SidebarLineUpdateEvent(name, SCOREBOARD_CHARACTERS.matcher(name).replaceAll("").trim(), score, scores.size(), scoreboard, sidebarObjective));
+ }
+ });
+ cachedScores = scores;
+ cachedScoresList = scores.values().stream().map(name -> SCOREBOARD_CHARACTERS.matcher(name).replaceAll("").trim()).collect(Collectors.toList());
+ }
+ MinecraftForge.EVENT_BUS.post(new SidebarPostEvent(scoreboard, sidebarObjective, cachedScoresList));
+ }
+ }
+ }
+
+ public String getLine(Score score) {
+ ScorePlayerTeam scorePlayerTeam = score.getScoreScoreboard().getPlayersTeam(score.getPlayerName());
+ return Utils.removeColor(ScorePlayerTeam.formatPlayerName(scorePlayerTeam, score.getPlayerName()));
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/LocationChangeEvent.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/LocationChangeEvent.java
new file mode 100644
index 000000000..384e76d2e
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/LocationChangeEvent.java
@@ -0,0 +1,15 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import com.thatgravyboat.skyblockhud.location.Locations;
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class LocationChangeEvent extends Event {
+
+ public Locations oldLocation;
+ public Locations newLocation;
+
+ public LocationChangeEvent(Locations oldLocation, Locations newLocation) {
+ this.oldLocation = oldLocation;
+ this.newLocation = newLocation;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileJoinedEvent.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileJoinedEvent.java
new file mode 100644
index 000000000..f1d4d38b8
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileJoinedEvent.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class ProfileJoinedEvent extends Event {
+
+ public String profile;
+
+ public ProfileJoinedEvent(String profile) {
+ this.profile = profile;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileSwitchedEvent.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileSwitchedEvent.java
new file mode 100644
index 000000000..408ac8ade
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/ProfileSwitchedEvent.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class ProfileSwitchedEvent extends Event {
+
+ public String profile;
+
+ public ProfileSwitchedEvent(String profile) {
+ this.profile = profile;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarLineUpdateEvent.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarLineUpdateEvent.java
new file mode 100644
index 000000000..2737ee911
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarLineUpdateEvent.java
@@ -0,0 +1,22 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import net.minecraft.scoreboard.ScoreObjective;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class SidebarLineUpdateEvent extends Event {
+
+ public String rawLine;
+ public String formattedLine;
+ public int position;
+ public Scoreboard scoreboard;
+ public ScoreObjective objective;
+
+ public SidebarLineUpdateEvent(String rawLine, String formattedLine, int score, int max, Scoreboard scoreboard, ScoreObjective objective) {
+ this.rawLine = rawLine;
+ this.formattedLine = formattedLine;
+ this.position = max - score;
+ this.scoreboard = scoreboard;
+ this.objective = objective;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPostEvent.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPostEvent.java
new file mode 100644
index 000000000..34d27e6bc
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPostEvent.java
@@ -0,0 +1,21 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import java.util.List;
+import net.minecraft.scoreboard.ScoreObjective;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class SidebarPostEvent extends Event {
+
+ public Scoreboard scoreboard;
+ public ScoreObjective objective;
+ public List<String> scores;
+ public String[] arrayScores;
+
+ public SidebarPostEvent(Scoreboard scoreboard, ScoreObjective objective, List<String> scores) {
+ this.scoreboard = scoreboard;
+ this.objective = objective;
+ this.scores = scores;
+ this.arrayScores = scores.toArray(new String[] {});
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPreGetEvent.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPreGetEvent.java
new file mode 100644
index 000000000..0db1895c6
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SidebarPreGetEvent.java
@@ -0,0 +1,18 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import net.minecraft.scoreboard.ScoreObjective;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraftforge.fml.common.eventhandler.Cancelable;
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+@Cancelable
+public class SidebarPreGetEvent extends Event {
+
+ public Scoreboard scoreboard;
+ public ScoreObjective objective;
+
+ public SidebarPreGetEvent(Scoreboard scoreboard, ScoreObjective objective) {
+ this.scoreboard = scoreboard;
+ this.objective = objective;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/events/SkyBlockEntityKilled.java b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SkyBlockEntityKilled.java
new file mode 100644
index 000000000..61f8d824f
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/events/SkyBlockEntityKilled.java
@@ -0,0 +1,18 @@
+package com.thatgravyboat.skyblockhud.api.events;
+
+import javax.annotation.Nullable;
+import net.minecraft.entity.Entity;
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class SkyBlockEntityKilled extends Event {
+
+ public String id;
+
+ @Nullable
+ public Entity entity;
+
+ public SkyBlockEntityKilled(String id, @Nullable Entity entity) {
+ this.id = id;
+ this.entity = entity;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/item/IAbility.java b/src/main/java/com/thatgravyboat/skyblockhud/api/item/IAbility.java
new file mode 100644
index 000000000..4f330d9a3
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/item/IAbility.java
@@ -0,0 +1,7 @@
+package com.thatgravyboat.skyblockhud.api.item;
+
+public interface IAbility {
+ String getAbility();
+
+ int getAbilityTime();
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeHelper.java b/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeHelper.java
new file mode 100644
index 000000000..bb14e5d21
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeHelper.java
@@ -0,0 +1,36 @@
+package com.thatgravyboat.skyblockhud.api.sbentities;
+
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.monster.EntityEnderman;
+import net.minecraft.entity.monster.EntityZombie;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+
+public class EntityTypeHelper {
+
+ public static boolean isZealot(Entity entity) {
+ if (entity instanceof EntityEnderman) {
+ EntityEnderman enderman = ((EntityEnderman) entity);
+ double maxHealthBase = enderman.getAttributeMap().getAttributeInstanceByName("generic.maxHealth").getBaseValue();
+ if (maxHealthBase == 13000 || (maxHealthBase == 2000d && enderman.getHeldBlockState().getBlock().equals(Blocks.end_portal_frame))) {
+ return LocationHandler.getCurrentLocation().equals(Locations.DRAGONSNEST);
+ }
+ }
+ return false;
+ }
+
+ public static boolean isCrypt(Entity entity) {
+ if (entity instanceof EntityZombie) {
+ EntityZombie zombie = ((EntityZombie) entity);
+ double maxHealthBase = zombie.getAttributeMap().getAttributeInstanceByName("generic.maxHealth").getBaseValue();
+ if (maxHealthBase != 2000d) return false;
+ if (zombie.getEquipmentInSlot(0) == null || !zombie.getEquipmentInSlot(0).getItem().equals(Items.iron_sword)) return false;
+ if (zombie.getEquipmentInSlot(1) == null || !zombie.getEquipmentInSlot(1).getItem().equals(Items.chainmail_boots)) return false;
+ if (zombie.getEquipmentInSlot(2) == null || !zombie.getEquipmentInSlot(2).getItem().equals(Items.chainmail_leggings)) return false;
+ return zombie.getEquipmentInSlot(3) != null && zombie.getEquipmentInSlot(3).getItem().equals(Items.chainmail_chestplate);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeRegistry.java b/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeRegistry.java
new file mode 100644
index 000000000..6ff88fafb
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/EntityTypeRegistry.java
@@ -0,0 +1,27 @@
+package com.thatgravyboat.skyblockhud.api.sbentities;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.List;
+import java.util.Map;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.monster.EntityEnderman;
+import net.minecraft.entity.monster.EntityZombie;
+
+public class EntityTypeRegistry {
+
+ private static final Map<Class<? extends Entity>, List<SkyBlockEntity>> entities = Maps.newHashMap();
+
+ static {
+ entities.put(EntityEnderman.class, ImmutableList.of(SkyBlockEntity.of("ZEALOT", EntityTypeHelper::isZealot)));
+ entities.put(EntityZombie.class, ImmutableList.of(SkyBlockEntity.of("CRYPT_GHOUL", EntityTypeHelper::isCrypt)));
+ }
+
+ public static String getEntityId(Entity entity) {
+ if (!entities.containsKey(entity.getClass())) return null;
+ for (SkyBlockEntity skyBlockEntity : entities.get(entity.getClass())) {
+ if (skyBlockEntity.isEntity(entity)) return skyBlockEntity.getName();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/SkyBlockEntity.java b/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/SkyBlockEntity.java
new file mode 100644
index 000000000..bcd1196f5
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/api/sbentities/SkyBlockEntity.java
@@ -0,0 +1,27 @@
+package com.thatgravyboat.skyblockhud.api.sbentities;
+
+import java.util.function.Predicate;
+import net.minecraft.entity.Entity;
+
+public class SkyBlockEntity {
+
+ private final String name;
+ private final Predicate<Entity> predicate;
+
+ public static SkyBlockEntity of(String name, Predicate<Entity> predicate) {
+ return new SkyBlockEntity(name, predicate);
+ }
+
+ private SkyBlockEntity(String name, Predicate<Entity> predicate) {
+ this.name = name;
+ this.predicate = predicate;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isEntity(Entity entity) {
+ return predicate.test(entity);
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/commands/Commands.java b/src/main/java/com/thatgravyboat/skyblockhud/commands/Commands.java
new file mode 100644
index 000000000..e8ac1d389
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/commands/Commands.java
@@ -0,0 +1,105 @@
+package com.thatgravyboat.skyblockhud.commands;
+
+import com.google.common.collect.ImmutableSet;
+import com.thatgravyboat.skyblockhud.DevModeConstants;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.LeaderboardGetter;
+import com.thatgravyboat.skyblockhud.config.SBHConfigEditor;
+import com.thatgravyboat.skyblockhud.core.GuiScreenElementWrapper;
+import com.thatgravyboat.skyblockhud.handlers.CrystalWaypoints;
+import com.thatgravyboat.skyblockhud.handlers.MapHandler;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.playerstats.ActionBarParsing;
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.AbstractClientPlayer;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.boss.BossStatus;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.client.ClientCommandHandler;
+import org.apache.commons.lang3.StringUtils;
+
+public class Commands {
+
+ private static boolean devMode = false;
+
+ private static final SimpleCommand.ProcessCommandRunnable settingsRunnable = new SimpleCommand.ProcessCommandRunnable() {
+ public void processCommand(ICommandSender sender, String[] args) {
+ if (args.length > 0) {
+ SkyblockHud.screenToOpen = new GuiScreenElementWrapper(new SBHConfigEditor(SkyblockHud.config, StringUtils.join(args, " ")));
+ } else {
+ SkyblockHud.screenToOpen = new GuiScreenElementWrapper(new SBHConfigEditor(SkyblockHud.config));
+ }
+ }
+ };
+
+ private static final SimpleSubCommand devCommand = new SimpleSubCommand("sbhdev", ImmutableSet.of("copyNpcSkin", "copyBossBar", "copyScoreboard", "copyActionBar", "mobDeathLogging")) {
+ @Override
+ void processSubCommand(ICommandSender sender, String subCommand, String[] args) {
+ StringSelection clipboard = null;
+ switch (subCommand) {
+ case "copyBossBar":
+ clipboard = new StringSelection(BossStatus.bossName);
+ break;
+ case "copyScoreboard":
+ StringBuilder builder = new StringBuilder();
+ LeaderboardGetter.getCachedScores().forEach(s -> builder.append(s).append("\n"));
+ clipboard = new StringSelection(builder.toString());
+ break;
+ case "copyActionBar":
+ clipboard = new StringSelection(ActionBarParsing.lastLowActionBar);
+ break;
+ case "copySkin":
+ Entity entity = Minecraft.getMinecraft().objectMouseOver.entityHit;
+ if (entity instanceof AbstractClientPlayer) {
+ clipboard = new StringSelection("http://textures.minecraft.net/texture/" + ((AbstractClientPlayer) entity).getLocationSkin().getResourcePath().replace("skins/", ""));
+ } else {
+ sendSBHMessage(sender, "Not a player!");
+ }
+ break;
+ case "mobDeathLogging":
+ DevModeConstants.mobDeathLogging = !DevModeConstants.mobDeathLogging;
+ sendSBHMessage(sender, "Mob Death Logging " + (DevModeConstants.mobDeathLogging ? "Enabled!" : "Disabled!"));
+ }
+ if (clipboard != null) {
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboard, clipboard);
+ sendSBHMessage(sender, "Info copied to clipboard!");
+ }
+ }
+
+ @Override
+ void processNoSubCommand(ICommandSender sender) {
+ devMode = !devMode;
+ sender.addChatMessage(new ChatComponentText("Dev Mode " + (devMode ? "Enabled!" : "Disabled!")));
+ }
+ };
+
+ private static final SimpleCommand settingsCommand = new SimpleCommand("sbh", settingsRunnable);
+ private static final SimpleCommand settingsCommand2 = new SimpleCommand("sbhsettings", settingsRunnable);
+ private static final SimpleCommand settingsCommand3 = new SimpleCommand("sbhud", settingsRunnable);
+
+ private static final SimpleCommand mapCommand = new SimpleCommand(
+ "sbhmap",
+ new SimpleCommand.ProcessCommandRunnable() {
+ public void processCommand(ICommandSender sender, String[] args) {
+ if (LocationHandler.getCurrentLocation().getCategory().getMap() != null && SkyblockHud.hasSkyblockScoreboard()) SkyblockHud.screenToOpen = new MapHandler.MapScreen();
+ }
+ }
+ );
+
+ public static void init() {
+ ClientCommandHandler.instance.registerCommand(settingsCommand);
+ ClientCommandHandler.instance.registerCommand(settingsCommand2);
+ ClientCommandHandler.instance.registerCommand(settingsCommand3);
+ ClientCommandHandler.instance.registerCommand(mapCommand);
+ ClientCommandHandler.instance.registerCommand(devCommand);
+ ClientCommandHandler.instance.registerCommand(new CrystalWaypoints.WaypointCommand());
+ }
+
+ private static void sendSBHMessage(ICommandSender sender, String message) {
+ sender.addChatMessage(new ChatComponentText("[" + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "SkyBlockHud" + EnumChatFormatting.RESET + "] : " + EnumChatFormatting.GRAY + message));
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleCommand.java b/src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleCommand.java
new file mode 100644
index 000000000..57ef3168a
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleCommand.java
@@ -0,0 +1,60 @@
+package com.thatgravyboat.skyblockhud.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 String commandName;
+ private 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/com/thatgravyboat/skyblockhud/commands/SimpleSubCommand.java b/src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleSubCommand.java
new file mode 100644
index 000000000..6bb62acff
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/commands/SimpleSubCommand.java
@@ -0,0 +1,61 @@
+package com.thatgravyboat.skyblockhud.commands;
+
+import java.util.List;
+import java.util.Set;
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.util.BlockPos;
+import org.apache.commons.lang3.ArrayUtils;
+
+public abstract class SimpleSubCommand extends CommandBase {
+
+ private final String commandName;
+ private final Set<String> subCommands;
+
+ public SimpleSubCommand(String commandName, Set<String> subCommands) {
+ this.commandName = commandName;
+ this.subCommands = subCommands;
+ }
+
+ @Override
+ public String getCommandName() {
+ return commandName;
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender) {
+ return "/" + commandName;
+ }
+
+ @Override
+ public void processCommand(ICommandSender sender, String[] args) {
+ if (args.length == 0) {
+ processNoSubCommand(sender);
+ return;
+ }
+ if (subCommands.contains(args[0])) {
+ processSubCommand(sender, args[0], ArrayUtils.remove(args, 0));
+ return;
+ }
+ processBadSubCommand(sender, args[0]);
+ }
+
+ @Override
+ public boolean canCommandSenderUseCommand(ICommandSender sender) {
+ return true;
+ }
+
+ @Override
+ public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) {
+ if (args.length == 1) {
+ return getListOfStringsMatchingLastWord(args, subCommands);
+ }
+ return null;
+ }
+
+ abstract void processSubCommand(ICommandSender sender, String subCommand, String[] args);
+
+ abstract void processNoSubCommand(ICommandSender sender);
+
+ public void processBadSubCommand(ICommandSender sender, String subCommand) {}
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/config/KeyBindings.java b/src/main/java/com/thatgravyboat/skyblockhud/config/KeyBindings.java
new file mode 100644
index 000000000..9ffd3523b
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/config/KeyBindings.java
@@ -0,0 +1,8 @@
+package com.thatgravyboat.skyblockhud.config;
+
+import net.minecraft.client.settings.KeyBinding;
+
+public class KeyBindings {
+
+ public static KeyBinding map = new KeyBinding("Opens the big map.", 50, "SkyblockHud");
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfig.java b/src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfig.java
new file mode 100644
index 000000000..8793fb8ec
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfig.java
@@ -0,0 +1,436 @@
+package com.thatgravyboat.skyblockhud.config;
+
+import com.google.gson.annotations.Expose;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.core.GuiScreenElementWrapper;
+import com.thatgravyboat.skyblockhud.core.config.Config;
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.core.config.annotations.*;
+import com.thatgravyboat.skyblockhud.core.config.gui.GuiPositionEditor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import net.minecraft.client.Minecraft;
+
+public class SBHConfig extends Config {
+
+ private void editOverlay(String activeConfig, int width, int height, Position position) {
+ Minecraft.getMinecraft().displayGuiScreen(new GuiPositionEditor(position, width, height, () -> {}, () -> {}, () -> SkyblockHud.screenToOpen = new GuiScreenElementWrapper(new SBHConfigEditor(SkyblockHud.config, activeConfig))));
+ }
+
+ @Override
+ public void executeRunnable(String runnableId) {
+ String activeConfigCategory = null;
+ if (Minecraft.getMinecraft().currentScreen instanceof GuiScreenElementWrapper) {
+ GuiScreenElementWrapper wrapper = (GuiScreenElementWrapper) Minecraft.getMinecraft().currentScreen;
+ if (wrapper.element instanceof SBHConfigEditor) {
+ activeConfigCategory = ((SBHConfigEditor) wrapper.element).getSelectedCategoryName();
+ }
+ }
+
+ switch (runnableId) {
+ case "rpg":
+ editOverlay(activeConfigCategory, 120, 47, rpg.rpgHudPosition);
+ return;
+ case "d1":
+ editOverlay(activeConfigCategory, 120, 32, dungeon.dungeonPlayer1);
+ return;
+ case "d2":
+ editOverlay(activeConfigCategory, 120, 32, dungeon.dungeonPlayer2);
+ return;
+ case "d3":
+ editOverlay(activeConfigCategory, 120, 32, dungeon.dungeonPlayer3);
+ return;
+ case "d4":
+ editOverlay(activeConfigCategory, 120, 32, dungeon.dungeonPlayer4);
+ return;
+ case "main":
+ editOverlay(activeConfigCategory, 1000, 34, main.mainHudPos);
+ return;
+ case "ultimate":
+ editOverlay(activeConfigCategory, 182, 5, dungeon.barPosition);
+ return;
+ case "map":
+ editOverlay(activeConfigCategory, 72, 72, map.miniMapPosition);
+ return;
+ case "tracker":
+ editOverlay(activeConfigCategory, 130, 70, trackers.trackerPosition);
+ return;
+ case "drill":
+ editOverlay(activeConfigCategory, 136, 7, mining.drillBar);
+ return;
+ case "heat":
+ editOverlay(activeConfigCategory, 45, 7, mining.heatBar);
+ return;
+ case "dialogue":
+ editOverlay(activeConfigCategory, 182, 68, misc.dialoguePos);
+ return;
+ }
+ }
+
+ @Expose
+ @Category(name = "Misc Options", desc = "Just a bunch of random options.")
+ public Misc misc = new Misc();
+
+ @Expose
+ @Category(name = "Main Hud", desc = "All Options for the main hud.")
+ public MainHud main = new MainHud();
+
+ @Expose
+ @Category(name = "RPG Hud", desc = "All Options for the RPG hud.")
+ public RPGHud rpg = new RPGHud();
+
+ @Expose
+ @Category(name = "Dungeon Hud", desc = "All Options for the Dungeon hud.")
+ public DungeonHud dungeon = new DungeonHud();
+
+ @Expose
+ @Category(name = "Renderer", desc = "All Options for rendering.")
+ public Renderer renderer = new Renderer();
+
+ @Expose
+ @Category(name = "Map", desc = "All Options for the Map.")
+ public Map map = new Map();
+
+ @Expose
+ @Category(name = "Mining", desc = "All Options for the Mining Stuff.")
+ public Mining mining = new Mining();
+
+ @Expose
+ @Category(name = "Tracker", desc = "All Options for the Trackers.")
+ public Trackers trackers = new Trackers();
+
+ public static class Misc {
+
+ @Expose
+ @ConfigOption(name = "Hide Scoreboard", desc = "Hides the scoreboard when in Skyblock.")
+ @ConfigEditorBoolean
+ public boolean hideScoreboard = false;
+
+ @Expose
+ @ConfigOption(name = "Texture Styles", desc = "If this list only contains 1 thing that means your texture pack doesnt support styles")
+ @ConfigEditorStyle
+ public int style = 0;
+
+ @Expose
+ @ConfigOption(name = "Hide Dialogue Box", desc = "Hides the Dialogue Box.")
+ @ConfigEditorBoolean
+ public boolean hideDialogueBox = true;
+
+ @Expose
+ @ConfigOption(name = "Dialogue Box", desc = "")
+ @ConfigEditorButton(runnableId = "dialogue", buttonText = "Edit")
+ public Position dialoguePos = new Position(0, -50, true, false);
+
+ @Expose
+ @ConfigOption(name = "Hide Item Cooldowns", desc = "Hides item cooldowns")
+ @ConfigEditorBoolean
+ public boolean hideItemCooldowns = false;
+ }
+
+ public static class MainHud {
+
+ @Expose
+ @ConfigOption(name = "Disable Main Hud", desc = "IDK Why you would do this as its like half the mod but ok.")
+ @ConfigEditorBoolean
+ public boolean disaleMainHud = false;
+
+ @Expose
+ @ConfigOption(name = "Main Hud Position", desc = "")
+ @ConfigEditorButton(runnableId = "main", buttonText = "Edit")
+ public Position mainHudPos = new Position(0, 1, true, false);
+
+ @Expose
+ @ConfigOption(name = "Twelve Hour Clock", desc = "Allows you to change the clock to be 12 hour instead of 24 hour.")
+ @ConfigEditorBoolean
+ public boolean twelveHourClock = false;
+
+ @Expose
+ @ConfigOption(name = "Shift hud with boss", desc = "Shifts the hud when bossbar is visible.")
+ @ConfigEditorBoolean
+ public boolean bossShiftHud = true;
+
+ @Expose
+ @ConfigOption(name = "Require Redstone", desc = "Allows to make it so that the redstone percentage requires you to hold a redstone item to show.")
+ @ConfigEditorBoolean
+ public boolean requireRedstone = true;
+ }
+
+ public static class RPGHud {
+
+ @Expose
+ @ConfigOption(name = "Show RPG Hud", desc = "Allows you to show or hide the RPG Hud.")
+ @ConfigEditorBoolean
+ public boolean showRpgHud = true;
+
+ @Expose
+ @ConfigOption(name = "Flip Hud", desc = "Flips the hud when half way across the screen.")
+ @ConfigEditorBoolean
+ public boolean flipHud = true;
+
+ @Expose
+ @ConfigOption(name = "RPG Hud Position", desc = "Allows you to change the position of the RPG Hud.")
+ @ConfigEditorButton(runnableId = "rpg", buttonText = "Edit")
+ public Position rpgHudPosition = new Position(1, 1);
+ }
+
+ public static class DungeonHud {
+
+ @Expose
+ @ConfigOption(name = "Dungeon Ultimate Bar", desc = "")
+ @ConfigEditorAccordion(id = 2)
+ public boolean ultimateBar = false;
+
+ @Expose
+ @ConfigOption(name = "Hide Ultimate Bar", desc = "Hides the custom ultimate bar.")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 2)
+ public boolean hideUltimateBar = false;
+
+ @Expose
+ @ConfigOption(name = "Bar Position", desc = "Change the position of the bar.")
+ @ConfigEditorButton(runnableId = "ultimate", buttonText = "Edit")
+ @ConfigAccordionId(id = 2)
+ public Position barPosition = new Position(0, 50, true, false);
+
+ @Expose
+ @ConfigOption(name = "Bar Loading Color", desc = "The color of the bar when its loading.")
+ @ConfigEditorColour
+ @ConfigAccordionId(id = 2)
+ public String barLoadColor = "159:0:0:0:255";
+
+ @Expose
+ @ConfigOption(name = "Bar Full Color", desc = "The color of the bar when its full.")
+ @ConfigEditorColour
+ @ConfigAccordionId(id = 2)
+ public String barFullColor = "255:0:0:0:255";
+
+ @Expose
+ @ConfigOption(name = "Bar Style", desc = "Change the style of the bar")
+ @ConfigEditorDropdown(values = { "No Notch", "6 Notch", "10 Notch", "12 Notch", "20 Notch" })
+ @ConfigAccordionId(id = 2)
+ public int barStyle = 2;
+
+ @Expose
+ @ConfigOption(name = "Dungeon Players", desc = "")
+ @ConfigEditorAccordion(id = 1)
+ public boolean dungeonPlayerAccordion = false;
+
+ @Expose
+ @ConfigOption(name = "Hide Dungeon Players", desc = "Allows you to hide the dungeon player hud")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 1)
+ public boolean hideDungeonPlayers = false;
+
+ @Expose
+ @ConfigOption(name = "Dungeon Player Opacity", desc = "Allows you to change the opacity of the dungeon players.")
+ @ConfigEditorSlider(minValue = 0, maxValue = 100, minStep = 1)
+ @ConfigAccordionId(id = 1)
+ public int dungeonPlayerOpacity = 0;
+
+ @Expose
+ @ConfigOption(name = "Hide Dead Players", desc = "Allows you to hide players that are dead or have left.")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 1)
+ public boolean hideDeadDungeonPlayers = false;
+
+ @Expose
+ @ConfigOption(name = "Player Position 1", desc = "Change the position of this dungeon player.")
+ @ConfigEditorButton(runnableId = "d1", buttonText = "Edit")
+ @ConfigAccordionId(id = 1)
+ public Position dungeonPlayer1 = new Position(5, 5);
+
+ @Expose
+ @ConfigOption(name = "Player Position 2", desc = "Change the position of this dungeon player.")
+ @ConfigEditorButton(runnableId = "d2", buttonText = "Edit")
+ @ConfigAccordionId(id = 1)
+ public Position dungeonPlayer2 = new Position(5, 42);
+
+ @Expose
+ @ConfigOption(name = "Player Position 3", desc = "Change the position of this dungeon player.")
+ @ConfigEditorButton(runnableId = "d3", buttonText = "Edit")
+ @ConfigAccordionId(id = 1)
+ public Position dungeonPlayer3 = new Position(5, 79);
+
+ @Expose
+ @ConfigOption(name = "Player Position 4", desc = "Change the position of this dungeon player.")
+ @ConfigEditorButton(runnableId = "d4", buttonText = "Edit")
+ @ConfigAccordionId(id = 1)
+ public Position dungeonPlayer4 = new Position(5, 116);
+ }
+
+ public static class Renderer {
+
+ @Expose
+ @ConfigOption(name = "Add Overflow Mana Back", desc = "Adds overflow mana back to the actionbar")
+ @ConfigEditorBoolean
+ public boolean addOverflowMana = false;
+
+ @Expose
+ @ConfigOption(name = "Hide Boss Bar", desc = "Hides Boss Bar when certain conditions are met such as the name is just wither or it starts with objective:")
+ @ConfigEditorBoolean
+ public boolean hideBossBar = true;
+
+ @Expose
+ @ConfigOption(name = "Hide XP Bar", desc = "Hides xp bar.")
+ @ConfigEditorBoolean
+ public boolean hideXpBar = true;
+
+ @Expose
+ @ConfigOption(name = "Hide Food", desc = "Hides food.")
+ @ConfigEditorBoolean
+ public boolean hideFood = true;
+
+ @Expose
+ @ConfigOption(name = "Hide air", desc = "Hides air.")
+ @ConfigEditorBoolean
+ public boolean hideAir = true;
+
+ @Expose
+ @ConfigOption(name = "Hide hearts", desc = "Hides hearts.")
+ @ConfigEditorBoolean
+ public boolean hideHearts = true;
+
+ @Expose
+ @ConfigOption(name = "Hide armor", desc = "Hides armor.")
+ @ConfigEditorBoolean
+ public boolean hideArmor = true;
+
+ @Expose
+ @ConfigOption(name = "Hide Animal Hearts", desc = "Hides Animal Hearts.")
+ @ConfigEditorBoolean
+ public boolean hideAnimalHearts = true;
+ }
+
+ public static class Map {
+
+ @Expose
+ @ConfigOption(name = "Show Player Location", desc = "This feature is off by default as Hypixel's rules are so vague that this would fall under their disallowed modifications.")
+ @ConfigEditorBoolean
+ public boolean showPlayerLocation = false;
+
+ @Expose
+ @ConfigOption(name = "Show Mini-Map", desc = "Shows the Mini-Map on your overlay if turned off you can still use /sbhmap to see the map in fullscreen.")
+ @ConfigEditorBoolean
+ public boolean showMiniMap = false;
+
+ @Expose
+ @ConfigOption(name = "Map Locations", desc = "Remove a location from this list if you would like the map to not show up in that location. This is so you can use other mods maps.")
+ @ConfigEditorDraggableList(exampleText = { "HUB", "BARN", "MUSHROOMDESERT", "GOLDMINE (No Map Yet)", "DEEPCAVERNS (No Map Yet)", "SPIDERSDEN", "PARK", "FORTRESS", "DUNGEONHUB (No Map Yet)", "JERRY (No Map Yet)", "THEEND (No Map Yet)", "DWARVENMINES", "CRYSTALHOLLOWS" })
+ public List<Integer> mapLocations = new ArrayList<>(Arrays.asList(0, 1, 2, 5, 6, 7, 11));
+
+ @Expose
+ @ConfigOption(name = "Mini-Map Position", desc = "Allows you to change the position of the Mini-Map.")
+ @ConfigEditorButton(runnableId = "map", buttonText = "Edit")
+ public Position miniMapPosition = new Position(0, 100, false, false);
+
+ @Expose
+ @ConfigOption(name = "Icons", desc = "")
+ @ConfigEditorAccordion(id = 3)
+ public boolean icons = false;
+
+ @Expose
+ @ConfigOption(name = "NPC", desc = "Show NPC Icons")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 3)
+ public boolean showNpcIcons = true;
+
+ @Expose
+ @ConfigOption(name = "Info", desc = "Show Info Icons")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 3)
+ public boolean showInfoIcons = true;
+
+ @Expose
+ @ConfigOption(name = "Misc", desc = "Show Misc Icons")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 3)
+ public boolean showMiscIcons = true;
+
+ @Expose
+ @ConfigOption(name = "Shops", desc = "Show Shop Icons")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 3)
+ public boolean showShopIcons = true;
+
+ @Expose
+ @ConfigOption(name = "Quests", desc = "Show Quest Icons")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 3)
+ public boolean showQuestIcons = false;
+ }
+
+ public static class Mining {
+
+ @Expose
+ @ConfigOption(name = "Mining Bars", desc = "")
+ @ConfigEditorAccordion(id = 4)
+ public boolean miningBars = false;
+
+ @Expose
+ @ConfigOption(name = "Bar Mode", desc = "Change the mode of bar. Static mode will allow it to auto replace the xp when drill is held or you are heating up.")
+ @ConfigEditorDropdown(values = { "Moveable", "Static" })
+ @ConfigAccordionId(id = 4)
+ public int barMode = 1;
+
+ @Expose
+ @ConfigOption(name = "Show Drill Bar", desc = "Allows you to show or hide the Drill Bar.")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 4)
+ public boolean showDrillBar = true;
+
+ @Expose
+ @ConfigOption(name = "Show Heat Bar", desc = "Allows you to show or hide the Heat Bar.")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 4)
+ public boolean showHeatBar = true;
+
+ @Expose
+ @ConfigOption(name = "Bar Positions (Requires mode to be Moveable)", desc = "")
+ @ConfigAccordionId(id = 4)
+ @ConfigEditorAccordion(id = 5)
+ public boolean barPositions = false;
+
+ @Expose
+ @ConfigOption(name = "Drill Bar Position", desc = "Allows you to change the position of the Drill Bar.")
+ @ConfigEditorButton(runnableId = "drill", buttonText = "Edit")
+ @ConfigAccordionId(id = 5)
+ public Position drillBar = new Position(-1, -1);
+
+ @Expose
+ @ConfigOption(name = "Heat Bar Position", desc = "Allows you to change the position of the Heat Bar.")
+ @ConfigEditorButton(runnableId = "heat", buttonText = "Edit")
+ @ConfigAccordionId(id = 5)
+ public Position heatBar = new Position(-1, -9);
+
+ @Expose
+ @ConfigOption(name = "Crystal Hollow Waypoints", desc = "")
+ @ConfigEditorAccordion(id = 6)
+ public boolean waypoints = false;
+
+ @Expose
+ @ConfigOption(name = "Auto Waypoint", desc = "Turns on auto waypoints for the main areas of crystal hollows.")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 6)
+ public boolean autoWaypoint = true;
+
+ @Expose
+ @ConfigOption(name = "Chat Waypoint Mode", desc = "Change the mode of the chat waypoint In Chat Bar will allow you to edit it before adding it to your waypoints.")
+ @ConfigEditorDropdown(values = { "Instant Add", "In chat bar" })
+ @ConfigAccordionId(id = 6)
+ public int chatWaypointMode = 1;
+ }
+
+ public static class Trackers {
+
+ @Expose
+ @ConfigOption(name = "Tracker Position", desc = "Allows you to change the position of the Trackers.")
+ @ConfigEditorButton(runnableId = "tracker", buttonText = "Edit")
+ public Position trackerPosition = new Position(-1, 200);
+
+ @Expose
+ @ConfigOption(name = "Hide Tracker", desc = "It will still track the data just in case.")
+ @ConfigEditorBoolean
+ public boolean hideTracker = true;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfigEditor.java b/src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfigEditor.java
new file mode 100644
index 000000000..113037066
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/config/SBHConfigEditor.java
@@ -0,0 +1,602 @@
+package com.thatgravyboat.skyblockhud.config;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.DISCORD;
+import static com.thatgravyboat.skyblockhud.GuiTextures.TWITTER;
+
+import com.google.common.collect.Lists;
+import com.thatgravyboat.skyblockhud.core.GlScissorStack;
+import com.thatgravyboat.skyblockhud.core.GuiElement;
+import com.thatgravyboat.skyblockhud.core.config.Config;
+import com.thatgravyboat.skyblockhud.core.config.gui.GuiOptionEditor;
+import com.thatgravyboat.skyblockhud.core.config.gui.GuiOptionEditorAccordion;
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.lerp.LerpUtils;
+import com.thatgravyboat.skyblockhud.core.util.lerp.LerpingInteger;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.core.util.render.TextRenderUtils;
+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 SBHConfigEditor extends GuiElement {
+
+ private static final ResourceLocation[] socialsIco = new ResourceLocation[] { DISCORD, TWITTER };
+ private static final String[] socialsLink = new String[] { "https://discord.gg/moulberry", "https://twitter.com/thatgravyboat/" };
+
+ 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 LinkedHashMap<String, ConfigProcessor.ProcessedCategory> processedConfig;
+ private TreeMap<String, Set<ConfigProcessor.ProcessedOption>> searchOptionMap = new TreeMap<>();
+ private HashMap<ConfigProcessor.ProcessedOption, ConfigProcessor.ProcessedCategory> categoryForOption = new HashMap<>();
+
+ public SBHConfigEditor(Config config) {
+ this(config, null);
+ }
+
+ public SBHConfigEditor(Config 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("SkyBlockHud by " + EnumChatFormatting.RED + "ThatGravyBoat" + 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/com/thatgravyboat/skyblockhud/core/BackgroundBlur.java b/src/main/java/com/thatgravyboat/skyblockhud/core/BackgroundBlur.java
new file mode 100644
index 000000000..64f04fe70
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/BackgroundBlur.java
@@ -0,0 +1,249 @@
+package com.thatgravyboat.skyblockhud.core;
+
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/ChromaColour.java b/src/main/java/com/thatgravyboat/skyblockhud/core/ChromaColour.java
new file mode 100644
index 000000000..d43e8b9cb
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/ChromaColour.java
@@ -0,0 +1,93 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/GlScissorStack.java b/src/main/java/com/thatgravyboat/skyblockhud/core/GlScissorStack.java
new file mode 100644
index 000000000..0e1694e8a
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/GlScissorStack.java
@@ -0,0 +1,86 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/GuiElement.java b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElement.java
new file mode 100644
index 000000000..e8d9d62df
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElement.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/GuiElementBoolean.java b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementBoolean.java
new file mode 100644
index 000000000..8daf4b1e0
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementBoolean.java
@@ -0,0 +1,118 @@
+package com.thatgravyboat.skyblockhud.core;
+
+import com.thatgravyboat.skyblockhud.GuiTextures;
+import com.thatgravyboat.skyblockhud.core.util.lerp.LerpUtils;
+import com.thatgravyboat.skyblockhud.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 int clickRadius;
+ private 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/com/thatgravyboat/skyblockhud/core/GuiElementColour.java b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementColour.java
new file mode 100644
index 000000000..c38e51b4c
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementColour.java
@@ -0,0 +1,370 @@
+package com.thatgravyboat.skyblockhud.core;
+
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.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 int x;
+ private int y;
+ private int xSize = 119;
+ private int ySize = 89;
+
+ private float wheelAngle = 0;
+ private float wheelRadius = 0;
+
+ private int clickedComponent = -1;
+
+ private Consumer<String> colourChangedCallback;
+ private 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/com/thatgravyboat/skyblockhud/core/GuiElementTextField.java b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementTextField.java
new file mode 100644
index 000000000..bbb29eec0
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiElementTextField.java
@@ -0,0 +1,549 @@
+package com.thatgravyboat.skyblockhud.core;
+
+import com.thatgravyboat.skyblockhud.core.util.StringUtils;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/GuiScreenElementWrapper.java b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiScreenElementWrapper.java
new file mode 100644
index 000000000..326c85fed
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/GuiScreenElementWrapper.java
@@ -0,0 +1,34 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/Config.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/Config.java
new file mode 100644
index 000000000..dbbca7495
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/Config.java
@@ -0,0 +1,6 @@
+package com.thatgravyboat.skyblockhud.core.config;
+
+public class Config {
+
+ public void executeRunnable(String runnableId) {}
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/core/config/KeybindHelper.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/KeybindHelper.java
new file mode 100644
index 000000000..7470b6ce0
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/KeybindHelper.java
@@ -0,0 +1,49 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/Position.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/Position.java
new file mode 100644
index 000000000..0b3a1f5d7
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/Position.java
@@ -0,0 +1,197 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/Category.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/Category.java
new file mode 100644
index 000000000..05ae6ad87
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/Category.java
@@ -0,0 +1,14 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigAccordionId.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigAccordionId.java
new file mode 100644
index 000000000..44b82bea6
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigAccordionId.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorAccordion.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorAccordion.java
new file mode 100644
index 000000000..9712148fa
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorAccordion.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorBoolean.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorBoolean.java
new file mode 100644
index 000000000..f86448e44
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorBoolean.java
@@ -0,0 +1,11 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorButton.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorButton.java
new file mode 100644
index 000000000..5e4bab8b2
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorButton.java
@@ -0,0 +1,14 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorColour.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorColour.java
new file mode 100644
index 000000000..146f09ae9
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorColour.java
@@ -0,0 +1,11 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDraggableList.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDraggableList.java
new file mode 100644
index 000000000..6bbeb27ec
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDraggableList.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDropdown.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDropdown.java
new file mode 100644
index 000000000..cc8bbf442
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorDropdown.java
@@ -0,0 +1,14 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorKeybind.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorKeybind.java
new file mode 100644
index 000000000..e8e55f1b2
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorKeybind.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorSlider.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorSlider.java
new file mode 100644
index 000000000..dcdf0b18b
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorSlider.java
@@ -0,0 +1,16 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorStyle.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorStyle.java
new file mode 100644
index 000000000..a02a12d66
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorStyle.java
@@ -0,0 +1,11 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorText.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorText.java
new file mode 100644
index 000000000..61e7ed58f
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigEditorText.java
@@ -0,0 +1,11 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigOption.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigOption.java
new file mode 100644
index 000000000..457475833
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/annotations/ConfigOption.java
@@ -0,0 +1,16 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditor.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditor.java
new file mode 100644
index 000000000..927bebf01
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditor.java
@@ -0,0 +1,62 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorAccordion.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorAccordion.java
new file mode 100644
index 000000000..dad85a61c
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorAccordion.java
@@ -0,0 +1,80 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.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 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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorBoolean.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorBoolean.java
new file mode 100644
index 000000000..4167e061f
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorBoolean.java
@@ -0,0 +1,37 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.GuiElementBoolean;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorButton.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorButton.java
new file mode 100644
index 000000000..9cc76c283
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorButton.java
@@ -0,0 +1,60 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.button_tex;
+
+import com.thatgravyboat.skyblockhud.core.config.Config;
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.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 String runnableId;
+ private String buttonText;
+ private Config config;
+
+ public GuiOptionEditorButton(ConfigProcessor.ProcessedOption option, String runnableId, String buttonText, Config 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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorColour.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorColour.java
new file mode 100644
index 000000000..c916fef38
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorColour.java
@@ -0,0 +1,81 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.*;
+
+import com.thatgravyboat.skyblockhud.core.ChromaColour;
+import com.thatgravyboat.skyblockhud.core.GuiElementColour;
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.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) {
+ if (colourElement != null && colourElement.mouseInput(mouseX, mouseY)) {
+ return true;
+ }
+ return false;
+ }
+
+ @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() {
+ if (colourElement != null && colourElement.keyboardInput()) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDraggableList.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDraggableList.java
new file mode 100644
index 000000000..1e6fa0c63
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDraggableList.java
@@ -0,0 +1,268 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.DELETE;
+import static com.thatgravyboat.skyblockhud.GuiTextures.button_tex;
+
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.lerp.LerpUtils;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.core.util.render.TextRenderUtils;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+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 String[] exampleText;
+ private 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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDropdown.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDropdown.java
new file mode 100644
index 000000000..6b703cd12
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorDropdown.java
@@ -0,0 +1,145 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorKeybind.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorKeybind.java
new file mode 100644
index 000000000..ecdaa3aae
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorKeybind.java
@@ -0,0 +1,88 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.*;
+
+import com.thatgravyboat.skyblockhud.core.config.KeybindHelper;
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.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 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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorSlider.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorSlider.java
new file mode 100644
index 000000000..ad96a4f78
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorSlider.java
@@ -0,0 +1,136 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.GuiElementTextField;
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorStyle.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorStyle.java
new file mode 100644
index 000000000..a2fd17aae
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorStyle.java
@@ -0,0 +1,42 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.config.struct.ConfigProcessor;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorText.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorText.java
new file mode 100644
index 000000000..c68deaf30
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiOptionEditorText.java
@@ -0,0 +1,78 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.GuiElementTextField;
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/config/gui/GuiPositionEditor.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiPositionEditor.java
new file mode 100644
index 000000000..7c16530c8
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/gui/GuiPositionEditor.java
@@ -0,0 +1,171 @@
+package com.thatgravyboat.skyblockhud.core.config.gui;
+
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.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 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/com/thatgravyboat/skyblockhud/core/config/struct/ConfigProcessor.java b/src/main/java/com/thatgravyboat/skyblockhud/core/config/struct/ConfigProcessor.java
new file mode 100644
index 000000000..9e0a9d899
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/config/struct/ConfigProcessor.java
@@ -0,0 +1,166 @@
+package com.thatgravyboat.skyblockhud.core.config.struct;
+
+import com.google.gson.annotations.Expose;
+import com.thatgravyboat.skyblockhud.core.config.Config;
+import com.thatgravyboat.skyblockhud.core.config.annotations.*;
+import com.thatgravyboat.skyblockhud.core.config.gui.*;
+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(Config 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/com/thatgravyboat/skyblockhud/core/util/GuiElementSlider.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/GuiElementSlider.java
new file mode 100644
index 000000000..eb5fe6fa6
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/GuiElementSlider.java
@@ -0,0 +1,120 @@
+package com.thatgravyboat.skyblockhud.core.util;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.*;
+
+import com.thatgravyboat.skyblockhud.core.GuiElement;
+import com.thatgravyboat.skyblockhud.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 float minValue;
+ private float maxValue;
+ private float minStep;
+
+ private float value;
+ private 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/com/thatgravyboat/skyblockhud/core/util/StringUtils.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/StringUtils.java
new file mode 100644
index 000000000..06506ee0b
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/StringUtils.java
@@ -0,0 +1,8 @@
+package com.thatgravyboat.skyblockhud.core.util;
+
+public class StringUtils {
+
+ public static String cleanColour(String in) {
+ return in.replaceAll("(?i)\\u00A7.", "");
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpUtils.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpUtils.java
new file mode 100644
index 000000000..1b88f5a14
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpUtils.java
@@ -0,0 +1,25 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingFloat.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingFloat.java
new file mode 100644
index 000000000..edf129b8c
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingFloat.java
@@ -0,0 +1,68 @@
+package com.thatgravyboat.skyblockhud.core.util.lerp;
+
+public class LerpingFloat {
+
+ private int timeSpent;
+ private long lastMillis;
+ private 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/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingInteger.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingInteger.java
new file mode 100644
index 000000000..0bae86985
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/lerp/LerpingInteger.java
@@ -0,0 +1,76 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/util/render/RenderUtils.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/render/RenderUtils.java
new file mode 100644
index 000000000..bbaabb88a
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/render/RenderUtils.java
@@ -0,0 +1,155 @@
+package com.thatgravyboat.skyblockhud.core.util.render;
+
+import com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/core/util/render/TextRenderUtils.java b/src/main/java/com/thatgravyboat/skyblockhud/core/util/render/TextRenderUtils.java
new file mode 100644
index 000000000..b8d255103
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/core/util/render/TextRenderUtils.java
@@ -0,0 +1,155 @@
+package com.thatgravyboat.skyblockhud.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/com/thatgravyboat/skyblockhud/dungeons/Classes.java b/src/main/java/com/thatgravyboat/skyblockhud/dungeons/Classes.java
new file mode 100644
index 000000000..b05fe91e1
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/dungeons/Classes.java
@@ -0,0 +1,48 @@
+package com.thatgravyboat.skyblockhud.dungeons;
+
+public enum Classes {
+ H("Healer", "H", 154),
+ M("Mage", "M", 90),
+ B("Berserk", "B", 122),
+ A("Archer", "A", 58),
+ T("Tank", "T", 186);
+
+ private final String displayName;
+ private final String classId;
+ private final int textureY;
+
+ Classes(String name, String id, int textureY) {
+ this.displayName = name;
+ this.classId = id;
+ this.textureY = textureY;
+ }
+
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ public String getClassId() {
+ return this.classId;
+ }
+
+ public int getTextureY() {
+ return this.textureY;
+ }
+
+ public static Classes findClass(String input) {
+ if (input.length() == 1) {
+ try {
+ return Classes.valueOf(input.toUpperCase());
+ } catch (IllegalArgumentException ignored) {}
+ } else if (input.length() == 3) {
+ try {
+ return Classes.valueOf(input.replace("[", "").replace("]", "").toUpperCase());
+ } catch (IllegalArgumentException ignored) {}
+ } else {
+ for (Classes clazz : Classes.values()) {
+ if (clazz.displayName.equalsIgnoreCase(input)) return clazz;
+ }
+ }
+ return B;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonHandler.java
new file mode 100644
index 000000000..5a5e4ec16
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonHandler.java
@@ -0,0 +1,195 @@
+package com.thatgravyboat.skyblockhud.dungeons;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarPostEvent;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class DungeonHandler {
+
+ private static final HashMap<String, DungeonPlayer> dungeonPlayersMap = new HashMap<>();
+ private static int dungeonTime = 0;
+ private static int dungeonCleared = 0;
+ private static boolean bloodKey = false;
+ private static int witherKeys = 0;
+ private static int maxSecrets = 0;
+ private static int secrets = 0;
+ private static int totalSecrets = 0;
+ private static int deaths = 0;
+ private static int crypts = 0;
+
+ private static final Pattern DungeonPlayerRegex = Pattern.compile("^\\[([HMBAT])] ([\\w]+) ([0-9]+|DEAD)$");
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (LocationHandler.getCurrentLocation().equals(Locations.CATACOMBS)) {
+ DungeonHandler.checkForDungeonTime(event.formattedLine);
+ DungeonHandler.checkForDungeonCleared(event.formattedLine);
+ DungeonHandler.checkForDungeonKeys(event.formattedLine, event.rawLine);
+ DungeonHandler.checkForDungeonPlayers(event.formattedLine, Minecraft.getMinecraft());
+ }
+ }
+
+ @SubscribeEvent
+ public void onSidebarPost(SidebarPostEvent event) {
+ if (!LocationHandler.getCurrentLocation().equals(Locations.CATACOMBS)) {
+ DungeonHandler.clearDungeonStats();
+ }
+ }
+
+ public static void checkForDungeonPlayers(String scoreLine, Minecraft mc) {
+ Matcher dungeonMatcher = DungeonPlayerRegex.matcher(scoreLine);
+ if (dungeonMatcher.matches() && DungeonHandler.dungeonTime > 0) {
+ Classes playerClass = Classes.valueOf(dungeonMatcher.group(1));
+ String displayName = dungeonMatcher.group(2);
+ String health = dungeonMatcher.group(3);
+ if (!mc.thePlayer.getName().toLowerCase().startsWith(displayName.toLowerCase().trim())) {
+ int healthNum = 0;
+ if (!health.equalsIgnoreCase("dead")) {
+ try {
+ healthNum = Integer.parseInt(health);
+ } catch (NumberFormatException ignored) {}
+ }
+ DungeonPlayer player = new DungeonPlayer(playerClass, displayName, healthNum, health.equalsIgnoreCase("dead"));
+ dungeonPlayersMap.put(displayName.toLowerCase(), player);
+ }
+ }
+ }
+
+ public static void checkForDungeonTime(String scoreLine) {
+ if (scoreLine.toLowerCase().trim().contains("time elapsed:")) {
+ String timeLine = scoreLine.toLowerCase().trim().replace("time elapsed:", "");
+ String[] times = timeLine.split("m ");
+ int time = 0;
+ try {
+ time += Integer.parseInt(times[0].replace(" ", "").replace("m", "")) * 60;
+ time += Integer.parseInt(times[1].replace(" ", "").replace("s", ""));
+ } catch (NumberFormatException ignored) {}
+ dungeonTime = time;
+ }
+ }
+
+ public static void checkForDungeonCleared(String scoreline) {
+ if (scoreline.toLowerCase().trim().contains("dungeon cleared:")) {
+ String dungeonClearedText = scoreline.toLowerCase().trim().replace("dungeon cleared:", "").replace(" ", "").replace("%", "");
+ try {
+ dungeonCleared = Integer.parseInt(dungeonClearedText);
+ } catch (NumberFormatException ignored) {}
+ }
+ }
+
+ public static void checkForDungeonKeys(String scoreline, String rawString) {
+ if (scoreline.toLowerCase().trim().contains("keys:")) {
+ String dungeonClearedText = scoreline.toLowerCase().trim().replace("keys:", "").replace(" ", "").replace("x", "");
+ bloodKey = rawString.contains("\u2713");
+ try {
+ witherKeys = Integer.parseInt(dungeonClearedText);
+ } catch (NumberFormatException ignored) {}
+ }
+ }
+
+ public static void parseSecrets(String statusBar) {
+ boolean hasSecrets = false;
+ String[] parts = statusBar.split(" {4,}");
+ for (String part : parts) {
+ if (part.toLowerCase().contains("secrets") && !statusBar.toLowerCase().contains("no secrets")) {
+ hasSecrets = true;
+ try {
+ String secret = Utils.removeColor(part.replace("Secrets", "")).replace(" ", "");
+ maxSecrets = Integer.parseInt(secret.split("/")[1]);
+ secrets = Integer.parseInt(secret.split("/")[0]);
+ } catch (NumberFormatException ignored) {}
+ }
+ }
+ if (!hasSecrets) {
+ maxSecrets = 0;
+ secrets = 0;
+ }
+ }
+
+ public static void parseTotalSecrets(String playerName) {
+ if (playerName.toLowerCase().contains("secrets found:")) {
+ String totalSecret = Utils.removeColor(playerName.toLowerCase().replace("secrets found:", "")).replace(" ", "");
+ try {
+ totalSecrets = Integer.parseInt(totalSecret);
+ } catch (NumberFormatException ignored) {}
+ }
+ }
+
+ public static void parseDeaths(String playerName) {
+ if (playerName.toLowerCase().contains("deaths:")) {
+ String death = Utils.removeColor(playerName.toLowerCase().replace("deaths:", "")).replace("(", "").replace(")", "").replace(" ", "");
+ try {
+ deaths = Integer.parseInt(death);
+ } catch (NumberFormatException ignored) {}
+ }
+ }
+
+ public static void parseCrypts(String playerName) {
+ if (playerName.toLowerCase().contains("crypts:")) {
+ String crypt = Utils.removeColor(playerName.toLowerCase().replace("crypts:", "")).replace(" ", "");
+ try {
+ crypts = Integer.parseInt(crypt);
+ } catch (NumberFormatException ignored) {}
+ }
+ }
+
+ public static void clearDungeonStats() {
+ dungeonPlayersMap.clear();
+ dungeonTime = 0;
+ dungeonCleared = 0;
+ bloodKey = false;
+ witherKeys = 0;
+ maxSecrets = 0;
+ secrets = 0;
+ totalSecrets = 0;
+ deaths = 0;
+ crypts = 0;
+ }
+
+ public static HashMap<String, DungeonPlayer> getDungeonPlayers() {
+ return dungeonPlayersMap;
+ }
+
+ public static int getDungeonTime() {
+ return dungeonTime;
+ }
+
+ public static int getDungeonCleared() {
+ return dungeonCleared;
+ }
+
+ public static int getWitherKeys() {
+ return witherKeys;
+ }
+
+ public static boolean hasBloodkey() {
+ return bloodKey;
+ }
+
+ public static int getMaxSecrets() {
+ return maxSecrets;
+ }
+
+ public static int getSecrets() {
+ return secrets;
+ }
+
+ public static int getDeaths() {
+ return deaths;
+ }
+
+ public static int getTotalSecrets() {
+ return totalSecrets;
+ }
+
+ public static int getCrypts() {
+ return crypts;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonPlayer.java b/src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonPlayer.java
new file mode 100644
index 000000000..b0816fb96
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/dungeons/DungeonPlayer.java
@@ -0,0 +1,32 @@
+package com.thatgravyboat.skyblockhud.dungeons;
+
+public class DungeonPlayer {
+
+ private final Classes dungeonClass;
+ private final String name;
+ private final int health;
+ private final boolean dead;
+
+ public DungeonPlayer(Classes playersClass, String playersName, int playersHealth, boolean isDead) {
+ this.dungeonClass = playersClass;
+ this.name = playersName;
+ this.health = isDead ? 0 : playersHealth;
+ this.dead = isDead;
+ }
+
+ public Classes getDungeonClass() {
+ return this.dungeonClass;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public int getHealth() {
+ return this.dead ? 0 : this.health;
+ }
+
+ public boolean isDead() {
+ return this.dead;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/BossbarHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/BossbarHandler.java
new file mode 100644
index 000000000..d605d8102
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/BossbarHandler.java
@@ -0,0 +1,36 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import net.minecraft.entity.boss.BossStatus;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class BossbarHandler {
+
+ public static boolean bossBarRendered = true;
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public void onBossbarRender(RenderGameOverlayEvent.Pre event) {
+ if (event.type == RenderGameOverlayEvent.ElementType.BOSSHEALTH && BossStatus.bossName != null) {
+ bossBarRendered = !event.isCanceled();
+ if (!SkyblockHud.config.main.bossShiftHud) {
+ bossBarRendered = false;
+ }
+ String bossName = Utils.removeColor(BossStatus.bossName);
+ if (SkyblockHud.config.renderer.hideBossBar && !LocationHandler.getCurrentLocation().equals(Locations.CATACOMBS)) {
+ if (bossName.equalsIgnoreCase("wither")) {
+ event.setCanceled(true);
+ bossBarRendered = false;
+ }
+ if (bossName.toLowerCase().startsWith("objective:")) {
+ event.setCanceled(true);
+ bossBarRendered = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/CooldownHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/CooldownHandler.java
new file mode 100644
index 000000000..7f382d56d
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/CooldownHandler.java
@@ -0,0 +1,121 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.google.common.collect.Sets;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.item.IAbility;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.event.entity.player.PlayerInteractEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+public class CooldownHandler {
+
+ private static final Pattern ABILITY_REGEX = Pattern.compile("\u00A76Ability: (.*) \u00A7e\u00A7lRIGHT CLICK .* \u00A78Cooldown: \u00A7a(\\d*)s");
+
+ private static final Map<String, Cooldown> COOLDOWNS = new HashMap<>();
+
+ private static final Set<String> CUSTOM_HANDLED_COOLDOWNS = Sets.newHashSet("Mining Speed Boost");
+
+ public static Matcher getAbility(NBTTagCompound nbt) {
+ if (nbt != null && nbt.hasKey("ExtraAttributes") && nbt.getCompoundTag("ExtraAttributes").hasKey("uuid") && nbt.hasKey("display")) {
+ NBTTagCompound display = nbt.getCompoundTag("display");
+ if (display != null && display.hasKey("Lore")) {
+ NBTTagList lore = display.getTagList("Lore", 8);
+ List<String> loreList = new ArrayList<>();
+ for (int i = 0; i < lore.tagCount(); i++) {
+ String loreLine = lore.getStringTagAt(i).trim();
+ if (!loreLine.isEmpty()) loreList.add(loreLine);
+ }
+ Matcher abilityMatcher = ABILITY_REGEX.matcher(String.join(" ", loreList));
+ if (abilityMatcher.find()) {
+ return abilityMatcher;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static void addCooldown(String id, int time) {
+ COOLDOWNS.putIfAbsent(id, new Cooldown(time * 20));
+ }
+
+ private static void addCooldown(IAbility ability, boolean isForced) {
+ if (isForced || !CUSTOM_HANDLED_COOLDOWNS.contains(ability.getAbility())) {
+ addCooldown(ability.getAbility(), ability.getAbilityTime());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onChat(ClientChatReceivedEvent event) {
+ String message = Utils.removeColor(event.message.getUnformattedText());
+ if (event.type != 2 && message.equals("You used your Mining Speed Boost Pickaxe Ability!")) {
+ if (Minecraft.getMinecraft().thePlayer.getHeldItem() != null) {
+ IAbility ability = (IAbility) (Object) Minecraft.getMinecraft().thePlayer.getHeldItem();
+ if (ability.getAbility().equals("Mining Speed Boost")) {
+ addCooldown("Mining Speed Boost", ability.getAbilityTime());
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void tick(TickEvent.ClientTickEvent event) {
+ if (SkyblockHud.config.misc.hideItemCooldowns) return;
+ if (event.phase.equals(TickEvent.Phase.END)) {
+ COOLDOWNS.values().forEach(Cooldown::tick);
+ COOLDOWNS.entrySet().removeIf(entry -> entry.getValue().isOver());
+ }
+ }
+
+ @SubscribeEvent
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ if (SkyblockHud.config.misc.hideItemCooldowns) return;
+ if (event.action.equals(PlayerInteractEvent.Action.RIGHT_CLICK_AIR) || event.action.equals(PlayerInteractEvent.Action.RIGHT_CLICK_BLOCK)) {
+ if (event.entityPlayer.getHeldItem() != null) {
+ IAbility ability = (IAbility) ((Object) event.entityPlayer.getHeldItem());
+ if (ability.getAbility() != null) {
+ addCooldown(ability, false);
+ }
+ }
+ }
+ }
+
+ public static float getAbilityTime(ItemStack stack) {
+ IAbility ability = (IAbility) ((Object) stack);
+ if (ability.getAbility() != null) {
+ return COOLDOWNS.containsKey(ability.getAbility()) ? COOLDOWNS.get(ability.getAbility()).getTime() : -1f;
+ }
+ return -1f;
+ }
+
+ private static class Cooldown {
+
+ public int current;
+ public final int end;
+
+ Cooldown(int end) {
+ this.end = end;
+ }
+
+ public boolean isOver() {
+ return current >= end;
+ }
+
+ public void tick() {
+ current++;
+ }
+
+ public float getTime() {
+ return current / (float) end;
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/CrystalWaypoints.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/CrystalWaypoints.java
new file mode 100644
index 000000000..bab57f8e1
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/CrystalWaypoints.java
@@ -0,0 +1,196 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.events.LocationChangeEvent;
+import com.thatgravyboat.skyblockhud.commands.SimpleCommand;
+import com.thatgravyboat.skyblockhud.location.LocationCategory;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.event.ClickEvent;
+import net.minecraft.event.HoverEvent;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.ChatStyle;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.client.event.RenderWorldLastEvent;
+import net.minecraftforge.event.entity.EntityJoinWorldEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class CrystalWaypoints {
+
+ public static final Pattern LOCATION_MESSAGE_REGEX = Pattern.compile("\\{(.*) : \\((-?\\d+)/(-?\\d+)/(-?\\d+)\\)}");
+
+ public static final HashMap<String, BlockPos> waypoints = new HashMap<>();
+
+ private static final Set<Locations> IMPORTANT_WAYPOINTS = Sets.newHashSet(Locations.GOBLINQUEENSDEN, Locations.LOSTPRECURSORCITY, Locations.JUNGLETEMPLE, Locations.MINESOFDIVAN, Locations.KHAZADDM, Locations.FAIRYGROTTO);
+
+ @SubscribeEvent
+ public void onRenderLast(RenderWorldLastEvent event) {
+ GlStateManager.disableCull();
+ GlStateManager.disableDepth();
+ waypoints.forEach((text, pos) -> Utils.renderWaypointText(text, pos, event.partialTicks));
+ GlStateManager.enableCull();
+ GlStateManager.enableDepth();
+ }
+
+ @SubscribeEvent
+ public void onWorldChange(EntityJoinWorldEvent event) {
+ if (event.entity == Minecraft.getMinecraft().thePlayer) {
+ waypoints.clear();
+ }
+ }
+
+ @SubscribeEvent
+ public void onLocationChange(LocationChangeEvent event) {
+ if (!event.newLocation.getCategory().equals(LocationCategory.CRYSTALHOLLOWS)) {
+ waypoints.clear();
+ } else if (!waypoints.containsKey("Crystal Nucleus") && SkyblockHud.config.mining.autoWaypoint) {
+ waypoints.put("Crystal Nucleus", new BlockPos(512.5, 106.5, 512.5));
+ }
+ if (IMPORTANT_WAYPOINTS.contains(event.newLocation) && SkyblockHud.config.mining.autoWaypoint) {
+ if (!waypoints.containsKey(event.newLocation.getDisplayName())) {
+ waypoints.put(event.newLocation.getDisplayName(), Minecraft.getMinecraft().thePlayer.getPosition());
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void onChatMessage(ClientChatReceivedEvent event) {
+ if (event.type != 2 && LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.CRYSTALHOLLOWS)) {
+ Matcher matcher = LOCATION_MESSAGE_REGEX.matcher(event.message.getUnformattedText());
+ if (!matcher.find()) return;
+ ChatStyle style = new ChatStyle();
+ style.setParentStyle(event.message.getChatStyle());
+ ClickEvent.Action action = SkyblockHud.config.mining.chatWaypointMode == 0 ? ClickEvent.Action.RUN_COMMAND : ClickEvent.Action.SUGGEST_COMMAND;
+ style.setChatClickEvent(new ClickEvent(action, "/sbhpoints addat " + matcher.group(2) + " " + matcher.group(3) + " " + matcher.group(4) + " " + matcher.group(1)));
+ style.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText("Click to add waypoint!").setChatStyle(new ChatStyle().setBold(true))));
+ event.message.setChatStyle(style);
+ }
+ }
+
+ private static String copyWayPoint(String name) {
+ BlockPos pos = waypoints.get(name);
+ if (pos == null) {
+ return null;
+ }
+ return "{" + name + " : (" + pos.getX() + "/" + pos.getY() + "/" + pos.getZ() + ")}";
+ }
+
+ public static class WaypointCommand extends SimpleCommand {
+
+ public WaypointCommand() {
+ super(
+ "sbhpoints",
+ new ProcessCommandRunnable() {
+ @Override
+ public void processCommand(ICommandSender sender, String[] args) {
+ if (args.length == 0) return;
+ String subCommand = args[0].toLowerCase();
+ String name = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
+ switch (subCommand) {
+ case "add":
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.CRYSTALHOLLOWS)) {
+ if (!CrystalWaypoints.waypoints.containsKey(name) && name.length() > 1) {
+ CrystalWaypoints.waypoints.put(name, sender.getPosition().add(0.5, 0.5, 0.5));
+ } else if (name.length() < 2) {
+ sbhMessage(sender, "Waypoint name needs to be longer than 1");
+ } else {
+ sbhMessage(sender, "Waypoint already exists!");
+ }
+ }
+ break;
+ case "remove":
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.CRYSTALHOLLOWS)) {
+ if (CrystalWaypoints.waypoints.containsKey(name)) {
+ CrystalWaypoints.waypoints.remove(name);
+ } else {
+ sbhMessage(sender, "Waypoint doesnt exist!");
+ }
+ }
+ break;
+ case "move":
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.CRYSTALHOLLOWS)) {
+ if (CrystalWaypoints.waypoints.containsKey(name)) {
+ CrystalWaypoints.waypoints.put(name, sender.getPosition().add(0.5, 0.5, 0.5));
+ } else {
+ sbhMessage(sender, "Waypoint doesnt exist!");
+ }
+ }
+ break;
+ case "clear":
+ CrystalWaypoints.waypoints.clear();
+ break;
+ case "addat":
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.CRYSTALHOLLOWS)) {
+ name = String.join(" ", Arrays.copyOfRange(args, 4, args.length));
+ try {
+ if (!CrystalWaypoints.waypoints.containsKey(name)) {
+ CrystalWaypoints.waypoints.put(name, parseBlockPos(sender, args, 1, true));
+ } else if (name.length() < 2) {
+ sbhMessage(sender, "Waypoint name needs to be longer than 1");
+ } else {
+ sbhMessage(sender, "Waypoint already exists!");
+ }
+ } catch (Exception e) {
+ sbhMessage(sender, "Error!");
+ }
+ }
+ break;
+ case "copy":
+ String copyText = copyWayPoint(name);
+ if (copyText == null) {
+ sbhMessage(sender, "No waypoint with that name!");
+ break;
+ }
+ StringSelection clipboard = new StringSelection(copyText);
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboard, clipboard);
+ break;
+ case "send":
+ String sendText = copyWayPoint(name);
+ if (sendText == null) {
+ sbhMessage(sender, "No waypoint with that name!");
+ break;
+ }
+ Minecraft.getMinecraft().thePlayer.sendChatMessage(sendText);
+ break;
+ }
+ }
+ },
+ new TabCompleteRunnable() {
+ @Override
+ public List<String> tabComplete(ICommandSender sender, String[] args, BlockPos pos) {
+ if (args.length == 2 && Utils.equalsIgnoreCaseAnyOf(args[0], "remove", "copy", "move", "send")) {
+ return getListOfStringsMatchingLastWord(args, waypoints.keySet());
+ }
+ if (args.length == 1) {
+ return getListOfStringsMatchingLastWord(args, Lists.newArrayList("add", "clear", "remove", "copy", "addat", "move", "send"));
+ }
+ if (args.length > 1 && args[0].equalsIgnoreCase("addat")) {
+ return func_175771_a(args, 1, pos);
+ }
+ return null;
+ }
+ }
+ );
+ }
+
+ private static void sbhMessage(ICommandSender sender, String message) {
+ sender.addChatMessage(new ChatComponentText("[" + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "SkyBlockHud" + EnumChatFormatting.RESET + "] : " + EnumChatFormatting.GRAY + message));
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/CurrencyHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/CurrencyHandler.java
new file mode 100644
index 000000000..7bbfaa074
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/CurrencyHandler.java
@@ -0,0 +1,86 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarPostEvent;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class CurrencyHandler {
+
+ private static int bits = 0;
+ private static double coins = 0;
+
+ public static void setBits(int amount) {
+ bits = amount;
+ }
+
+ public static void setCoins(double amount) {
+ coins = amount;
+ }
+
+ public static int getBits() {
+ return bits;
+ }
+
+ public static double getCoins() {
+ return coins;
+ }
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (Utils.removeColor(event.formattedLine.toLowerCase().trim()).contains("purse:") || Utils.removeColor(event.formattedLine.toLowerCase().trim()).contains("piggy:")) {
+ CurrencyHandler.checkCoins(event.formattedLine);
+ }
+ if (Utils.removeColor(event.formattedLine.toLowerCase().trim()).contains("bits:") && !event.formattedLine.toLowerCase().contains("(")) {
+ CurrencyHandler.checkBits(event.formattedLine);
+ }
+ }
+
+ @SubscribeEvent
+ public void onSidebarPost(SidebarPostEvent event) {
+ if (!Arrays.toString(event.arrayScores).toLowerCase().contains("bits:")) {
+ CurrencyHandler.setBits(0);
+ }
+ }
+
+ public static String getCoinsFormatted() {
+ DecimalFormat formatter = new DecimalFormat("#,###.0", DecimalFormatSymbols.getInstance(Locale.CANADA));
+ String output = formatter.format(getCoins());
+ if (output.equals(".0")) output = "0.0"; else if (output.equals(",0")) output = "0,0";
+ return output;
+ }
+
+ public static String getBitsFormatted() {
+ DecimalFormat formatter = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.CANADA));
+ formatter.setRoundingMode(RoundingMode.FLOOR);
+ return getBits() > 999 ? formatter.format((double) getBits() / 1000) + "k" : String.valueOf(getBits());
+ }
+
+ public static void checkCoins(String formatedScoreboardLine) {
+ String purse = Utils.removeWhiteSpaceAndRemoveWord(Utils.removeColor(formatedScoreboardLine.toLowerCase().trim()), Utils.removeColor(formatedScoreboardLine.toLowerCase().trim()).contains("purse:") ? "purse:" : "piggy:").replace(",", "");
+ if (!purse.contains("(") && !purse.contains("+")) {
+ try {
+ double coins = Double.parseDouble(Pattern.compile("[^0-9.]").matcher(purse).replaceAll(""));
+ CurrencyHandler.setCoins(coins);
+ } catch (IllegalArgumentException ex) {
+ System.out.println("Failed to parse purse, please report to ThatGravyBoat. Purse Text: " + purse);
+ }
+ }
+ }
+
+ public static void checkBits(String formatedScoreboardLine) {
+ String bits = Utils.removeWhiteSpaceAndRemoveWord(Utils.removeColor(formatedScoreboardLine.toLowerCase().trim()), "bits:").replace(",", "");
+ try {
+ int bit = Integer.parseInt(Pattern.compile("[^0-9]").matcher(bits).replaceAll(""));
+ CurrencyHandler.setBits(bit);
+ } catch (IllegalArgumentException ex) {
+ System.out.println("Failed to parse bits, please report to ThatGravyBoat. Bits Text: " + bits);
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/HeldItemHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/HeldItemHandler.java
new file mode 100644
index 000000000..2feef3817
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/HeldItemHandler.java
@@ -0,0 +1,31 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.item.ItemStack;
+
+public class HeldItemHandler extends Gui {
+
+ private static final Pattern MANA_COST_REGEX = Pattern.compile("Mana Cost: \u00A73([0-9]+)");
+
+ public static boolean hasManaCost(ItemStack stack) {
+ if (stack == null) return false;
+ if (!stack.hasTagCompound()) return false;
+ if (!stack.getTagCompound().hasKey("display")) return false;
+ if (!stack.getTagCompound().getCompoundTag("display").hasKey("Lore")) return false;
+ String lore = stack.getTagCompound().getCompoundTag("display").getTagList("Lore", 8).toString();
+ return MANA_COST_REGEX.matcher(lore).find();
+ }
+
+ public static int getManaCost(ItemStack stack) {
+ String lore = stack.getTagCompound().getCompoundTag("display").getTagList("Lore", 8).toString();
+ Matcher matcher = MANA_COST_REGEX.matcher(lore);
+ if (matcher.find()) {
+ try {
+ return Integer.parseInt(matcher.group(1));
+ } catch (Exception ignored) {}
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/MapHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/MapHandler.java
new file mode 100644
index 000000000..937a5a498
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/MapHandler.java
@@ -0,0 +1,206 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import static com.thatgravyboat.skyblockhud.GuiTextures.mapOverlay;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.config.KeyBindings;
+import com.thatgravyboat.skyblockhud.config.SBHConfig;
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.handlers.mapicons.DwarvenIcons;
+import com.thatgravyboat.skyblockhud.handlers.mapicons.HubIcons;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.vecmath.Vector2f;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.settings.GameSettings;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.lwjgl.opengl.GL11;
+
+public class MapHandler {
+
+ public enum MapIconTypes {
+ SHOPS,
+ MISC,
+ NPC,
+ INFO,
+ QUEST
+ }
+
+ public static class MapIcon {
+
+ public Vector2f position;
+ public ResourceLocation icon;
+ public String tooltip;
+ public String command;
+ public MapIconTypes type;
+
+ public MapIcon(Vector2f pos, ResourceLocation icon, String tooltip, MapIconTypes type) {
+ this(pos, icon, tooltip, type, "");
+ }
+
+ public MapIcon(Vector2f pos, ResourceLocation icon, String tooltip, MapIconTypes type, String command) {
+ this.position = pos;
+ this.icon = icon;
+ this.tooltip = tooltip;
+ this.type = type;
+ this.command = command;
+ }
+
+ public boolean cantRender() {
+ SBHConfig.Map mapConfig = SkyblockHud.config.map;
+ if (mapConfig.showInfoIcons && type.equals(MapIconTypes.INFO)) return false; else if (mapConfig.showMiscIcons && type.equals(MapIconTypes.MISC)) return false; else if (mapConfig.showNpcIcons && type.equals(MapIconTypes.NPC)) return false; else if (mapConfig.showQuestIcons && type.equals(MapIconTypes.QUEST)) return false; else return (!mapConfig.showShopIcons || !type.equals(MapIconTypes.SHOPS));
+ }
+ }
+
+ public enum Maps {
+ HUB(0.5f, 494, 444, 294, 218, 294, 224, new ResourceLocation("skyblockhud", "maps/hub.png"), HubIcons.hubIcons),
+ MUSHROOM(1.0f, 318, 316, -84, 605, -84, 612, new ResourceLocation("skyblockhud", "maps/mushroom.png"), Collections.emptyList()),
+ SPIDERS(1.0f, 270, 238, 400, 362, 400, 364, new ResourceLocation("skyblockhud", "maps/spidersden.png"), Collections.emptyList()),
+ NETHER(0.5f, 257, 371, 436, 732, 433, 736, new ResourceLocation("skyblockhud", "maps/fort.png"), Collections.emptyList()),
+ BARN(1.5f, 135, 130, -82, 320, -81, 318, new ResourceLocation("skyblockhud", "maps/barn.png"), Collections.emptyList()),
+ DWARVEN(0.5f, 409, 461, 206, 160, 202, 166, new ResourceLocation("skyblockhud", "maps/dwarven.png"), DwarvenIcons.dwarvenIcons),
+ CRYSTAL(0.5f, 624, 624, -202, -215.7, -202, -212, new ResourceLocation("skyblockhud", "maps/crystal.png"), Collections.emptyList()),
+ PARK(1f, 211, 230, 480, 133, 478, 134, new ResourceLocation("skyblockhud", "maps/park.png"), Collections.emptyList());
+
+ public float scaleFactor;
+ public int width;
+ public int height;
+ public double xMiniOffset;
+ public double yMiniOffset;
+ public double xOffset;
+ public double yOffset;
+ public ResourceLocation mapTexture;
+ public List<MapIcon> icons;
+
+ Maps(float scaleFactor, int width, int height, double xMiniOffset, double yMiniOffset, double xOffset, double yOffset, ResourceLocation mapTexture, List<MapIcon> icons) {
+ this.scaleFactor = scaleFactor;
+ this.width = width;
+ this.height = height;
+ this.xMiniOffset = xMiniOffset;
+ this.yMiniOffset = yMiniOffset;
+ this.xOffset = xOffset;
+ this.yOffset = yOffset;
+ this.mapTexture = mapTexture;
+ this.icons = icons;
+ }
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard(), SkyblockHud.config.map.showMiniMap)) {
+ Minecraft mc = Minecraft.getMinecraft();
+ if (mc.currentScreen instanceof MapScreen) return;
+ if (LocationHandler.getCurrentLocation().getCategory().getMap() == null) return;
+ if (mc.thePlayer != null) {
+ MapHandler.Maps map = LocationHandler.getCurrentLocation().getCategory().getMap();
+ mc.renderEngine.bindTexture(mapOverlay);
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ Position pos = SkyblockHud.config.map.miniMapPosition;
+ Gui.drawModalRectWithCustomSizedTexture(pos.getAbsX(event.resolution, 72), pos.getAbsY(event.resolution, 72), 72, 0, 72, 72, 256, 256);
+ mc.renderEngine.bindTexture(map.mapTexture);
+
+ double x = mc.thePlayer.getPosition().getX() + map.xMiniOffset;
+ double z = mc.thePlayer.getPosition().getZ() + map.yMiniOffset;
+ float u = (float) ((x / (map.width / 256f)) - 33f);
+ float v = (float) ((z / (map.height / 256f)) - 28f);
+
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
+
+ Gui.drawModalRectWithCustomSizedTexture(pos.getAbsX(event.resolution, 72) + 4, pos.getAbsY(event.resolution, 72) + 2, u, v, 64, 64, 256, 256);
+
+ if (SkyblockHud.config.map.showPlayerLocation) {
+ mc.fontRendererObj.drawString("\u2022", pos.getAbsX(event.resolution, 72) + 36, pos.getAbsY(event.resolution, 72) + 34, 0xff0000, false);
+ }
+
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ mc.renderEngine.bindTexture(mapOverlay);
+ Gui.drawModalRectWithCustomSizedTexture(pos.getAbsX(event.resolution, 72), pos.getAbsY(event.resolution, 72), 0, 0, 72, 72, 256, 256);
+ String keyCode = GameSettings.getKeyDisplayString(KeyBindings.map.getKeyCode());
+ Utils.drawStringCenteredScaled(keyCode, mc.fontRendererObj, pos.getAbsX(event.resolution, 64) + (pos.rightAligned(event.resolution, 72) ? 50 : 58), pos.getAbsY(event.resolution, 72) + 66, false, 6, 0xFFFFFF);
+ BlockPos playerPos = mc.thePlayer.getPosition();
+ String position = String.format("%d/%d/%d", playerPos.getX(), playerPos.getY(), playerPos.getZ());
+ Utils.drawStringCenteredScaled(position, mc.fontRendererObj, pos.getAbsX(event.resolution, 64) + (pos.rightAligned(event.resolution, 72) ? 21 : 29), pos.getAbsY(event.resolution, 72) + 66, false, 36, 0xFFFFFF);
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void clientTick(TickEvent.ClientTickEvent event) {
+ if (KeyBindings.map.isPressed() && LocationHandler.getCurrentLocation().getCategory().getMap() != null && SkyblockHud.hasSkyblockScoreboard()) SkyblockHud.screenToOpen = new MapScreen();
+ }
+
+ public static class MapScreen extends GuiScreen {
+
+ public MapHandler.Maps map = LocationHandler.getCurrentLocation().getCategory().getMap();
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ this.drawWorldBackground(0);
+ this.mc.renderEngine.bindTexture(map.mapTexture);
+ float mapX = (width / 2f) - ((map.width / 2f) * map.scaleFactor);
+ float mapY = (height / 2f) - ((map.height / 2f) * map.scaleFactor);
+ Gui.drawModalRectWithCustomSizedTexture((int) mapX, (int) mapY, 0, 0, (int) (map.width * map.scaleFactor), (int) (map.height * map.scaleFactor), (int) (map.width * map.scaleFactor), (int) (map.height * map.scaleFactor));
+ drawIcons((int) mapX, (int) mapY);
+ if (this.mc.thePlayer != null && SkyblockHud.config.map.showPlayerLocation) {
+ double x = this.mc.thePlayer.getPosition().getX() + map.xOffset;
+ double z = this.mc.thePlayer.getPosition().getZ() + map.yOffset;
+ fontRendererObj.drawString("\u2022", (int) (x * map.scaleFactor + mapX), (int) (z * map.scaleFactor + mapY), 0xff0000);
+ }
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ onTooltip(mouseX, mouseY, (int) mapX, (int) mapY);
+ }
+
+ public void drawIcons(int startX, int startY) {
+ if (map.icons == null) return;
+ for (MapIcon icon : map.icons) {
+ if (icon.cantRender()) continue;
+ GlStateManager.enableBlend();
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ this.mc.renderEngine.bindTexture(icon.icon);
+ double x = ((icon.position.x + map.xOffset) * map.scaleFactor) + startX - 4;
+ double y = ((icon.position.y + map.yOffset) * map.scaleFactor) + startY - 4;
+ Gui.drawModalRectWithCustomSizedTexture((int) x, (int) y, 0, 0, 8, 8, 8, 8);
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+
+ public void onTooltip(int mouseX, int mouseY, int startX, int startY) {
+ if (map.icons == null) return;
+ for (MapIcon icon : map.icons) {
+ if (icon.cantRender()) continue;
+ if (Utils.inRangeInclusive(mouseX, (int) ((icon.position.x + map.xOffset) * map.scaleFactor) + startX - 4, (int) ((icon.position.x + map.xOffset) * map.scaleFactor) + startX + 4) && Utils.inRangeInclusive(mouseY, (int) ((icon.position.y + map.yOffset) * map.scaleFactor) + startY - 4, (int) ((icon.position.y + map.yOffset) * map.scaleFactor) + startY + 4)) {
+ drawHoveringText(Arrays.asList(icon.tooltip.split("\n")), mouseX, mouseY);
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ int mapX = (int) ((width / 2f) - ((map.width / 2f) * map.scaleFactor));
+ int mapY = (int) ((height / 2f) - ((map.height / 2f) * map.scaleFactor));
+ for (MapIcon icon : map.icons) {
+ if (icon.cantRender()) continue;
+ if (Utils.inRangeInclusive(mouseX, (int) ((icon.position.x + map.xOffset) * map.scaleFactor) + mapX - 4, (int) ((icon.position.x + map.xOffset) * map.scaleFactor) + mapX + 4) && Utils.inRangeInclusive(mouseY, (int) ((icon.position.y + map.yOffset) * map.scaleFactor) + mapY - 4, (int) ((icon.position.y + map.yOffset) * map.scaleFactor) + mapY + 4)) {
+ if (!icon.command.isEmpty()) {
+ this.mc.thePlayer.sendChatMessage("/" + icon.command);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/NpcDialogue.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/NpcDialogue.java
new file mode 100644
index 000000000..9a7679fde
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/NpcDialogue.java
@@ -0,0 +1,131 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.textures.Textures;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.resources.IResourceManager;
+import net.minecraft.client.resources.IResourceManagerReloadListener;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+public class NpcDialogue implements IResourceManagerReloadListener {
+
+ public static final Pattern NPC_DIALOGUE_REGEX = Pattern.compile("\\[NPC] (.*): (.*)");
+
+ private static final Gson gson = new GsonBuilder().create();
+ private static final Map<String, ResourceLocation> NPCS = new HashMap<>();
+
+ private static boolean showDialogue = false;
+ private static int ticks = 0;
+
+ private static final Queue<Dialogue> DIALOGUE = new ArrayDeque<>();
+ private static Dialogue currentDialogue = null;
+
+ @SubscribeEvent
+ public void onTick(TickEvent.ClientTickEvent event) {
+ if (event.phase.equals(TickEvent.Phase.START) || SkyblockHud.config.misc.hideDialogueBox) return;
+ if (showDialogue) ticks++; else ticks = 0;
+
+ if (showDialogue && ticks % 60 == 0) {
+ currentDialogue = DIALOGUE.poll();
+
+ if (currentDialogue == null) {
+ showDialogue = false;
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public void onChat(ClientChatReceivedEvent event) {
+ if (event.type != 2 && !SkyblockHud.config.misc.hideDialogueBox) {
+ String message = Utils.removeColor(event.message.getUnformattedText());
+ if (message.toLowerCase(Locale.ENGLISH).startsWith("[npc]")) {
+ Matcher matcher = NPC_DIALOGUE_REGEX.matcher(message);
+ if (matcher.find()) {
+ showDialogue = true;
+ event.setCanceled(true);
+
+ Dialogue dialogue = new Dialogue(matcher.group(1), matcher.group(2));
+ if (currentDialogue == null) currentDialogue = dialogue; else DIALOGUE.add(dialogue);
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard(), showDialogue, !SkyblockHud.config.misc.hideDialogueBox)) {
+ Minecraft mc = Minecraft.getMinecraft();
+ mc.renderEngine.bindTexture(Textures.texture.dialogue);
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+
+ int x = SkyblockHud.config.misc.dialoguePos.getAbsX(event.resolution, 182) - 91;
+ int y = SkyblockHud.config.misc.dialoguePos.getAbsY(event.resolution, 68);
+
+ Gui.drawModalRectWithCustomSizedTexture(x, y, 0, 0, 182, 68, 256, 256);
+
+ String npcID = currentDialogue.name.toLowerCase(Locale.ENGLISH).replace(" ", "_");
+
+ if (NPCS.containsKey(npcID)) {
+ mc.renderEngine.bindTexture(NPCS.get(npcID));
+ Gui.drawModalRectWithCustomSizedTexture(x + 4, y + 4, 0, 0, 32, 60, 128, 128);
+ }
+
+ FontRenderer font = mc.fontRendererObj;
+
+ font.drawString(currentDialogue.name, x + 40, y + 10, 0xffffff);
+
+ for (int i = 0; i < currentDialogue.dialogue.size(); i++) {
+ Utils.drawStringScaled(currentDialogue.dialogue.get(i), font, x + 40, y + 10 + font.FONT_HEIGHT + 6 + (i * font.FONT_HEIGHT + 3), false, 0xffffff, 0.75f);
+ }
+ }
+ }
+
+ @Override
+ public void onResourceManagerReload(IResourceManager resourceManager) {
+ NPCS.clear();
+ try {
+ ResourceLocation npcs = new ResourceLocation("skyblockhud:data/npc_textures.json");
+ InputStream is = resourceManager.getResource(npcs).getInputStream();
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ for (JsonElement npc : gson.fromJson(reader, JsonObject.class).getAsJsonArray("npcs")) {
+ JsonObject npcObject = npc.getAsJsonObject();
+ String npcName = npcObject.get("name").getAsString();
+ ResourceLocation rl = new ResourceLocation(npcObject.get("texture").getAsString());
+ NPCS.put(npcName.toLowerCase(Locale.ENGLISH).replace(" ", "_"), rl);
+ }
+ }
+ } catch (Exception ignored) {}
+ }
+
+ static class Dialogue {
+
+ public List<String> dialogue;
+ public String name;
+
+ public Dialogue(String name, String dialogue) {
+ this.dialogue = Minecraft.getMinecraft().fontRendererObj.listFormattedStringToWidth(dialogue, 160);
+ this.name = name;
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/SlayerHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/SlayerHandler.java
new file mode 100644
index 000000000..4a1f4e698
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/SlayerHandler.java
@@ -0,0 +1,130 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarPostEvent;
+import com.thatgravyboat.skyblockhud.api.events.SkyBlockEntityKilled;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class SlayerHandler {
+
+ private static final Pattern KILLS_REGEX = Pattern.compile("(\\d+)/(\\d+) kills?");
+ private static final Pattern SLAYER_PATTERN = Pattern.compile("Talk to Maddox to claim your ([A-Za-z]+) Slayer XP!");
+
+ public enum slayerTypes {
+ ZOMBIE(34, "Revenant Horror"),
+ WOLF(42, "Sven Packmaster"),
+ SPIDER(50, "Tarantula Broodfather"),
+ VOIDGLOOMSERAPH(58, "Voidgloom Seraph"),
+ NONE(0, "");
+
+ private final String displayName;
+ private final int x;
+
+ slayerTypes(int x, String displayName) {
+ this.displayName = displayName;
+ this.x = x;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public int getX() {
+ return x;
+ }
+ }
+
+ public static slayerTypes currentSlayer = slayerTypes.NONE;
+ public static int slayerTier = 0;
+ public static boolean isDoingSlayer = false;
+ public static int progress = 0;
+ public static int maxKills = 0;
+ public static boolean bossSlain = false;
+ public static boolean isKillingBoss = false;
+
+ public static void clearSlayer() {
+ currentSlayer = slayerTypes.NONE;
+ isDoingSlayer = false;
+ progress = 0;
+ maxKills = 0;
+ bossSlain = false;
+ isKillingBoss = false;
+ }
+
+ @SubscribeEvent
+ public void onSidebarPost(SidebarPostEvent event) {
+ String arrayString = Arrays.toString(event.arrayScores);
+ isDoingSlayer = Arrays.toString(event.arrayScores).contains("Slayer Quest");
+ if (isDoingSlayer && (currentSlayer.equals(slayerTypes.NONE) || !arrayString.replace(" ", "").contains(currentSlayer.getDisplayName().replace(" ", "") + Utils.intToRomanNumeral(slayerTier)))) {
+ for (int i = 0; i < event.scores.size(); i++) {
+ String line = event.scores.get(i);
+ if (line.contains("Slayer Quest") && event.scores.size() > 3) {
+ String slayer = event.scores.get(i - 1).toLowerCase();
+ SlayerHandler.slayerTypes selectedSlayer = SlayerHandler.slayerTypes.NONE;
+ for (slayerTypes types : slayerTypes.values()) {
+ if (slayer.contains(types.displayName.toLowerCase(Locale.ENGLISH))) {
+ selectedSlayer = types;
+ break;
+ }
+ }
+ SlayerHandler.currentSlayer = selectedSlayer;
+ SlayerHandler.slayerTier = Utils.whatRomanNumeral(slayer.replace(selectedSlayer.getDisplayName().toLowerCase(), "").replace(" ", ""));
+ break;
+ }
+ }
+ }
+
+ if (!isDoingSlayer) {
+ clearSlayer();
+ }
+ }
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (!isDoingSlayer && event.formattedLine.equals("Slayer Quest")) isDoingSlayer = true;
+
+ if (isDoingSlayer) {
+ String line = event.formattedLine.toLowerCase();
+ Matcher killMatcher = KILLS_REGEX.matcher(line);
+
+ if (killMatcher.find()) {
+ SlayerHandler.bossSlain = false;
+ SlayerHandler.isKillingBoss = false;
+ try {
+ progress = Integer.parseInt(killMatcher.group(1));
+ } catch (Exception ignored) {}
+ try {
+ maxKills = Integer.parseInt(killMatcher.group(2));
+ } catch (Exception ignored) {}
+ } else if (line.contains("slay the boss")) {
+ SlayerHandler.bossSlain = false;
+ SlayerHandler.isKillingBoss = true;
+ SlayerHandler.maxKills = 0;
+ SlayerHandler.progress = 0;
+ } else if (line.contains("boss slain")) {
+ SlayerHandler.isKillingBoss = false;
+ SlayerHandler.maxKills = 0;
+ SlayerHandler.progress = 0;
+ SlayerHandler.bossSlain = true;
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onChatMessage(ClientChatReceivedEvent event) {
+ if (event.type != 2) {
+ Matcher slayerMatcher = SLAYER_PATTERN.matcher(Utils.removeColor(event.message.getUnformattedText()));
+ if (slayerMatcher.find()) {
+ MinecraftForge.EVENT_BUS.post(new SkyBlockEntityKilled(slayerMatcher.group(1).toUpperCase(Locale.ENGLISH) + "_SLAYER", null));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/TimeHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/TimeHandler.java
new file mode 100644
index 000000000..f1aa22295
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/TimeHandler.java
@@ -0,0 +1,28 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import org.apache.logging.log4j.LogManager;
+
+public class TimeHandler {
+
+ public static long time;
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (Pattern.matches("([0-9]*):([0-9]*)(pm|am)", event.formattedLine.toLowerCase().trim())) {
+ boolean isPm = event.formattedLine.toLowerCase().trim().endsWith("pm");
+ SimpleDateFormat parseFormat = new SimpleDateFormat("hh:mm a", Locale.CANADA);
+ String currentTimeString = event.formattedLine.replace(" ", "").replace(isPm ? "pm" : "am", isPm ? " pm" : " am");
+ try {
+ time = (parseFormat.parse(currentTimeString).getTime() - parseFormat.parse("00:00 am").getTime()) / 1000L;
+ } catch (ParseException ignored) {
+ LogManager.getLogger().warn("timeformat error: " + currentTimeString);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/WarpHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/WarpHandler.java
new file mode 100644
index 000000000..77a4199ad
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/WarpHandler.java
@@ -0,0 +1,179 @@
+package com.thatgravyboat.skyblockhud.handlers;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.gson.*;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.events.ProfileJoinedEvent;
+import com.thatgravyboat.skyblockhud.api.events.ProfileSwitchedEvent;
+import com.thatgravyboat.skyblockhud.mixins.GuiChestAccessor;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.client.event.GuiOpenEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class WarpHandler {
+
+ private static String profile = null;
+ private static File warpConfig;
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
+ private static final SetMultimap<String, Warp> PLAYER_WARPS = HashMultimap.create();
+
+ public static Collection<Warp> getWarps() {
+ return PLAYER_WARPS.get(profile);
+ }
+
+ @SubscribeEvent
+ public void profileChange(ProfileSwitchedEvent event) {
+ if (profile != null && !profile.equals(event.profile)) {
+ load();
+ }
+ profile = event.profile;
+ }
+
+ @SubscribeEvent
+ public void profileJoined(ProfileJoinedEvent event) {
+ if (profile != null && !profile.equals(event.profile)) {
+ load();
+ }
+ profile = event.profile;
+ }
+
+ @SubscribeEvent
+ public void onGuiClosed(GuiOpenEvent event) {
+ boolean changed = false;
+ GuiScreen currentScreen = Minecraft.getMinecraft().currentScreen;
+ if (currentScreen instanceof GuiChest) {
+ GuiChestAccessor accessor = (GuiChestAccessor) currentScreen;
+ if (accessor.getLowerChestInventory().getDisplayName().getUnformattedText().contains("Fast Travel")) {
+ for (int i = 9; i < Math.min(36, accessor.getLowerChestInventory().getSizeInventory()); i++) {
+ ItemStack stack = accessor.getLowerChestInventory().getStackInSlot(i);
+ if (stack != null && stack.getItem().equals(Items.skull) && stack.getTagCompound().hasKey("display")) {
+ NBTTagList lore = stack.getTagCompound().getCompoundTag("display").getTagList("Lore", 8);
+
+ String warpLine = Utils.removeColor(lore.getStringTagAt(0)).trim();
+
+ if (warpLine.equals("Unknown island!")) continue;
+
+ String disabledLine = Utils.removeColor(lore.getStringTagAt(lore.tagCount() - 1)).trim();
+
+ Warp warp = Warp.fromId(warpLine.replace("/warp", "").trim());
+
+ if (warp != null && !disabledLine.equals("Warp not unlocked!")) {
+ if (PLAYER_WARPS.put(profile, warp)) {
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (changed) save();
+ }
+
+ public static void save() {
+ JsonObject json = new JsonObject();
+ JsonArray array = new JsonArray();
+ PLAYER_WARPS
+ .asMap()
+ .forEach((profile, warps) -> {
+ JsonObject profileObject = new JsonObject();
+ profileObject.addProperty("profile", profile);
+ JsonArray warpArray = new JsonArray();
+ warps.forEach(warp -> warpArray.add(new JsonPrimitive(warp.name())));
+ profileObject.add("warps", warpArray);
+ array.add(profileObject);
+ });
+ json.add("profileWarps", array);
+
+ warpConfig = new File(SkyblockHud.configDirectory, "sbh-warps.json");
+
+ try {
+ warpConfig.createNewFile();
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(warpConfig), StandardCharsets.UTF_8))) {
+ writer.write(GSON.toJson(json));
+ }
+ } catch (IOException ignored) {}
+ }
+
+ public static boolean load() {
+ warpConfig = new File(SkyblockHud.configDirectory, "sbh-warps.json");
+
+ try {
+ if (warpConfig.createNewFile()) return true;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(warpConfig), StandardCharsets.UTF_8))) {
+ JsonObject json = GSON.fromJson(reader, JsonObject.class);
+ json
+ .get("profileWarps")
+ .getAsJsonArray()
+ .forEach(jsonElement -> {
+ JsonObject profileObject = jsonElement.getAsJsonObject();
+ List<Warp> warps = new ArrayList<>();
+ profileObject
+ .get("warps")
+ .getAsJsonArray()
+ .forEach(warpId -> {
+ Warp warp = Warp.safeValueOf(warpId.getAsString());
+ if (warp != null) warps.add(warp);
+ });
+ PLAYER_WARPS.putAll(profileObject.get("profile").getAsString(), warps);
+ });
+ }
+ } catch (Exception ignored) {}
+ return false;
+ }
+
+ public enum Warp {
+ HUB("hub"),
+ PRIVATE("home"),
+ SPIDERSDEN("spider"),
+ BLAZINGFORTRESS("nether"),
+ THEEND("end"),
+ THEPARK("park"),
+ GOLDMINE("gold"),
+ DEEPCAVERNS("deep"),
+ DWARVENMINES("mines"),
+ THEBARN("barn"),
+ MUSHROOMDESERT("desert"),
+ THECASTLE("castle"),
+ SIRIUSSHACK("da"),
+ GRAVEYARDCAVES("crypt"),
+ SPIDERSNEST("nest"),
+ MAGMACUBE("magma"),
+ DRAGONNEST("drag"),
+ JUNGLE("jungle"),
+ HOWLINGCAVE("howl"),
+ DUNGEONHUB("dungeon_hub");
+
+ public String warpId;
+
+ Warp(String warpId) {
+ this.warpId = warpId;
+ }
+
+ public static Warp fromId(String id) {
+ for (Warp value : Warp.values()) {
+ if (value.warpId.equals(id)) return value;
+ }
+ return null;
+ }
+
+ public static Warp safeValueOf(String value) {
+ try {
+ return Warp.valueOf(value);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/DwarvenIcons.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/DwarvenIcons.java
new file mode 100644
index 000000000..211ed4b68
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/DwarvenIcons.java
@@ -0,0 +1,39 @@
+package com.thatgravyboat.skyblockhud.handlers.mapicons;
+
+import com.thatgravyboat.skyblockhud.handlers.MapHandler;
+import com.thatgravyboat.skyblockhud.utils.ComponentBuilder;
+import java.util.ArrayList;
+import java.util.List;
+import javax.vecmath.Vector2f;
+import net.minecraft.util.ResourceLocation;
+
+public class DwarvenIcons {
+
+ public static List<MapHandler.MapIcon> dwarvenIcons = new ArrayList<>();
+
+ static {
+ setupNpcIcons();
+ setupMiscIcons();
+ setupInfoIcons();
+ setupShopIcons();
+ setupQuestIcons();
+ }
+
+ private static void setupNpcIcons() {
+ dwarvenIcons.add(new MapHandler.MapIcon(new Vector2f(181, 135), new ResourceLocation("skyblockhud", "maps/icons/puzzle.png"), new ComponentBuilder().nl("Puzzler", 'a', 'l').nl("Description", 'l').nl("The Puzzler gives you a small puzzle each day to solve and").nl("gives you 1000 mithril powder.").build(), MapHandler.MapIconTypes.NPC));
+ }
+
+ private static void setupMiscIcons() {}
+
+ private static void setupInfoIcons() {
+ dwarvenIcons.add(new MapHandler.MapIcon(new Vector2f(129, 187), new ResourceLocation("skyblockhud", "maps/icons/crown.png"), new ComponentBuilder().nl("King", 'a', 'l').nl("Description", 'l').nl("The King allows you to first start commissions and if you click").nl("each king which change every skyblock day you will get").nl("the King Talisman.").nl().apd("Click to open HOTM", '6', 'l').build(), MapHandler.MapIconTypes.INFO, "hotm"));
+ }
+
+ private static void setupShopIcons() {
+ dwarvenIcons.add(new MapHandler.MapIcon(new Vector2f(4, 8), new ResourceLocation("skyblockhud", "maps/icons/blacksmith.png"), new ComponentBuilder().nl("Forge", 'a', 'l').nl("Description", 'l').nl("The Forge is where you can go craft special items").nl("and fuel your drill.").nl("NPCS", 'c', 'l').nl(" Forger - Allows you to forge special items").nl(" Jotraeline Greatforge - Allows you to refuel your drill.").nl().apd("Click to warp", '6', 'l').build(), MapHandler.MapIconTypes.SHOPS, "warpforge"));
+ }
+
+ private static void setupQuestIcons() {
+ dwarvenIcons.add(new MapHandler.MapIcon(new Vector2f(67, 204), new ResourceLocation("skyblockhud", "maps/icons/special.png"), new ComponentBuilder().nl("Royal Resident", 'a', 'l').nl("The Royal Resident is a quest where you right").nl("click them for a bit to obtain and if you continue").nl("to right click them for about 7 hours it will give").apd("the achievement Royal Conversation.").build(), MapHandler.MapIconTypes.QUEST));
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/HubIcons.java b/src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/HubIcons.java
new file mode 100644
index 000000000..7de7d92cf
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/handlers/mapicons/HubIcons.java
@@ -0,0 +1,55 @@
+package com.thatgravyboat.skyblockhud.handlers.mapicons;
+
+import com.thatgravyboat.skyblockhud.handlers.MapHandler;
+import com.thatgravyboat.skyblockhud.utils.ComponentBuilder;
+import java.util.ArrayList;
+import java.util.List;
+import javax.vecmath.Vector2f;
+import net.minecraft.util.ResourceLocation;
+
+public class HubIcons {
+
+ public static List<MapHandler.MapIcon> hubIcons = new ArrayList<>();
+
+ static {
+ setupNpcIcons();
+ setupMiscIcons();
+ setupInfoIcons();
+ setupShopIcons();
+ setupQuestIcons();
+ }
+
+ private static void setupNpcIcons() {
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-2, -34), new ResourceLocation("skyblockhud", "maps/icons/special.png"), new ComponentBuilder().nl("Event Hut", 'a', 'l').nl("Description", 'l').nl("The Event Hut is where special event npcs").nl("are during some events.").nl("NPC'S", 'c', 'l').nl(" Baker - During New Years").nl(" Jerry - While Winter Island is opened").nl(" Fear Mongerer - During Spooky Festival").apd(" Oringo - During Traveling Zoo").build(), MapHandler.MapIconTypes.NPC));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(135, 142), new ResourceLocation("skyblockhud", "maps/icons/fairy.png"), new ComponentBuilder().nl("Fairy", 'a', 'l').nl("Description", 'l').nl("The Fairy is where you go when you find fairy souls").apd("to trade them in to get permanent stat upgrades.").build(), MapHandler.MapIconTypes.NPC));
+ }
+
+ private static void setupShopIcons() {
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-50, -22), new ResourceLocation("skyblockhud", "maps/icons/building.png"), new ComponentBuilder().nl("Builder's House", 'a', 'l').nl("NPCS", 'c', 'l').nl(" Wool Weaver").nl(" Builder").apd(" Mad Redstone Engineer").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-78, -46), new ResourceLocation("skyblockhud", "maps/icons/bar.png"), new ComponentBuilder().nl("Tavern", 'a', 'l').nl("NPCS", 'c', 'l').nl(" Bartender").nl(" Maddox the slayer").nl("Description", 'l').nl("The Tavern is where maddox the slayer is located you can").nl("start slayer quests with them to unlock").apd("new items the more slayer bosses you kill.").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(36, -82), new ResourceLocation("skyblockhud", "maps/icons/vet.png"), new ComponentBuilder().nl("Vet", 'a', 'l').nl("NPCS", 'c', 'l').nl(" Bea").nl(" Zog").nl(" Kat").nl(" George").nl("Description", 'l').nl("The Vet is where you go to upgrade your pet").nl("at Kat or to buy pet upgrade items from Zog").nl("or trade in your pet at George and if you're").apd("a new player you can buy a bee pet from Bea.").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(58, -73), new ResourceLocation("skyblockhud", "maps/icons/fishing_merchant.png"), new ComponentBuilder().nl("Fishing Merchant", 'a', 'l').nl("Description", 'l').nl("The Fishing Merchant allows you to buy").nl("fishing related items and he has his friend").nl("joe whose in the house hes setup").apd("in front of who sells sponges.").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(46, -53), new ResourceLocation("skyblockhud", "maps/icons/witch.png"), new ComponentBuilder().nl("Alchemist", 'a', 'l').nl("Description", 'l').nl("The Alchemist allows you to buy").apd("potion making related items").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-4, -128), new ResourceLocation("skyblockhud", "maps/icons/metal_merchants.png"), new ComponentBuilder().nl("Blacksmith Merchants", 'a', 'l').nl("Merchants", 'c', 'l').nl(" Weaponsmith - Weapon Related Items").nl(" Armorsmith - Armor Related Items").apd(" Mine Merchant - Mining Related Items").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-30, -120), new ResourceLocation("skyblockhud", "maps/icons/blacksmith.png"), new ComponentBuilder().nl("Blacksmith", 'a', 'l').nl("NPCS", 'c', 'l').nl(" Blacksmith").nl(" Dusk").nl(" Smithmonger").nl("Description", 'l').nl("The Blacksmith lets you reforge your items").nl("while the Smithmonger allows you to buy reforge stones").apd("and Dusk allows you to combine and apply runes.").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(124, 180), new ResourceLocation("skyblockhud", "maps/icons/dark_bar.png"), new ComponentBuilder().nl("Dark Bar", 'a', 'l').nl("NPCS", 'c', 'l').nl(" Shifty").nl(" Lucius").nl("Description", 'l').nl("The Dark Bar is where you can buy special").nl("brews from Shifty and you can buy special").nl("items from Lucius after buying a certain").apd("amount of items from the Dark Auction.").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(92, 185), new ResourceLocation("skyblockhud", "maps/icons/dark_ah.png"), new ComponentBuilder().nl("Dark Auction", 'a', 'l').nl("Description", 'l').nl("The Dark Auction allows you to buy").nl("super special items from Sirius the").apd("auctioneer in a special auction.").build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-245, 52), new ResourceLocation("skyblockhud", "maps/icons/scroll.png"), new ComponentBuilder().nl("Lonely Philosopher", 'a', 'l').nl("Shop", '6', 'l').nl(" Travel Scroll to Hub Castle").nl().nl(" Cost").nl(" 150,000 Coins", '6').nl().apd(" Requires ").apd("MVP", 'b').apd("+", 'c').build(), MapHandler.MapIconTypes.SHOPS));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(24, -38), new ResourceLocation("skyblockhud", "maps/icons/tux.png"), new ComponentBuilder().nl("Fashion Shop", 'a', 'l').nl("NPCS", 'c', 'l').nl(" Wool Weaver").nl(" Builder").apd(" Mad Redstone Engineer").build(), MapHandler.MapIconTypes.SHOPS));
+ }
+
+ private static void setupMiscIcons() {
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-24, -53), new ResourceLocation("skyblockhud", "maps/icons/bank.png"), new ComponentBuilder().nl("Bank", 'a', 'l').nl("Description", 'l').nl("The Bank is where you can store your money on skyblock").apd("you can also store some items in the vault.").build(), MapHandler.MapIconTypes.MISC));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-26, -80), new ResourceLocation("skyblockhud", "maps/icons/ah.png"), new ComponentBuilder().nl("Auction House", 'a', 'l').nl("Description", 'l').nl("The Auction House is where you can auction off your").apd("precious items in skyblock to make a profit.").build(), MapHandler.MapIconTypes.MISC));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-38, -66), new ResourceLocation("skyblockhud", "maps/icons/bazaar.png"), new ComponentBuilder().nl("Bazaar", 'a', 'l').nl("Description", 'l').nl("The Bazaar is where you can sell specific items").nl("on a sort of stock market and request and").apd("sell items at a specific price.").build(), MapHandler.MapIconTypes.MISC));
+ }
+
+ private static void setupInfoIcons() {
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(8, -95), new ResourceLocation("skyblockhud", "maps/icons/community.png"), new ComponentBuilder().nl("Community Center", 'a', 'l').nl("Description", 'l').nl("The Community Center is where you can vote").nl("for your favorite election candidate,").nl("access the community shop, upgrade your").apd("account, and help with city projects.").build(), MapHandler.MapIconTypes.INFO));
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(150, 45), new ResourceLocation("skyblockhud", "maps/icons/fishing.png"), new ComponentBuilder().nl("Fisherman's Hut", 'a', 'l').nl("Description", 'l').nl("This is a spot where people regularly").nl("do their fishing, this is one").apd("of many spots.").build(), MapHandler.MapIconTypes.INFO));
+ }
+
+ private static void setupQuestIcons() {
+ hubIcons.add(new MapHandler.MapIcon(new Vector2f(-8, -10), new ResourceLocation("skyblockhud", "maps/icons/painter.png"), new ComponentBuilder().nl("Marco", 'a', 'l').nl("Description", 'l').nl("Marco is an NPC that has no other uses").nl("besides giving you a spray can for").apd("completing a quest.").build(), MapHandler.MapIconTypes.QUEST));
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/EndIslandHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/EndIslandHandler.java
new file mode 100644
index 000000000..505db19d2
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/EndIslandHandler.java
@@ -0,0 +1,53 @@
+package com.thatgravyboat.skyblockhud.location;
+
+public class EndIslandHandler {
+
+ public enum dragonTypes {
+ PROTECTOR("Protector Dragon", 9000000),
+ OLD("Old Dragon", 15000000),
+ WISE("Wise Dragon", 9000000),
+ UNSTABLE("Unstable Dragon", 9000000),
+ YOUNG("Young Dragon", 7500000),
+ STRONG("Strong Dragon", 9000000),
+ SUPERIOR("Superior Dragon", 12000000),
+ NODRAGON("", 0);
+
+ private final String displayName;
+ private final int maxHealth;
+
+ dragonTypes(String displayName, int maxHealth) {
+ this.displayName = displayName;
+ this.maxHealth = maxHealth;
+ }
+
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ public int getMaxHealth() {
+ return this.maxHealth;
+ }
+
+ public static dragonTypes findDragon(String input) {
+ if (input.contains(" ")) {
+ try {
+ return dragonTypes.valueOf(input.toLowerCase().replace("dragon", "").replace(" ", "").toUpperCase());
+ } catch (IllegalArgumentException ignored) {
+ return NODRAGON;
+ }
+ } else {
+ try {
+ return dragonTypes.valueOf(input);
+ } catch (IllegalArgumentException ignored) {
+ return NODRAGON;
+ }
+ }
+ }
+ }
+
+ private static dragonTypes currentDragon = dragonTypes.NODRAGON;
+
+ public static void setCurrentDragon(dragonTypes dragon) {
+ currentDragon = dragon;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/FarmHouseHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/FarmHouseHandler.java
new file mode 100644
index 000000000..005d9ff8c
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/FarmHouseHandler.java
@@ -0,0 +1,41 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import com.thatgravyboat.skyblockhud.api.events.ProfileSwitchedEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import java.util.Arrays;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class FarmHouseHandler {
+
+ public enum Medal {
+ BRONZE,
+ SILVER,
+ GOLD
+ }
+
+ private static final int[] medals = new int[Medal.values().length];
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (event.formattedLine.contains("medals:")) {
+ for (Medal value : Medal.values()) {
+ if (event.formattedLine.contains(value.name())) {
+ try {
+ medals[value.ordinal()] = Integer.parseInt(event.formattedLine.replace("medals:", "").replace(value.name(), "").trim());
+ } catch (Exception ignored) {}
+ break;
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void onProfileSwitch(ProfileSwitchedEvent event) {
+ Arrays.fill(medals, 0);
+ }
+
+ public static String getFormattedMedals(Medal medal) {
+ if (medal == null) return "0";
+ return String.valueOf(medals[medal.ordinal()]);
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/FarmingIslandHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/FarmingIslandHandler.java
new file mode 100644
index 000000000..a4abaaec8
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/FarmingIslandHandler.java
@@ -0,0 +1,28 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarPostEvent;
+import java.util.Arrays;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class FarmingIslandHandler {
+
+ public static Locations location = Locations.NONE;
+ public static int pelts;
+
+ @SubscribeEvent
+ public void onSidebarPost(SidebarPostEvent event) {
+ boolean isTracking = Arrays.toString(event.arrayScores).toLowerCase().contains("tracker mob location:");
+ if (isTracking && location == Locations.NONE) {
+ for (int i = 0; i < event.scores.size(); i++) {
+ String line = event.scores.get(i);
+ if (line.toLowerCase().contains("tracker mob location:") && i > 2) {
+ location = Locations.get(event.scores.get(i - 1).toLowerCase());
+ break;
+ }
+ }
+ }
+ if (!isTracking && location != Locations.NONE) {
+ location = Locations.NONE;
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/IslandHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/IslandHandler.java
new file mode 100644
index 000000000..cc6668a17
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/IslandHandler.java
@@ -0,0 +1,67 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import com.thatgravyboat.skyblockhud.api.events.ProfileSwitchedEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class IslandHandler {
+
+ public static int flightTime;
+ public static boolean hadFlightTime;
+
+ public static int redstone;
+ public static boolean hadRedstone;
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ hadFlightTime = checkFlightDuration(event.formattedLine);
+ hadRedstone = checkRestone(event.formattedLine);
+ }
+
+ @SubscribeEvent
+ public void onProfileSwitch(ProfileSwitchedEvent event) {
+ flightTime = 0;
+ }
+
+ public static boolean checkFlightDuration(String formatedScoreboardLine) {
+ if (LocationHandler.getCurrentLocation() == Locations.YOURISLAND && Utils.removeColor(formatedScoreboardLine.toLowerCase().trim()).contains("flight duration:")) {
+ String timeString = formatedScoreboardLine.toLowerCase().replace("flight duration:", "").replace(" ", "");
+ String[] times = timeString.split(":");
+ if (times.length == 2) {
+ int s = 0;
+ try {
+ s += Integer.parseInt(times[0]) * 60;
+ } catch (NumberFormatException ignored) {}
+ try {
+ s += Integer.parseInt(times[1]);
+ } catch (NumberFormatException ignored) {}
+ flightTime = s - 1;
+ } else if (times.length == 3) {
+ int s = 0;
+ try {
+ s += Integer.parseInt(times[0]) * 3600;
+ } catch (NumberFormatException ignored) {}
+ try {
+ s += Integer.parseInt(times[1]) * 60;
+ } catch (NumberFormatException ignored) {}
+ try {
+ s += Integer.parseInt(times[2]);
+ } catch (NumberFormatException ignored) {}
+ flightTime = s - 1;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean checkRestone(String formatedScoreboardLine) {
+ if (LocationHandler.getCurrentLocation() == Locations.YOURISLAND) {
+ if (formatedScoreboardLine.toLowerCase().contains("redstone:")) return true;
+ try {
+ redstone = formatedScoreboardLine.toLowerCase().contains("redstone:") ? Integer.parseInt(Utils.removeWhiteSpaceAndRemoveWord(formatedScoreboardLine, "redstone:")) : 0;
+ } catch (Exception ignored) {}
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/LocationCategory.java b/src/main/java/com/thatgravyboat/skyblockhud/location/LocationCategory.java
new file mode 100644
index 000000000..b6ef6fbac
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/LocationCategory.java
@@ -0,0 +1,54 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import static com.thatgravyboat.skyblockhud.handlers.MapHandler.Maps;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.handlers.MapHandler;
+
+public enum LocationCategory {
+ ERROR("error", 34),
+ ISLAND("island", 43),
+ HUB("hub", 34, Maps.HUB),
+ BARN("barn", 67, Maps.BARN),
+ MUSHROOMDESERT("mushroomdesert", 75, Maps.MUSHROOM),
+ GOLDMINE("gold_mine", 83),
+ DEEPCAVERNS("deepcaverns", 91),
+ SPIDERSDEN("spiders_den", 99, Maps.SPIDERS),
+ PARK("park", 51, Maps.PARK),
+ FORTRESS("fortress", 107, Maps.NETHER),
+ DUNGEONHUB("dungeonhub", 115),
+ JERRY("jerry", 59),
+ THEEND("the_end", 123),
+ DWARVENMINES("dwarven_mines", 131, Maps.DWARVEN),
+ CRYSTALHOLLOWS("crystal_hollows", 139, Maps.CRYSTAL);
+
+ private final String name;
+ private final int texturePos;
+ private final MapHandler.Maps map;
+
+ LocationCategory(String name, int texturePos) {
+ this(name, texturePos, null);
+ }
+
+ LocationCategory(String name, int texturePos, MapHandler.Maps map) {
+ this.name = name;
+ this.texturePos = texturePos;
+ this.map = map;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public int getTexturePos() {
+ return this.texturePos;
+ }
+
+ public MapHandler.Maps getMap() {
+ if (this.map != null && SkyblockHud.config.map.mapLocations.contains(this.ordinal() - 2)) return this.map; else return null;
+ }
+
+ public boolean isMiningCategory() {
+ return this == LocationCategory.DWARVENMINES || this == LocationCategory.CRYSTALHOLLOWS;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/LocationHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/LocationHandler.java
new file mode 100644
index 000000000..cfbfdc324
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/LocationHandler.java
@@ -0,0 +1,44 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import com.thatgravyboat.skyblockhud.api.events.LocationChangeEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import java.util.Locale;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class LocationHandler {
+
+ private static Locations currentLocation = Locations.NONE;
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (event.rawLine.contains("\u23E3")) {
+ String objectiveName = event.objective.getDisplayName().replaceAll("(?i)\\u00A7.", "");
+ if (objectiveName.toLowerCase(Locale.ENGLISH).endsWith("guest")) {
+ LocationHandler.setCurrentLocation(Locations.GUESTISLAND);
+ } else {
+ LocationHandler.handleLocation(event.formattedLine);
+ }
+ }
+ }
+
+ public static void setCurrentLocation(Locations location) {
+ currentLocation = location;
+ }
+
+ public static Locations getCurrentLocation() {
+ return currentLocation;
+ }
+
+ public static void handleLocation(String locationLine) {
+ String location = locationLine.replace(" ", "").toUpperCase(Locale.ENGLISH).trim();
+ if (location.startsWith("THECATACOMBS")) {
+ MinecraftForge.EVENT_BUS.post(new LocationChangeEvent(currentLocation, Locations.CATACOMBS));
+ currentLocation = Locations.CATACOMBS;
+ } else {
+ Locations locations = Locations.get(location.replaceAll("[^A-Za-z0-9]", ""));
+ MinecraftForge.EVENT_BUS.post(new LocationChangeEvent(currentLocation, locations));
+ currentLocation = locations;
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/Locations.java b/src/main/java/com/thatgravyboat/skyblockhud/location/Locations.java
new file mode 100644
index 000000000..5077bce99
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/Locations.java
@@ -0,0 +1,161 @@
+package com.thatgravyboat.skyblockhud.location;
+
+public enum Locations {
+ //ERROR LOCATIONS
+ DEFAULT("unknown", "Error", LocationCategory.ERROR),
+ NONE("none", "Unknown", LocationCategory.ERROR),
+ //ISLAND
+ YOURISLAND("yourisland", "Your Island", LocationCategory.ISLAND),
+ GUESTISLAND("guestisland", "Guest Island", LocationCategory.ISLAND),
+ MOULBERRYSISLAND("moulberryisland", "Cool Dude Hub", LocationCategory.ISLAND),
+ //HUB
+ VILLAGE("village", "Village", LocationCategory.HUB),
+ AUCTIONHOUSE("auctionhouse", "Auction House", LocationCategory.HUB),
+ BAZAARALLEY("bazaaralley", "Bazaar Alley", LocationCategory.HUB),
+ BANK("bank", "Bank", LocationCategory.HUB),
+ FASHIONSHOP("fashionshop", "Fashion Shop", LocationCategory.HUB),
+ COLOSSEUM("colosseum", "Colosseum", LocationCategory.HUB),
+ COLOSSEUMARENA("colosseumarena", "Colosseum Arena", LocationCategory.HUB),
+ MOUNTAIN("mountain", "Mountain", LocationCategory.HUB),
+ HIGHLEVEL("highlevel", "High Level", LocationCategory.HUB),
+ WILDERNESS("wilderness", "Wilderness", LocationCategory.HUB),
+ FISHERMANSHUT("fishermanshut", "Fisherman's Hut", LocationCategory.HUB),
+ FLOWERHOUSE("flowerhouse", "Flower House", LocationCategory.HUB),
+ CANVASROOM("canvasroom", "Canvas Room", LocationCategory.HUB),
+ TAVERN("tavern", "Tavern", LocationCategory.HUB),
+ FOREST("forest", "Forest", LocationCategory.HUB),
+ RUINS("ruins", "Ruins", LocationCategory.HUB),
+ GRAVEYARD("graveyard", "Graveyard", LocationCategory.HUB),
+ COALMINE("coalmine", "Coal Mine", LocationCategory.HUB),
+ FARM("farm", "Farm", LocationCategory.HUB),
+ LIBRARY("library", "Library", LocationCategory.HUB),
+ COMMUNITYCENTER("communitycenter", "Community Center", LocationCategory.HUB),
+ ELECTIONROOM("electionroom", "Election Room", LocationCategory.HUB),
+ BUILDERSHOUSE("buildershouse", "Builder's House", LocationCategory.HUB),
+ BLACKSMITH("blacksmith", "Blacksmith", LocationCategory.HUB),
+ FARMHOUSE("farmhouse", "Farmhouse", LocationCategory.HUB),
+ WIZARDTOWER("wizardtower", "Wizard Tower", LocationCategory.HUB),
+ //THE BARN
+ THEBARN("thebarn", "The Barn", LocationCategory.BARN),
+ WINDMILL("windmill", "Windmill", LocationCategory.BARN),
+ //MUSHROOM DESERT
+ MUSHROOMDESERT("mushroomdesert", "Mushroom Desert", LocationCategory.MUSHROOMDESERT),
+ DESERTSETTLEMENT("desertsettlement", "Desert Settlement", LocationCategory.MUSHROOMDESERT),
+ OASIS("oasis", "Oasis", LocationCategory.MUSHROOMDESERT),
+ MUSHROOMGORGE("mushroomgorge", "Mushroom Gorge", LocationCategory.MUSHROOMDESERT),
+ SHEPHERDSKEEP("shepherdskeep", "Shepherds Keep", LocationCategory.MUSHROOMDESERT),
+ JAKESHOUSE("jakeshouse", "Jake's House", LocationCategory.MUSHROOMDESERT),
+ TREASUREHUNTERCAMP("treasurehuntercamp", "Treasure Hunter Camp", LocationCategory.MUSHROOMDESERT),
+ GLOWINGMUSHROOMCAVE("glowingmushroomcave", "Glowing Mushroom Cave", LocationCategory.MUSHROOMDESERT),
+ TRAPPERSDEN("trappersden", "Trappers Den", LocationCategory.MUSHROOMDESERT),
+ OVERGROWNMUSHROOMCAVE("overgrownmushroomcave", "Overgrown Mushroom Cave", LocationCategory.MUSHROOMDESERT),
+ //GOLD MINE
+ GOLDMINE("goldmine", "Gold Mine", LocationCategory.GOLDMINE),
+ //DEEP CAVERNS
+ DEEPCAVERNS("deepcaverns", "Deep Caverns", LocationCategory.DEEPCAVERNS),
+ GUNPOWDERMINES("gunpowdermines", "Gunpowder Mines", LocationCategory.DEEPCAVERNS),
+ LAPISQUARRY("lapisquarry", "Lapis Quarry", LocationCategory.DEEPCAVERNS),
+ PIGMENSDEN("pigmensden", "Pigmen's Den", LocationCategory.DEEPCAVERNS),
+ SLIMEHILL("slimehill", "Slimehill", LocationCategory.DEEPCAVERNS),
+ DIAMONDRESERVE("diamondreserve", "Diamond Reserve", LocationCategory.DEEPCAVERNS),
+ OBSIDIANSANCTUARY("obsidiansanctuary", "Obsidian Sanctuary", LocationCategory.DEEPCAVERNS),
+ //SPIDERS DEN
+ SPIDERSDEN("spidersden", "Spider's Den", LocationCategory.SPIDERSDEN),
+
+ //THE END
+ THEEND("theend", "The End", LocationCategory.THEEND),
+ DRAGONSNEST("dragonsnest", "Dragon's Nest", LocationCategory.THEEND),
+ VOIDSEPULTURE("voidsepulture", "Void Sepulture", LocationCategory.THEEND),
+ //PARK
+ HOWLINGCAVE("howlingcave", "Howling Cave", LocationCategory.PARK),
+ BIRCHPARK("birchpark", "Birch Park", LocationCategory.PARK),
+ SPRUCEWOODS("sprucewoods", "Spruce Woods", LocationCategory.PARK),
+ DARKTHICKET("darkthicket", "Dark Thicket", LocationCategory.PARK),
+ SAVANNAWOODLAND("savannawoodland", "Savanna Woodland", LocationCategory.PARK),
+ JUNGLEISLAND("jungleisland", "Jungle Island", LocationCategory.PARK),
+ //BLAZING FORTRESS
+ BLAZINGFORTRESS("blazingfortress", "Blazing Fortress", LocationCategory.FORTRESS),
+ //DUNGEONS
+ DUNGEONHUB("dungeonhub", "Dungeon Hub", LocationCategory.DUNGEONHUB),
+ CATACOMBS("catacombs", "The Catacombs", LocationCategory.DUNGEONHUB),
+ CATACOMBSENTRANCE("catacombsentrance", "Catacombs Entrance", LocationCategory.DUNGEONHUB),
+ //JERRYISLAND
+ JERRYSWORKSHOP("jerrysworkshop", "Jerry's Workshop", LocationCategory.JERRY),
+ JERRYPOND("jerrypond", "Jerry Pond", LocationCategory.JERRY),
+ //DWARVENMINES
+ THELIFT("thelift", "The Lift", LocationCategory.DWARVENMINES),
+ DWARVENVILLAGE("dwarvenvillage", "Dwarven Village", LocationCategory.DWARVENMINES),
+ DWARVENMINES("dwarvenmines", "Dwarven Mines", LocationCategory.DWARVENMINES),
+ DWARVENTAVERN("dwarvemtavern", "Dwarven Tavern", LocationCategory.DWARVENMINES),
+ LAVASPRINGS("lavasprings", "Lava Springs", LocationCategory.DWARVENMINES),
+ PALACEBRIDGE("palacebridge", "Palace Bridge", LocationCategory.DWARVENMINES),
+ ROYALPALACE("royalpalace", "Royal Palace", LocationCategory.DWARVENMINES),
+ GRANDLIBRARY("grandlibrary", "Grand Library", LocationCategory.DWARVENMINES),
+ ROYALQUARTERS("royalquarters", "Royal Quarters", LocationCategory.DWARVENMINES),
+ BARRACKSOFHEROES("barracksofheroes", "Barracks of Heroes", LocationCategory.DWARVENMINES),
+ HANGINGCOURT("hangingcourt", "Hanging Court", LocationCategory.DWARVENMINES),
+ GREATICEWALL("greaticewall", "Great Ice Wall", LocationCategory.DWARVENMINES),
+ GOBLINBURROWS("goblinburrows", "Goblin Burrows", LocationCategory.DWARVENMINES),
+ FARRESERVE("farreserve", "Far Reserve", LocationCategory.DWARVENMINES),
+ CCMINECARTSCO("ccminecartco", "Minecart Co.", LocationCategory.DWARVENMINES),
+ UPPERMINES("uppermines", "Upper Mines", LocationCategory.DWARVENMINES),
+ RAMPARTSQUARRY("rampartsquarry", "Ramparts Quarry", LocationCategory.DWARVENMINES),
+ GATESTOTHEMINES("gatestothemines", "Gates to The Mines", LocationCategory.DWARVENMINES),
+ FORGEBASIN("forgebasin", "Forge Basin", LocationCategory.DWARVENMINES),
+ THEFORGE("theforge", "The Forge", LocationCategory.DWARVENMINES),
+ CLIFFSIDEVEINS("cliffsideveins", "Cliffside Veins", LocationCategory.DWARVENMINES),
+ DIVANSGATEWAY("divansgateway", "Divan's Gateway", LocationCategory.DWARVENMINES),
+ THEMIST("themist", "The Mist", LocationCategory.DWARVENMINES),
+ ROYALMINES("royalmines", "Royal Mines", LocationCategory.DWARVENMINES),
+ ARISTOCRATPASSAGE("aristocratpassage", "Aristocrat Passage", LocationCategory.DWARVENMINES),
+ MINERSGUILD("minersguild", "Miner's Guild", LocationCategory.DWARVENMINES),
+ //CRYSTALHOLLOWS
+ JUNGLE("jungle", "Jungle", LocationCategory.CRYSTALHOLLOWS),
+ JUNGLETEMPLE("jungletemple", "Jungle Temple", LocationCategory.CRYSTALHOLLOWS),
+ MITHRILDEPOSITS("mithrildeposits", "Mithril Deposits", LocationCategory.CRYSTALHOLLOWS),
+ MINESOFDIVAN("minesofdivan", "Mines of Divan", LocationCategory.CRYSTALHOLLOWS),
+ MAGMAFIELDS("magmafields", "Magma Fields", LocationCategory.CRYSTALHOLLOWS),
+ KHAZADDM("khzaddm", "Khazad-d\u00FBm", LocationCategory.CRYSTALHOLLOWS),
+ GOBLINHOLDOUT("goblinholdout", "Goblin Holdout", LocationCategory.CRYSTALHOLLOWS),
+ GOBLINQUEENSDEN("goblinqueensden", "Goblin Queens Den", LocationCategory.CRYSTALHOLLOWS),
+ PRECURSORREMNANTS("precursorremnants", "Precursor Remnants", LocationCategory.CRYSTALHOLLOWS),
+ LOSTPRECURSORCITY("lostprecursorcity", "Lost Precursor City", LocationCategory.CRYSTALHOLLOWS),
+ CRYSTALNUCLEUS("crystalnucleus", "Crystal Nucleus", LocationCategory.CRYSTALHOLLOWS),
+ CRYSTALHOLLOWS("crystalhollows", "Crystal Hollows", LocationCategory.CRYSTALHOLLOWS),
+ FAIRYGROTTO("fairygrotto", "Fairy Grotto", LocationCategory.CRYSTALHOLLOWS);
+
+ private final String name;
+ private final String displayName;
+ private final LocationCategory category;
+
+ Locations(String name, String displayName, LocationCategory category) {
+ this.name = name;
+ this.displayName = displayName;
+ this.category = category;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ public LocationCategory getCategory() {
+ return this.category;
+ }
+
+ public static Locations get(String id) {
+ try {
+ return Locations.valueOf(id.replace(" ", "").toUpperCase());
+ } catch (IllegalArgumentException ex) {
+ return DEFAULT;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/MinesHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/MinesHandler.java
new file mode 100644
index 000000000..ebde7dba7
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/MinesHandler.java
@@ -0,0 +1,192 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.api.events.SidebarPostEvent;
+import com.thatgravyboat.skyblockhud.overlay.MiningHud;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.lang.ref.WeakReference;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.Locale;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class MinesHandler {
+
+ public enum Event {
+ NONE(0, "Unknown", false, false),
+ TICKET(107, "Raffle", true, true),
+ GOBLIN(99, "Goblin Raid", true, true),
+ WIND(0, "Gone With The Wind", false, false),
+ TOGETHER(171, "Better Together", false, true);
+
+ public int x;
+ public String displayName;
+ public boolean needsMax;
+ public boolean display;
+
+ Event(int x, String displayName, boolean needsMax, boolean display) {
+ this.x = x;
+ this.displayName = displayName;
+ this.needsMax = needsMax;
+ this.display = display;
+ }
+ }
+
+ public static int mithril;
+ public static int gemstone;
+
+ public static int eventMax;
+ public static int eventProgress;
+ public static Event currentEvent;
+
+ private static final DecimalFormat NORMAL_FORMATTER = new DecimalFormat("#,###", DecimalFormatSymbols.getInstance(Locale.CANADA));
+ private static final DecimalFormat SHORT_FORMATTER = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.CANADA));
+
+ static {
+ SHORT_FORMATTER.setRoundingMode(RoundingMode.FLOOR);
+ }
+
+ public static String getMithrilFormatted() {
+ String output = NORMAL_FORMATTER.format(mithril);
+ if (output.equals(".0")) output = "0.0"; else if (output.equals(",0")) output = "0,0";
+ return output;
+ }
+
+ public static String getMithrilShortFormatted() {
+ return mithril > 999 ? SHORT_FORMATTER.format((double) mithril / 1000) + "k" : String.valueOf(mithril);
+ }
+
+ public static String getGemstoneFormatted() {
+ String output = NORMAL_FORMATTER.format(gemstone);
+ if (output.equals(".0")) output = "0.0"; else if (output.equals(",0")) output = "0,0";
+ return output;
+ }
+
+ public static String getGemstoneShortFormatted() {
+ return gemstone > 999 ? SHORT_FORMATTER.format((double) gemstone / 1000) + "k" : String.valueOf(gemstone);
+ }
+
+ public static void parseMithril(String line) {
+ try {
+ mithril = Integer.parseInt(line.toLowerCase().replace("mithril powder:", "").trim());
+ } catch (Exception ignored) {}
+ }
+
+ public static void parseGemstone(String line) {
+ try {
+ gemstone = Integer.parseInt(line.toLowerCase().replace("gemstone powder:", "").trim());
+ } catch (Exception ignored) {}
+ }
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (event.formattedLine.toLowerCase().contains("heat")) {
+ try {
+ MiningHud.setHeat(Integer.parseInt(event.formattedLine.toLowerCase().replace("heat:", "").trim()));
+ } catch (Exception ignored) {}
+ }
+ if (event.formattedLine.toLowerCase().contains("mithril")) {
+ try {
+ mithril = Integer.parseInt(event.formattedLine.toLowerCase().replace("mithril:", "").trim());
+ } catch (Exception ignored) {}
+ }
+ if (event.formattedLine.toLowerCase().contains("gemstone")) {
+ try {
+ gemstone = Integer.parseInt(event.formattedLine.toLowerCase().replace("gemstone:", "").trim());
+ } catch (Exception ignored) {}
+ }
+ if (event.formattedLine.toLowerCase().contains("event")) {
+ if (event.formattedLine.toLowerCase().contains("raffle")) {
+ MinesHandler.currentEvent = Event.TICKET;
+ } else if (event.formattedLine.toLowerCase().contains("goblin raid")) {
+ MinesHandler.currentEvent = Event.GOBLIN;
+ }
+ }
+ if (event.formattedLine.equalsIgnoreCase("wind compass")) {
+ MinesHandler.currentEvent = Event.WIND;
+ }
+ if (event.formattedLine.toLowerCase(Locale.ENGLISH).contains("nearby players")) {
+ MinesHandler.currentEvent = Event.TOGETHER;
+ try {
+ MinesHandler.eventProgress = Integer.parseInt(event.formattedLine.toLowerCase().replace("nearby players:", "").trim());
+ } catch (Exception ignored) {}
+ }
+
+ if (MinesHandler.currentEvent != Event.NONE) {
+ if (MinesHandler.currentEvent == Event.TICKET) {
+ if (event.formattedLine.toLowerCase().contains("pool:")) {
+ try {
+ eventMax = Integer.parseInt(event.formattedLine.toLowerCase().replace("pool:", "").trim().split("/")[0].trim());
+ } catch (Exception ignored) {}
+ } else if (event.formattedLine.toLowerCase().contains("tickets:")) {
+ try {
+ eventProgress = Integer.parseInt(event.formattedLine.toLowerCase().replace("tickets:", "").split("\\(")[0].trim());
+ } catch (Exception ignored) {}
+ }
+ } else if (MinesHandler.currentEvent == Event.GOBLIN) {
+ if (event.formattedLine.toLowerCase().contains("remaining:")) {
+ try {
+ eventMax = Integer.parseInt(event.formattedLine.toLowerCase().replace("goblins", "").replace("remaining:", "").trim());
+ } catch (Exception ignored) {}
+ } else if (event.formattedLine.toLowerCase().contains("your kills:") && !event.formattedLine.toLowerCase().contains("(")) {
+ try {
+ eventProgress = Integer.parseInt(event.formattedLine.toLowerCase().replace("your kills:", "").trim());
+ } catch (Exception ignored) {}
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void onSidebarPost(SidebarPostEvent event) {
+ String arrayString = Arrays.toString(event.arrayScores);
+ boolean hasEvent = arrayString.toLowerCase().contains("event:");
+ boolean hasWind = arrayString.toLowerCase().contains("wind compass");
+ boolean hasNearbyPlayers = arrayString.toLowerCase().contains("nearby players");
+
+ if (!hasEvent && !hasWind && !hasNearbyPlayers) {
+ MinesHandler.currentEvent = Event.NONE;
+ MinesHandler.eventProgress = 0;
+ MinesHandler.eventMax = 0;
+ }
+ if (!arrayString.toLowerCase().contains("heat:")) {
+ MiningHud.setHeat(0);
+ }
+ }
+
+ public static WeakReference<PrehistoricEggProgress> getEggColorAndProgress(ItemStack stack) {
+ String id = Utils.getItemCustomId(stack);
+ if (id == null || !id.equals("PREHISTORIC_EGG")) return null;
+ NBTTagCompound extraAttributes = stack.getTagCompound().getCompoundTag("ExtraAttributes");
+ if (!extraAttributes.hasKey("blocks_walked")) return null;
+ PrehistoricEggProgress progress = new PrehistoricEggProgress();
+ int walked = extraAttributes.getInteger("blocks_walked");
+ if (walked < 4000) {
+ progress.currentColor = 0xffffff;
+ progress.progress = walked / 4000f;
+ } else if (walked < 10000) {
+ progress.currentColor = 0x55FF55;
+ progress.progress = (walked - 4000f) / 6000f;
+ } else if (walked < 20000) {
+ progress.currentColor = 0x5555FF;
+ progress.progress = (walked - 10000f) / 10000f;
+ } else if (walked < 40000) {
+ progress.currentColor = 0xAA00AA;
+ progress.progress = (walked - 20000f) / 20000f;
+ } else if (walked < 100000) {
+ progress.currentColor = 0xFFAA00;
+ progress.progress = (walked - 40000f) / 60000f;
+ }
+ return new WeakReference<>(progress);
+ }
+
+ public static class PrehistoricEggProgress {
+
+ public float progress;
+ public int currentColor;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/location/ParkIslandHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/location/ParkIslandHandler.java
new file mode 100644
index 000000000..b9628d008
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/location/ParkIslandHandler.java
@@ -0,0 +1,29 @@
+package com.thatgravyboat.skyblockhud.location;
+
+import javax.annotation.Nullable;
+
+public class ParkIslandHandler {
+
+ private static boolean isRaining = false;
+ private static String rainTime = "";
+
+ public static void parseRain(@Nullable String tabLine) {
+ if (tabLine == null) {
+ isRaining = false;
+ rainTime = "";
+ } else if (tabLine.toLowerCase().contains("rain:")) {
+ if (tabLine.toLowerCase().contains("no rain")) isRaining = false; else {
+ rainTime = tabLine.toLowerCase().replace("rain:", "").replace(" ", "");
+ isRaining = true;
+ }
+ }
+ }
+
+ public static String getRainTime() {
+ return rainTime;
+ }
+
+ public static boolean isRaining() {
+ return isRaining;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/mixins/GuiChestAccessor.java b/src/main/java/com/thatgravyboat/skyblockhud/mixins/GuiChestAccessor.java
new file mode 100644
index 000000000..0a1fb7565
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/mixins/GuiChestAccessor.java
@@ -0,0 +1,12 @@
+package com.thatgravyboat.skyblockhud.mixins;
+
+import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.inventory.IInventory;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(GuiChest.class)
+public interface GuiChestAccessor {
+ @Accessor
+ IInventory getLowerChestInventory();
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinEntityArrow.java b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinEntityArrow.java
new file mode 100644
index 000000000..dbde075ee
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinEntityArrow.java
@@ -0,0 +1,26 @@
+package com.thatgravyboat.skyblockhud.mixins;
+
+import com.thatgravyboat.skyblockhud.api.KillTracking;
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.projectile.EntityArrow;
+import net.minecraft.util.MovingObjectPosition;
+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.ModifyVariable;
+
+@Mixin(EntityArrow.class)
+public class MixinEntityArrow {
+
+ @Shadow
+ public Entity shootingEntity;
+
+ @ModifyVariable(method = "onUpdate", at = @At(value = "STORE", ordinal = 1))
+ public MovingObjectPosition onUpdate(MovingObjectPosition position) {
+ if (position != null && position.entityHit != null && this.shootingEntity != null && this.shootingEntity.getUniqueID().equals(Minecraft.getMinecraft().thePlayer.getUniqueID())) {
+ KillTracking.attackedEntities.add(position.entityHit.getUniqueID());
+ }
+ return position;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinGuiIngameForge.java b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinGuiIngameForge.java
new file mode 100644
index 000000000..eaa1b5a74
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinGuiIngameForge.java
@@ -0,0 +1,113 @@
+package com.thatgravyboat.skyblockhud.mixins;
+
+import static net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType.*;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.overlay.MiningHud;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraftforge.client.GuiIngameForge;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.common.MinecraftForge;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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(GuiIngameForge.class)
+public class MixinGuiIngameForge {
+
+ @Shadow(remap = false)
+ private RenderGameOverlayEvent eventParent;
+
+ @Inject(method = "renderArmor", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderArmor(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideArmor && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(ARMOR, eventParent)) return;
+ postPost(ARMOR, eventParent);
+ }
+ }
+
+ @Inject(method = "renderHealth", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderHealth(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideHearts && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(HEALTH, eventParent)) return;
+ postPost(HEALTH, eventParent);
+ }
+ }
+
+ @Inject(method = "renderAir", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderAir(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideAir && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(AIR, eventParent)) return;
+ postPost(AIR, eventParent);
+ }
+ }
+
+ @Inject(method = "renderHealthMount", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderHealthMount(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideAnimalHearts && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(HEALTHMOUNT, eventParent)) return;
+ postPost(HEALTHMOUNT, eventParent);
+ }
+ }
+
+ @Inject(method = "renderExperience", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderExperience(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideXpBar && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(EXPERIENCE, eventParent)) return;
+ postPost(EXPERIENCE, eventParent);
+ } else if (SkyblockHud.config.mining.barMode == 1) {
+ if (!SkyblockHud.config.renderer.hideXpBar && (SkyblockHud.config.mining.showDrillBar || SkyblockHud.config.mining.showHeatBar) && SkyblockHud.hasSkyblockScoreboard()) {
+ if (MiningHud.getHeat() > 0 || Utils.isDrill(Minecraft.getMinecraft().thePlayer.getHeldItem())) {
+ ci.cancel();
+ if (prePost(EXPERIENCE, eventParent)) return;
+ postPost(EXPERIENCE, eventParent);
+ }
+ }
+ }
+ }
+
+ @Inject(method = "renderJumpBar", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderJumpBar(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideXpBar && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(JUMPBAR, eventParent)) return;
+ postPost(JUMPBAR, eventParent);
+ } else if (SkyblockHud.config.mining.barMode == 1) {
+ if (!SkyblockHud.config.renderer.hideXpBar && (SkyblockHud.config.mining.showDrillBar || SkyblockHud.config.mining.showHeatBar) && SkyblockHud.hasSkyblockScoreboard()) {
+ if (MiningHud.getHeat() > 0 || Utils.isDrill(Minecraft.getMinecraft().thePlayer.getHeldItem())) {
+ ci.cancel();
+ if (prePost(JUMPBAR, eventParent)) return;
+ postPost(JUMPBAR, eventParent);
+ }
+ }
+ }
+ }
+
+ @Inject(method = "renderFood", at = @At("HEAD"), cancellable = true, remap = false)
+ public void onRenderFood(int width, int height, CallbackInfo ci) {
+ if (SkyblockHud.config.renderer.hideFood && SkyblockHud.hasSkyblockScoreboard()) {
+ ci.cancel();
+ if (prePost(FOOD, eventParent)) return;
+ postPost(FOOD, eventParent);
+ }
+ }
+
+ @Unique
+ private static boolean prePost(RenderGameOverlayEvent.ElementType type, RenderGameOverlayEvent eventParent) {
+ return MinecraftForge.EVENT_BUS.post(new RenderGameOverlayEvent.Pre(eventParent, type));
+ }
+
+ @Unique
+ private static void postPost(RenderGameOverlayEvent.ElementType type, RenderGameOverlayEvent eventParent) {
+ MinecraftForge.EVENT_BUS.post(new RenderGameOverlayEvent.Post(eventParent, type));
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinItemStack.java b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinItemStack.java
new file mode 100644
index 000000000..1f372ad40
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinItemStack.java
@@ -0,0 +1,42 @@
+package com.thatgravyboat.skyblockhud.mixins;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.item.IAbility;
+import com.thatgravyboat.skyblockhud.handlers.CooldownHandler;
+import java.util.regex.Matcher;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+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(ItemStack.class)
+public class MixinItemStack implements IAbility {
+
+ private String ability;
+ private int abilityTime;
+
+ @Inject(method = "setTagCompound", at = @At("HEAD"))
+ public void onNbt(NBTTagCompound nbt, CallbackInfo ci) {
+ if (SkyblockHud.config != null && !SkyblockHud.config.misc.hideItemCooldowns) {
+ Matcher abilityMatcher = CooldownHandler.getAbility(nbt);
+ if (abilityMatcher != null) {
+ ability = abilityMatcher.group(1);
+ try {
+ abilityTime = Integer.parseInt(abilityMatcher.group(2).trim());
+ } catch (Exception ignored) {}
+ }
+ }
+ }
+
+ @Override
+ public String getAbility() {
+ return ability;
+ }
+
+ @Override
+ public int getAbilityTime() {
+ return abilityTime;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinNetHandlerPlayClient.java b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinNetHandlerPlayClient.java
new file mode 100644
index 000000000..40f33342e
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinNetHandlerPlayClient.java
@@ -0,0 +1,67 @@
+package com.thatgravyboat.skyblockhud.mixins;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.tracker.TrackerHandler;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.network.NetHandlerPlayClient;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.PacketThreadUtil;
+import net.minecraft.network.play.server.S2FPacketSetSlot;
+import net.minecraft.network.play.server.S3EPacketTeams;
+import net.minecraft.scoreboard.ScorePlayerTeam;
+import net.minecraft.scoreboard.Scoreboard;
+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;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+@Mixin(NetHandlerPlayClient.class)
+public class MixinNetHandlerPlayClient {
+
+ @Inject(method = "handleSetSlot", at = @At("HEAD"))
+ public void onHandleSetSlot(S2FPacketSetSlot packetIn, CallbackInfo ci) {
+ if (SkyblockHud.hasSkyblockScoreboard()) {
+ Minecraft mc = Minecraft.getMinecraft();
+ PacketThreadUtil.checkThreadAndEnqueue(packetIn, mc.getNetHandler(), mc);
+ if (packetIn.func_149175_c() == 0) {
+ ItemStack stack = packetIn.func_149174_e();
+
+ if (stack != null && stack.hasTagCompound()) {
+ if (stack.getTagCompound().hasKey("ExtraAttributes")) {
+ NBTTagCompound extraAttributes = stack.getTagCompound().getCompoundTag("ExtraAttributes");
+ String id = extraAttributes.getString("id");
+ ItemStack slotStack = Minecraft.getMinecraft().thePlayer.inventoryContainer.getSlot(packetIn.func_149173_d()).getStack();
+ int changeAmount = stack.stackSize - (slotStack == null ? 0 : slotStack.stackSize);
+ String specialId = null;
+ int number = -1;
+ if (extraAttributes.hasKey("enchantments")) {
+ NBTTagCompound enchantments = extraAttributes.getCompoundTag("enchantments");
+ if (enchantments.getKeySet().size() == 1) {
+ for (String e : enchantments.getKeySet()) {
+ specialId = e;
+ break;
+ }
+ if (specialId != null) number = enchantments.getInteger(specialId);
+ }
+ }
+ TrackerHandler.onItemAdded(id, changeAmount, specialId, number);
+ }
+ }
+ }
+ }
+ }
+
+ @Inject(method = "handleTeams", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/network/play/server/S3EPacketTeams;getAction()I", ordinal = 0, shift = At.Shift.BEFORE), cancellable = true)
+ public void handleTeams(S3EPacketTeams packetIn, CallbackInfo ci, Scoreboard scoreboard) {
+ //This stops Hypixel from being stupid and spamming our logs because they dont have different ids for things.
+ if (scoreboard.getTeam(packetIn.getName()) != null && packetIn.getAction() == 0) ci.cancel();
+ }
+
+ @Inject(method = "handleTeams", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/network/play/server/S3EPacketTeams;getAction()I", ordinal = 6, shift = At.Shift.BEFORE), cancellable = true)
+ public void handleTeamRemove(S3EPacketTeams packetIn, CallbackInfo ci, Scoreboard scoreboard, ScorePlayerTeam scoreplayerteam) {
+ //This stops Hypixel from being stupid and spamming our logs because they dont have different ids for things.
+ if (scoreplayerteam == null) ci.cancel();
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinRenderItem.java b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinRenderItem.java
new file mode 100644
index 000000000..ae7d5bf89
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/mixins/MixinRenderItem.java
@@ -0,0 +1,63 @@
+package com.thatgravyboat.skyblockhud.mixins;
+
+import com.thatgravyboat.skyblockhud.handlers.CooldownHandler;
+import com.thatgravyboat.skyblockhud.location.MinesHandler;
+import java.lang.ref.WeakReference;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.item.ItemStack;
+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(RenderItem.class)
+public abstract class MixinRenderItem {
+
+ @Shadow
+ protected abstract void draw(WorldRenderer renderer, int x, int y, int width, int height, int red, int green, int blue, int alpha);
+
+ @Inject(method = "renderItemOverlayIntoGUI", at = @At("RETURN"))
+ public void renderItemOverlayIntoGUI(FontRenderer fr, ItemStack stack, int xPosition, int yPosition, String text, CallbackInfo ci) {
+ if (stack == null) return;
+ float cooldown = CooldownHandler.getAbilityTime(stack);
+
+ WeakReference<MinesHandler.PrehistoricEggProgress> weakProgress = MinesHandler.getEggColorAndProgress(stack);
+
+ if (weakProgress != null) {
+ MinesHandler.PrehistoricEggProgress progress = weakProgress.get();
+ if (progress == null) return;
+ GlStateManager.disableLighting();
+ GlStateManager.disableDepth();
+ GlStateManager.disableTexture2D();
+ GlStateManager.disableAlpha();
+ GlStateManager.disableBlend();
+ WorldRenderer worldrenderer = Tessellator.getInstance().getWorldRenderer();
+ this.draw(worldrenderer, xPosition + 2, yPosition + 13, 13, 2, 0, 0, 0, 255);
+ this.draw(worldrenderer, xPosition + 2, yPosition + 13, Math.round(progress.progress * 13f), 1, (progress.currentColor >> 16) & 0xFF, (progress.currentColor >> 8) & 0xFF, progress.currentColor & 0xFF, 255);
+ GlStateManager.enableAlpha();
+ GlStateManager.enableTexture2D();
+ GlStateManager.enableLighting();
+ GlStateManager.enableDepth();
+ }
+
+ if (cooldown > -1) {
+ GlStateManager.disableLighting();
+ GlStateManager.disableDepth();
+ GlStateManager.disableTexture2D();
+ GlStateManager.disableAlpha();
+ GlStateManager.disableBlend();
+ WorldRenderer worldrenderer = Tessellator.getInstance().getWorldRenderer();
+ this.draw(worldrenderer, xPosition + 2, yPosition + 13, 13, 2, 0, 0, 0, 255);
+ this.draw(worldrenderer, xPosition + 2, yPosition + 13, Math.round(cooldown * 13f), 1, 102, 102, 255, 255);
+ GlStateManager.enableAlpha();
+ GlStateManager.enableTexture2D();
+ GlStateManager.enableLighting();
+ GlStateManager.enableDepth();
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/overlay/DungeonOverlay.java b/src/main/java/com/thatgravyboat/skyblockhud/overlay/DungeonOverlay.java
new file mode 100644
index 000000000..9cfa1e76c
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/overlay/DungeonOverlay.java
@@ -0,0 +1,149 @@
+package com.thatgravyboat.skyblockhud.overlay;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.config.SBHConfig;
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.dungeons.Classes;
+import com.thatgravyboat.skyblockhud.dungeons.DungeonHandler;
+import com.thatgravyboat.skyblockhud.dungeons.DungeonPlayer;
+import com.thatgravyboat.skyblockhud.handlers.BossbarHandler;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import com.thatgravyboat.skyblockhud.textures.Textures;
+import com.thatgravyboat.skyblockhud.utils.SpecialColour;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+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.entity.boss.BossStatus;
+import net.minecraftforge.client.GuiIngameForge;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class DungeonOverlay extends Gui {
+
+ private static final FontRenderer font = Minecraft.getMinecraft().fontRendererObj;
+ private static boolean bossBarVisible = false;
+
+ public void drawDungeonPlayer(String name, int health, boolean isDead, Classes dungeonClass, int x, int y) {
+ if (!SkyblockHud.config.dungeon.hideDeadDungeonPlayers || !isDead) {
+ GlStateManager.enableBlend();
+ Minecraft mc = Minecraft.getMinecraft();
+ mc.renderEngine.bindTexture(Textures.texture.dungeon);
+
+ String healthString = isDead ? "DEAD" : Integer.toString(health);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, (float) SkyblockHud.config.dungeon.dungeonPlayerOpacity / 100);
+ drawTexturedModalRect(x, y, 0, dungeonClass.getTextureY(), 120, 32);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ drawString(font, name, x + 50, y + 6, 0xFFFFFF);
+ drawString(font, healthString, x + 50, y + font.FONT_HEIGHT + 9, 0xFF2B2B);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ }
+ }
+
+ public void drawDungeonClock(int width, int offset, Minecraft mc) {
+ GlStateManager.enableBlend();
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int dungeonTime = DungeonHandler.getDungeonTime();
+ int dungeonTimeMin = dungeonTime / 60;
+ int dungeonTimeSec = dungeonTime - dungeonTimeMin * 60;
+ drawTexturedModalRect((width / 2) - 17, offset + (bossBarVisible ? 17 : 0), 0, 0, 34, 34);
+ mc.renderEngine.bindTexture(Textures.texture.dungeon);
+ drawTexturedModalRect((width / 2) - 7, offset + (bossBarVisible ? 20 : 3), 16, 50, 3, 8);
+ drawTexturedModalRect((width / 2) - 7, offset + (bossBarVisible ? 30 : 13), 19, 50, 3, 8);
+ String dungeonTimeElapsed = (dungeonTimeMin > 9 ? String.valueOf(dungeonTimeMin) : "0" + dungeonTimeMin) + ":" + (dungeonTimeSec > 9 ? String.valueOf(dungeonTimeSec) : "0" + dungeonTimeSec);
+ drawCenteredString(font, dungeonTimeElapsed, (width / 2), offset + (bossBarVisible ? 40 : 23), 0xFFFF55);
+ //KEYS
+ drawString(font, (DungeonHandler.hasBloodkey() ? "\u2714" : "x"), (width / 2), offset + (bossBarVisible ? 19 : 2), (DungeonHandler.hasBloodkey() ? 0x55FF55 : 0xAA0000));
+ drawString(font, DungeonHandler.getWitherKeys() + "x", (width / 2), offset + (bossBarVisible ? 30 : 13), 0x555555);
+ //CLEARED PERCENTAGE
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int clearPercent = DungeonHandler.getDungeonCleared();
+ String clearPercentage = "Dungeon Cleared: \u00A7" + (clearPercent <= 20 ? "4" : clearPercent <= 50 ? "6" : clearPercent <= 80 ? "e" : "a") + clearPercent + "%";
+ drawTexturedModalRect((width / 2) + 17, offset + (bossBarVisible ? 20 : 3), 2, 34, font.getStringWidth(clearPercentage) + 3, 14);
+ drawTexturedModalRect(((width / 2) + 17) + font.getStringWidth(clearPercentage) + 3, offset + (bossBarVisible ? 20 : 3), 252, 34, 4, 14);
+ drawString(font, clearPercentage, (width / 2) + 18, offset + (bossBarVisible ? 23 : 6), 0xAAAAAA);
+
+ //DEATHS
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int deaths = DungeonHandler.getDeaths();
+ String deathText = "Deaths: " + deaths;
+ drawTexturedModalRect((width / 2) + 17, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(deathText) + 3, 14);
+ drawTexturedModalRect(((width / 2) + 17) + font.getStringWidth(deathText) + 3, offset + (bossBarVisible ? 35 : 18), 252, 34, 4, 14);
+ drawString(font, deathText, (width / 2) + 18, offset + (bossBarVisible ? 38 : 21), 0xAAAAAA);
+
+ //SECRETS
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int maxSecrets = DungeonHandler.getMaxSecrets();
+ int secrets = DungeonHandler.getSecrets();
+ int totalSecrets = DungeonHandler.getTotalSecrets();
+ String secretsText = "Secrets: " + secrets + "/" + maxSecrets + " (" + totalSecrets + ")";
+ drawTexturedModalRect((width / 2) - 17 - (font.getStringWidth(secretsText)) - 4, offset + (bossBarVisible ? 20 : 3), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 17 - (font.getStringWidth(secretsText))) - 2, offset + (bossBarVisible ? 20 : 3), 2, 34, font.getStringWidth(secretsText) + 2, 14);
+ drawString(font, secretsText, (width / 2) - 17 - (font.getStringWidth(secretsText)), offset + (bossBarVisible ? 23 : 6), 0xAAAAAA);
+
+ //CRYPTS
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int crypts = DungeonHandler.getCrypts();
+ String cryptText = "Crypts: " + crypts;
+ drawTexturedModalRect((width / 2) - 17 - (font.getStringWidth(cryptText)) - 4, offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 17 - (font.getStringWidth(cryptText))) - 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(cryptText) + 2, 14);
+ drawString(font, cryptText, (width / 2) - 17 - (font.getStringWidth(cryptText)), offset + (bossBarVisible ? 38 : 21), 0xAAAAAA);
+ }
+
+ public void drawUltimateBar(Minecraft mc, ScaledResolution resolution) {
+ if (!SkyblockHud.config.dungeon.hideUltimateBar) {
+ float percentage = mc.thePlayer.experience;
+ SBHConfig.DungeonHud dungeonHud = SkyblockHud.config.dungeon;
+ Position position = dungeonHud.barPosition;
+
+ int x = position.getAbsX(resolution, 182);
+ int y = position.getAbsY(resolution, 5);
+
+ GenericOverlays.drawLargeBar(mc, x - 91, y, percentage, 0.999f, SpecialColour.specialToChromaRGB(dungeonHud.barLoadColor), SpecialColour.specialToChromaRGB(dungeonHud.barFullColor), dungeonHud.barStyle);
+ }
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ Minecraft mc = Minecraft.getMinecraft();
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard(), LocationHandler.getCurrentLocation().equals(Locations.CATACOMBS))) {
+ bossBarVisible = BossStatus.statusBarTime > 0 && GuiIngameForge.renderBossHealth && BossbarHandler.bossBarRendered;
+ GlStateManager.enableBlend();
+ drawUltimateBar(mc, event.resolution);
+
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ if (!SkyblockHud.config.dungeon.hideDungeonPlayers) {
+ int[] hardCodedPos = new int[] { 5, 42, 79, 116 };
+ Position[] positions = new Position[] { SkyblockHud.config.dungeon.dungeonPlayer1, SkyblockHud.config.dungeon.dungeonPlayer2, SkyblockHud.config.dungeon.dungeonPlayer3, SkyblockHud.config.dungeon.dungeonPlayer4 };
+ for (int i = 0; i < Math.min(DungeonHandler.getDungeonPlayers().values().size(), 4); i++) {
+ DungeonPlayer player = (DungeonPlayer) DungeonHandler.getDungeonPlayers().values().toArray()[i];
+ int posX;
+ int posY;
+ try {
+ posX = positions[i].getAbsX(event.resolution, 120);
+ } catch (ArrayIndexOutOfBoundsException ignored) {
+ posX = hardCodedPos[i];
+ }
+ try {
+ posY = positions[i].getAbsY(event.resolution, 120);
+ } catch (ArrayIndexOutOfBoundsException ignored) {
+ posY = 0;
+ }
+ drawDungeonPlayer(player.getName(), player.getHealth(), player.isDead(), player.getDungeonClass(), posX, posY);
+ }
+ }
+
+ if (!SkyblockHud.config.main.disaleMainHud) {
+ drawDungeonClock(event.resolution.getScaledWidth(), SkyblockHud.config.main.mainHudPos.getAbsY(event.resolution, 34), mc);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/overlay/GenericOverlays.java b/src/main/java/com/thatgravyboat/skyblockhud/overlay/GenericOverlays.java
new file mode 100644
index 000000000..daf3ec036
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/overlay/GenericOverlays.java
@@ -0,0 +1,42 @@
+package com.thatgravyboat.skyblockhud.overlay;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.core.util.render.RenderUtils;
+import com.thatgravyboat.skyblockhud.textures.Textures;
+import java.awt.*;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+
+public class GenericOverlays extends Gui {
+
+ public static void drawLargeBar(Minecraft mc, int x, int y, float percentage, float max, int fullColor, int loadingColor, int barStyle) {
+ if (SkyblockHud.hasSkyblockScoreboard()) {
+ mc.renderEngine.bindTexture(Textures.texture.bars);
+ Color color = new Color(percentage == max ? fullColor : loadingColor);
+
+ RenderUtils.drawTexturedModalRect(x, y, 0, 0, 182, 5);
+ GlStateManager.color(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f);
+ RenderUtils.drawTexturedModalRect(x, y, 0, 30, 182, 5);
+ RenderUtils.drawTexturedModalRect(x, y, 0, 5, (int) (182 * percentage), 5);
+ if (barStyle != 0) {
+ RenderUtils.drawTexturedModalRect(x, y, 0, 5 + (barStyle * 5), 182, 5);
+ }
+ }
+ }
+
+ public static void drawSmallBar(Minecraft mc, int x, int y, double percentage, double max, int fullColor, int loadingColor, int barStyle) {
+ if (SkyblockHud.hasSkyblockScoreboard()) {
+ mc.renderEngine.bindTexture(Textures.texture.bars);
+ Color color = new Color(percentage == max ? fullColor : loadingColor);
+ GlStateManager.enableBlend();
+ RenderUtils.drawTexturedModalRect(x, y, 0, 35, 62, 5);
+ GlStateManager.color(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f);
+ RenderUtils.drawTexturedModalRect(x, y, 0, 65, 62, 5);
+ RenderUtils.drawTexturedModalRect(x, y, 0, 40, (int) (62 * percentage), 5);
+ if (barStyle != 0) {
+ RenderUtils.drawTexturedModalRect(x, y, 0, 45 + (barStyle * 5), 62, 5);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/overlay/MiningHud.java b/src/main/java/com/thatgravyboat/skyblockhud/overlay/MiningHud.java
new file mode 100644
index 000000000..a3aab50b1
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/overlay/MiningHud.java
@@ -0,0 +1,76 @@
+package com.thatgravyboat.skyblockhud.overlay;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.textures.Textures;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class MiningHud extends Gui {
+
+ private static int fuel, maxFuel;
+ private static int heat;
+
+ public static void setFuel(int fuel, int maxFuel) {
+ MiningHud.fuel = fuel;
+ MiningHud.maxFuel = maxFuel;
+ }
+
+ public static void setHeat(int heat) {
+ MiningHud.heat = heat;
+ }
+
+ public static int getHeat() {
+ return heat;
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard(), SkyblockHud.config.mining.showDrillBar || SkyblockHud.config.mining.showHeatBar)) {
+ Minecraft mc = Minecraft.getMinecraft();
+
+ if (SkyblockHud.config.mining.barMode == 1) {
+ if (heat > 0 && Utils.isDrill(mc.thePlayer.getHeldItem()) && SkyblockHud.config.mining.showDrillBar && SkyblockHud.config.mining.showHeatBar) {
+ renderFuelBar(mc, (event.resolution.getScaledWidth() / 2) - 91, event.resolution.getScaledHeight() - 31);
+ renderHeatBar(mc, (event.resolution.getScaledWidth() / 2) + 46, event.resolution.getScaledHeight() - 31);
+ } else if (Utils.isDrill(mc.thePlayer.getHeldItem()) && SkyblockHud.config.mining.showDrillBar) {
+ renderFuelBar(mc, (event.resolution.getScaledWidth() / 2) - 68, event.resolution.getScaledHeight() - 31);
+ } else if (heat > 0 && SkyblockHud.config.mining.showHeatBar) {
+ renderHeatBar(mc, (event.resolution.getScaledWidth() / 2) - 22, event.resolution.getScaledHeight() - 31);
+ }
+ } else if (SkyblockHud.config.mining.barMode == 0) {
+ if (heat > 0 && SkyblockHud.config.mining.showHeatBar) {
+ Position position = SkyblockHud.config.mining.heatBar;
+ renderHeatBar(mc, position.getAbsX(event.resolution, 45), position.getAbsY(event.resolution, 7));
+ }
+ if (Utils.isDrill(mc.thePlayer.getHeldItem()) && SkyblockHud.config.mining.showDrillBar) {
+ Position position = SkyblockHud.config.mining.drillBar;
+ renderFuelBar(mc, position.getAbsX(event.resolution, 136), position.getAbsY(event.resolution, 7));
+ }
+ }
+ }
+ }
+
+ private void renderFuelBar(Minecraft mc, int x, int y) {
+ if (maxFuel == 0) return;
+ GlStateManager.enableBlend();
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ mc.renderEngine.bindTexture(Textures.texture.mines);
+ drawTexturedModalRect(x, y, 0, 0, 136, 7);
+ drawTexturedModalRect(x, y, 0, 7, Utils.lerp((float) fuel / (float) maxFuel, 0, 136), 7);
+ String percentageText = Math.round(((float) fuel / (float) maxFuel) * 100) + "%";
+ this.drawCenteredString(mc.fontRendererObj, percentageText, x + 68, y - 2, 0xffffff);
+ }
+
+ private void renderHeatBar(Minecraft mc, int x, int y) {
+ GlStateManager.enableBlend();
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ mc.renderEngine.bindTexture(Textures.texture.mines);
+ drawTexturedModalRect(x, y, 137, 0, 45, 7);
+ drawTexturedModalRect(x, y, 137, 7, Utils.lerp(heat / 100f, 0, 45), 7);
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/overlay/OverlayHud.java b/src/main/java/com/thatgravyboat/skyblockhud/overlay/OverlayHud.java
new file mode 100644
index 000000000..70cb1532b
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/overlay/OverlayHud.java
@@ -0,0 +1,331 @@
+package com.thatgravyboat.skyblockhud.overlay;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.handlers.BossbarHandler;
+import com.thatgravyboat.skyblockhud.handlers.CurrencyHandler;
+import com.thatgravyboat.skyblockhud.handlers.SlayerHandler;
+import com.thatgravyboat.skyblockhud.handlers.TimeHandler;
+import com.thatgravyboat.skyblockhud.location.*;
+import com.thatgravyboat.skyblockhud.seasons.Season;
+import com.thatgravyboat.skyblockhud.seasons.SeasonDateHandler;
+import com.thatgravyboat.skyblockhud.textures.Textures;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.entity.boss.BossStatus;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.client.GuiIngameForge;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class OverlayHud extends Gui {
+
+ private static final FontRenderer font = Minecraft.getMinecraft().fontRendererObj;
+
+ //STATS
+ private static boolean eventToggle;
+
+ public static boolean bossBarVisible = false;
+
+ public void drawClock(int width, int offset, Minecraft mc) {
+ GlStateManager.enableBlend();
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ //CLOCK
+ int timeMin = (int) (TimeHandler.time / 60);
+ int timeHour = timeMin / 60;
+ timeMin = timeMin - (timeHour * 60);
+ String militaryTime = timeHour + ":" + (timeMin == 0 ? timeMin + "0" : timeMin);
+ int time12Hour = timeHour >= 12 ? timeHour - 12 : timeHour;
+ String normalTime = (time12Hour == 0 ? "12" : String.valueOf(time12Hour)) + ":" + (timeMin == 0 ? "00" : timeMin) + (timeHour >= 12 ? "pm" : "am");
+
+ drawTexturedModalRect((width / 2) - 17, offset + (bossBarVisible ? 17 : 0), 0, 0, 34, 34);
+ drawTexturedModalRect((width / 2) - 4, offset + (bossBarVisible ? 24 : 7), (timeHour > 19 || timeHour < 6) ? 43 : 43 + 8, 0, 8, 8);
+ if (SkyblockHud.config.main.twelveHourClock) drawScaledString(0.8f, width / 2, offset + (bossBarVisible ? 38 : 21), normalTime, (timeHour > 19 || timeHour < 6) ? 0xAFB8CC : 0xFFFF55); else drawCenteredString(font, militaryTime, (width / 2), offset + (bossBarVisible ? 38 : 21), (timeHour > 19 || timeHour < 4) ? 0xAFB8CC : 0xFFFF55);
+
+ //PURSE
+ drawPurseAndBits(width, offset, mc);
+
+ //SEASON/DATE
+ drawSeasonAndDate(width, offset, mc);
+
+ //REDSTONE PERCENT
+ drawRedstone(width, offset, mc);
+
+ // LOCATION
+ drawLocation(width, offset, mc);
+
+ //EXTRA SLOT
+ if (LocationHandler.getCurrentLocation().equals(Locations.YOURISLAND)) {
+ if (IslandHandler.flightTime > 0) drawFlightDuration(width, offset, mc);
+ } else if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.MUSHROOMDESERT)) {
+ drawTrapperOrPelts(width, offset, mc);
+ } else if (LocationHandler.getCurrentLocation().getCategory().isMiningCategory()) {
+ if (MinesHandler.currentEvent.display) {
+ drawDwarvenEvent(width, offset, mc);
+ } else {
+ drawMiningPowders(width, offset, mc);
+ }
+ } else if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.PARK) && ParkIslandHandler.isRaining()) {
+ if (LocationHandler.getCurrentLocation().equals(Locations.HOWLINGCAVE)) {
+ drawSlayer(width, offset, mc);
+ } else drawRainDuration(width, offset, mc);
+ } else if (LocationHandler.getCurrentLocation().equals(Locations.FARMHOUSE)) {
+ drawFarmHouseMedals(width, offset, mc);
+ } else if (SlayerHandler.isDoingSlayer) {
+ drawSlayer(width, offset, mc);
+ }
+ }
+
+ public void drawSeasonAndDate(int width, int offset, Minecraft mc) {
+ if (SeasonDateHandler.getCurrentSeason() != Season.ERROR) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ if (mc.thePlayer.ticksExisted % 100 == 0 && eventToggle) eventToggle = false;
+ if (mc.thePlayer.ticksExisted % 600 == 0) eventToggle = true;
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ String dateText = SeasonDateHandler.getFancySeasonAndDate();
+ if (eventToggle && !SeasonDateHandler.getCurrentEvent().isEmpty() && !SeasonDateHandler.getCurrentEventTime().isEmpty()) dateText = SeasonDateHandler.getCurrentEvent().trim() + " " + SeasonDateHandler.getCurrentEventTime().trim();
+ drawTexturedModalRect((width / 2) + 17, offset + (bossBarVisible ? 20 : 3), 2, 34, font.getStringWidth(dateText) + 9, 14);
+ drawTexturedModalRect(((width / 2) + 17) + font.getStringWidth(dateText) + 9, offset + (bossBarVisible ? 20 : 3), 252, 34, 4, 14);
+ drawTexturedModalRect(((width / 2) + 17) + font.getStringWidth(dateText) + 2, offset + (bossBarVisible ? 23 : 6), SeasonDateHandler.getCurrentSeason().getTextureX(), 16, 8, 8);
+ drawString(font, dateText, (width / 2) + 18, offset + (bossBarVisible ? 23 : 6), 0xffffff);
+ }
+ }
+
+ public void drawLocation(int width, int offset, Minecraft mc) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(LocationHandler.getCurrentLocation().getDisplayName())), offset + (bossBarVisible ? 20 : 3), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(LocationHandler.getCurrentLocation().getDisplayName()))) + 2, offset + (bossBarVisible ? 20 : 3), 2, 34, font.getStringWidth(LocationHandler.getCurrentLocation().getDisplayName()) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(LocationHandler.getCurrentLocation().getDisplayName()))) + 4, offset + (bossBarVisible ? 23 : 6), LocationHandler.getCurrentLocation().getCategory().getTexturePos(), 8, 8, 8);
+ drawString(font, LocationHandler.getCurrentLocation().getDisplayName(), (width / 2) - 19 - (font.getStringWidth(LocationHandler.getCurrentLocation().getDisplayName())), offset + (bossBarVisible ? 23 : 6), 0xFFFFFF);
+ }
+
+ public void drawRedstone(int width, int offset, Minecraft mc) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int redstoneColor = IslandHandler.redstone > 90 ? 0xFF0000 : IslandHandler.redstone > 75 ? 0xC45B00 : IslandHandler.redstone > 50 ? 0xFFFF55 : 0x55FF55;
+ if (IslandHandler.redstone > 0 && Utils.isPlayerHoldingRedstone(mc.thePlayer)) {
+ drawTexturedModalRect((width / 2) - 15, offset + (bossBarVisible ? 51 : 34), 0, 48, 30, 18);
+ drawTexturedModalRect((width / 2) - 4, offset + (bossBarVisible ? 51 : 34), 59, 0, 8, 8);
+ drawCenteredString(mc.fontRendererObj, IslandHandler.redstone + "%", (width / 2), offset + (bossBarVisible ? 58 : 41), redstoneColor);
+ }
+ }
+
+ public void drawPurseAndBits(int width, int offset, Minecraft mc) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int xPos = (width / 2) + 17;
+
+ //COINS
+ drawTexturedModalRect(xPos, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(CurrencyHandler.getCoinsFormatted()) + 11, 14);
+ drawTexturedModalRect(xPos + 1, offset + (bossBarVisible ? 37 : 20), 34, 0, 8, 8);
+ drawString(font, CurrencyHandler.getCoinsFormatted(), xPos + 10, offset + (bossBarVisible ? 38 : 21), 0xFFAA00);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ xPos += font.getStringWidth(CurrencyHandler.getCoinsFormatted()) + 11;
+
+ //BITS
+ if (CurrencyHandler.getBits() > 0) {
+ drawTexturedModalRect(xPos, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(CurrencyHandler.getBitsFormatted()) + 11, 14);
+ drawTexturedModalRect(xPos + 1, offset + (bossBarVisible ? 37 : 20), 75, 0, 8, 8);
+ drawString(font, CurrencyHandler.getBitsFormatted(), xPos + 10, offset + (bossBarVisible ? 38 : 21), 0x55FFFF);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ xPos += font.getStringWidth(CurrencyHandler.getBitsFormatted()) + 11;
+ }
+
+ drawTexturedModalRect(xPos, offset + (bossBarVisible ? 35 : 18), 252, 34, 4, 14);
+ }
+
+ public void drawFlightDuration(int width, int offset, Minecraft mc) {
+ if (LocationHandler.getCurrentLocation().equals(Locations.YOURISLAND)) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ DecimalFormat flightFormat = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.CANADA));
+ String duration;
+ if (IslandHandler.flightTime < 60) duration = IslandHandler.flightTime + "s"; else if (IslandHandler.flightTime < 3600) duration = flightFormat.format((double) IslandHandler.flightTime / 60) + "m"; else if (IslandHandler.flightTime < 86400) duration = flightFormat.format((double) IslandHandler.flightTime / 3600) + "hr"; else if (IslandHandler.flightTime < 86460) duration = flightFormat.format((double) IslandHandler.flightTime / 86400) + "day"; else duration = flightFormat.format((double) IslandHandler.flightTime / 86400) + "days";
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(duration) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 4, offset + (bossBarVisible ? 38 : 21), 67, 0, 8, 8);
+ drawString(font, duration, (width / 2) - 19 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 38 : 21), 0xFFFFFF);
+ }
+ }
+
+ public void drawRainDuration(int width, int offset, Minecraft mc) {
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.PARK)) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ String duration = "Rain: " + ParkIslandHandler.getRainTime();
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(duration) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 4, offset + (bossBarVisible ? 38 : 21), 83, 0, 8, 8);
+ drawString(font, duration, (width / 2) - 19 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 38 : 21), 0xFFFFFF);
+ }
+ }
+
+ public void drawSlayer(int width, int offset, Minecraft mc) {
+ if (SlayerHandler.isDoingSlayer) {
+ int kills = SlayerHandler.progress;
+ int maxKills = SlayerHandler.maxKills;
+ int tier = SlayerHandler.slayerTier;
+ SlayerHandler.slayerTypes slayerType = SlayerHandler.currentSlayer;
+ if (slayerType != SlayerHandler.slayerTypes.NONE) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(EnumChatFormatting.GREEN);
+ stringBuilder.append(Utils.intToRomanNumeral(tier));
+ stringBuilder.append(" ");
+ if (SlayerHandler.isKillingBoss) {
+ stringBuilder.append(EnumChatFormatting.RED);
+ stringBuilder.append("Slay Boss!");
+ } else if (SlayerHandler.bossSlain) {
+ stringBuilder.append(EnumChatFormatting.RED);
+ stringBuilder.append("Boss Slain!");
+ } else if (kills == 0 && maxKills == 0) {
+ stringBuilder.append(EnumChatFormatting.RED);
+ stringBuilder.append("Not Slaying!");
+ } else {
+ stringBuilder.append(EnumChatFormatting.YELLOW);
+ stringBuilder.append(kills);
+ stringBuilder.append(EnumChatFormatting.GRAY);
+ stringBuilder.append("/");
+ stringBuilder.append(EnumChatFormatting.RED);
+ stringBuilder.append(maxKills);
+ }
+ String text = stringBuilder.toString();
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(text)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(text))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(text) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(text))) + 4, offset + (bossBarVisible ? 38 : 21), slayerType.getX(), 24, 8, 8);
+ drawString(font, text, (width / 2) - 19 - (font.getStringWidth(text)), offset + (bossBarVisible ? 38 : 21), 0xFFFFFF);
+ }
+ }
+ }
+
+ public void drawMiningPowders(int width, int offset, Minecraft mc) {
+ if (MinesHandler.gemstone == 0) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ String mithril = MinesHandler.getMithrilFormatted();
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(mithril)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(mithril))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(mithril) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(mithril))) + 4, offset + (bossBarVisible ? 38 : 21), 91, 0, 8, 8);
+ drawString(font, mithril, (width / 2) - 19 - (font.getStringWidth(mithril)), offset + (bossBarVisible ? 38 : 21), 0x00C896);
+ } else {
+ LocationCategory locationCategory = LocationHandler.getCurrentLocation().getCategory();
+ String mithril = locationCategory == LocationCategory.DWARVENMINES ? MinesHandler.getMithrilFormatted() : MinesHandler.getMithrilShortFormatted();
+ String gemstone = locationCategory == LocationCategory.CRYSTALHOLLOWS ? MinesHandler.getGemstoneFormatted() : MinesHandler.getGemstoneShortFormatted();
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+
+ int edge = (width / 2) - 33;
+
+ int barWidth = font.getStringWidth(mithril) + 12 + font.getStringWidth(gemstone);
+
+ int firstText = locationCategory == LocationCategory.DWARVENMINES ? font.getStringWidth(mithril) : font.getStringWidth(gemstone);
+
+ //Bar
+ drawTexturedModalRect(edge - barWidth, offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(edge - barWidth + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, barWidth + 14, 14);
+
+ //Icons
+ drawTexturedModalRect(edge - barWidth + 4, offset + (bossBarVisible ? 38 : 21), locationCategory == LocationCategory.DWARVENMINES ? 91 : 131, 0, 8, 8);
+ drawTexturedModalRect(edge - barWidth + 16 + firstText, offset + (bossBarVisible ? 38 : 21), locationCategory == LocationCategory.DWARVENMINES ? 131 : 91, 0, 8, 8);
+
+ drawString(font, locationCategory == LocationCategory.DWARVENMINES ? mithril : gemstone, edge - barWidth + 14, offset + (bossBarVisible ? 38 : 21), locationCategory == LocationCategory.DWARVENMINES ? 0x00C896 : 0xFF55FF);
+ drawString(font, locationCategory == LocationCategory.DWARVENMINES ? gemstone : mithril, edge - barWidth + 26 + firstText, offset + (bossBarVisible ? 38 : 21), locationCategory == LocationCategory.DWARVENMINES ? 0xFF55FF : 0x00C896);
+ }
+ }
+
+ public void drawTrapperOrPelts(int width, int offset, Minecraft mc) {
+ if (LocationHandler.getCurrentLocation().getCategory().equals(LocationCategory.MUSHROOMDESERT)) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ String duration = FarmingIslandHandler.location != Locations.NONE ? FarmingIslandHandler.location.getDisplayName() : "" + FarmingIslandHandler.pelts;
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(duration) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 4, offset + (bossBarVisible ? 38 : 21), FarmingIslandHandler.location != Locations.NONE ? 123 : 115, 0, 8, 8);
+ drawString(font, duration, (width / 2) - 19 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 38 : 21), 0xFFFFFF);
+ }
+ }
+
+ public void drawDwarvenEvent(int width, int offset, Minecraft mc) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ if (MinesHandler.eventMax > 0) {
+ String duration = MinesHandler.currentEvent.needsMax ? MinesHandler.eventProgress + "/" + MinesHandler.eventMax : String.valueOf(MinesHandler.eventProgress);
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(duration) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(duration))) + 4, offset + (bossBarVisible ? 38 : 21), MinesHandler.currentEvent.x, 0, 8, 8);
+ drawString(font, duration, (width / 2) - 19 - (font.getStringWidth(duration)), offset + (bossBarVisible ? 38 : 21), 0xFFFFFF);
+ } else if (!MinesHandler.currentEvent.needsMax) {
+ drawSingleEvent(width, offset, mc);
+ } else {
+ String text = MinesHandler.currentEvent.displayName;
+ drawTexturedModalRect((width / 2) - 33 - (font.getStringWidth(text)), offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(text))) + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, font.getStringWidth(text) + 14, 14);
+ drawTexturedModalRect(((width / 2) - 33 - (font.getStringWidth(text))) + 4, offset + (bossBarVisible ? 38 : 21), MinesHandler.currentEvent.x, 0, 8, 8);
+ drawString(font, text, (width / 2) - 19 - (font.getStringWidth(text)), offset + (bossBarVisible ? 38 : 21), 0xFFFFFF);
+ }
+ }
+
+ public void drawSingleEvent(int width, int offset, Minecraft mc) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ drawTexturedModalRect((width / 2) - 15, offset + (bossBarVisible ? 51 : 34), 0, 52, 30, 14);
+ drawTexturedModalRect((width / 2) - 10, offset + (bossBarVisible ? 53 : 36), MinesHandler.currentEvent.x, 0, 8, 8);
+ drawString(mc.fontRendererObj, MinesHandler.eventProgress + "", (width / 2), offset + (bossBarVisible ? 53 : 36), 0xffffff);
+ }
+
+ public void drawFarmHouseMedals(int width, int offset, Minecraft mc) {
+ if (LocationHandler.getCurrentLocation().equals(Locations.FARMHOUSE)) {
+ int bronze = font.getStringWidth(FarmHouseHandler.getFormattedMedals(FarmHouseHandler.Medal.BRONZE));
+ int silver = font.getStringWidth(FarmHouseHandler.getFormattedMedals(FarmHouseHandler.Medal.SILVER));
+ int gold = font.getStringWidth(FarmHouseHandler.getFormattedMedals(FarmHouseHandler.Medal.GOLD));
+
+ int end = drawLeftBottomBar(width, offset, 40 + bronze + silver + gold, mc);
+ drawTexturedModalRect(end + 2, offset + (bossBarVisible ? 38 : 21), 139, 0, 8, 8);
+ drawTexturedModalRect(end + 14 + gold, offset + (bossBarVisible ? 38 : 21), 147, 0, 8, 8);
+ drawTexturedModalRect(end + 26 + gold + silver, offset + (bossBarVisible ? 38 : 21), 155, 0, 8, 8);
+
+ drawString(font, FarmHouseHandler.getFormattedMedals(FarmHouseHandler.Medal.GOLD), end + 12, offset + (bossBarVisible ? 38 : 21), 0xffffff);
+ drawString(font, FarmHouseHandler.getFormattedMedals(FarmHouseHandler.Medal.SILVER), end + gold + 24, offset + (bossBarVisible ? 38 : 21), 0xffffff);
+ drawString(font, FarmHouseHandler.getFormattedMedals(FarmHouseHandler.Medal.BRONZE), end + gold + silver + 36, offset + (bossBarVisible ? 38 : 21), 0xffffff);
+ }
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard())) {
+ bossBarVisible = BossStatus.statusBarTime > 0 && GuiIngameForge.renderBossHealth && BossbarHandler.bossBarRendered;
+ Minecraft mc = Minecraft.getMinecraft();
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ if (LocationHandler.getCurrentLocation() != Locations.CATACOMBS && !SkyblockHud.config.main.disaleMainHud) {
+ drawClock(event.resolution.getScaledWidth(), SkyblockHud.config.main.mainHudPos.getAbsY(event.resolution, 34), mc);
+ }
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ }
+ }
+
+ public void drawScaledString(float factor, int x, int y, String text, int color) {
+ GlStateManager.scale(factor, factor, 1);
+ drawCenteredString(font, text, (int) (x / factor), (int) (y / factor), color);
+ GlStateManager.scale(1 / factor, 1 / factor, 1);
+ }
+
+ public int drawLeftBottomBar(int width, int offset, int barWidth, Minecraft mc) {
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ mc.renderEngine.bindTexture(Textures.texture.stats);
+ int edge = (width / 2) - 17;
+
+ drawTexturedModalRect(edge - barWidth, offset + (bossBarVisible ? 35 : 18), 0, 34, 2, 14);
+ drawTexturedModalRect(edge - barWidth + 2, offset + (bossBarVisible ? 35 : 18), 2, 34, barWidth - 2, 14);
+ return edge - barWidth + 2;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/overlay/RPGHud.java b/src/main/java/com/thatgravyboat/skyblockhud/overlay/RPGHud.java
new file mode 100644
index 000000000..1c3cbb0ef
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/overlay/RPGHud.java
@@ -0,0 +1,112 @@
+package com.thatgravyboat.skyblockhud.overlay;
+
+import com.mojang.realmsclient.gui.ChatFormatting;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.handlers.HeldItemHandler;
+import com.thatgravyboat.skyblockhud.textures.Textures;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class RPGHud extends Gui {
+
+ private static int mana, maxMana, overflow = 0;
+ private static int health, maxHealth = 0;
+ private static int defense = 0;
+
+ public static void updateMana(int current, int max) {
+ mana = current;
+ maxMana = max;
+ }
+
+ public static void updateOverflow(int current) {
+ overflow = current;
+ }
+
+ public static void updateHealth(int current, int max) {
+ health = current;
+ maxHealth = max;
+ }
+
+ public static void updateDefense(int input) {
+ defense = input;
+ }
+
+ public static void manaPredictionUpdate(boolean isIncrease, int decrease) {
+ mana = isIncrease ? Math.min(mana + (maxMana / 50), maxMana) : mana - decrease;
+ }
+
+ private static final DecimalFormat decimalFormat = new DecimalFormat("#.##");
+
+ static {
+ decimalFormat.setGroupingUsed(true);
+ decimalFormat.setGroupingSize(3);
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard(), SkyblockHud.config.rpg.showRpgHud)) {
+ Minecraft mc = Minecraft.getMinecraft();
+ GlStateManager.enableBlend();
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
+ FontRenderer font = mc.fontRendererObj;
+ if (mc.thePlayer.getHealth() < mc.thePlayer.getMaxHealth()) {
+ health = Math.max((int) (maxHealth * (mc.thePlayer.getHealth() / mc.thePlayer.getMaxHealth())), health);
+ }
+
+ mc.renderEngine.bindTexture(Textures.texture.playerStats);
+ Position position = SkyblockHud.config.rpg.rpgHudPosition;
+
+ int x = position.getAbsX(event.resolution, 120);
+ int y = position.getAbsY(event.resolution, 47);
+
+ boolean rightAligned = position.rightAligned(event.resolution, 120) && SkyblockHud.config.rpg.flipHud;
+
+ drawTexturedModalRect(x, y, rightAligned ? 131 : 5, 6, 120, 47);
+
+ float manaWidth = Math.min(57 * ((float) mana / (float) maxMana), 57);
+ int manaX = rightAligned ? x + 16 : 47 + x;
+ if (HeldItemHandler.hasManaCost(mc.thePlayer.getHeldItem())) {
+ int manaCost = HeldItemHandler.getManaCost(mc.thePlayer.getHeldItem());
+ drawTexturedModalRect(manaX, 17 + y, rightAligned ? 199 : 0, manaCost > mana ? 96 : 64, (int) manaWidth, 4);
+ if (manaCost <= mana) {
+ drawTexturedModalRect(manaX, 17 + y, rightAligned ? 199 : 0, 92, Utils.lerp((float) manaCost / (float) maxMana, 0, 57), 4);
+ }
+ } else {
+ drawTexturedModalRect(manaX, 17 + y, rightAligned ? 199 : 0, 64, (int) manaWidth, 4);
+ }
+
+ float healthWidth = Math.min(70 * ((float) health / (float) maxHealth), 70);
+ int healthX = rightAligned ? x + 3 : 47 + x;
+ drawTexturedModalRect(healthX, 22 + y, rightAligned ? 186 : 0, 68, (int) healthWidth, 5);
+
+ if (health > maxHealth) {
+ float absorptionWidth = Math.min(70 * ((float) (health - maxHealth) / (float) maxHealth), 70);
+ drawTexturedModalRect(healthX, 22 + y, rightAligned ? 186 : 0, 77, (int) absorptionWidth, 5);
+ }
+
+ drawTexturedModalRect(rightAligned ? x + 7 : 45 + x, 28 + y, rightAligned ? 189 : 0, 73, Utils.lerp(mc.thePlayer.experience, 0, 67), 4);
+ //Air in water
+ NumberFormat myFormat = NumberFormat.getInstance();
+ myFormat.setGroupingUsed(true);
+ if (mc.thePlayer.getAir() < 300) {
+ drawTexturedModalRect(rightAligned ? x + 17 : 39 + x, 33 + y, rightAligned ? 192 : 0, 82, 64, 6);
+ drawTexturedModalRect(rightAligned ? x + 19 : 41 + x, 33 + y, rightAligned ? 196 : 0, 88, Utils.lerp(mc.thePlayer.getAir() / 300f, 0, 60), 4);
+ }
+
+ Utils.drawStringScaled("" + mc.thePlayer.experienceLevel, font, (rightAligned ? 112 : 14) + x - (font.getStringWidth("" + mc.thePlayer.experienceLevel) / 2f), 34 + y, false, 8453920, 0.75f);
+
+ Utils.drawStringScaled(ChatFormatting.RED + " \u2764 " + health + "/" + maxHealth, font, (rightAligned ? 10 : 42) + x, 8 + y, true, 0xffffff, 0.75f);
+
+ GlStateManager.color(255, 255, 255);
+ GlStateManager.disableBlend();
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/playerstats/ActionBarParsing.java b/src/main/java/com/thatgravyboat/skyblockhud/playerstats/ActionBarParsing.java
new file mode 100644
index 000000000..f82c2f2e9
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/playerstats/ActionBarParsing.java
@@ -0,0 +1,152 @@
+package com.thatgravyboat.skyblockhud.playerstats;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.overlay.MiningHud;
+import com.thatgravyboat.skyblockhud.overlay.RPGHud;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.IChatComponent;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+public class ActionBarParsing {
+
+ private static String lastActionBar = "";
+ public static String lastLowActionBar = "";
+ private static IChatComponent lastLowEditedActionBar = null;
+
+ private static final Pattern HealthRegex = Pattern.compile("([0-9]+)/([0-9]+)\u2764");
+ private static final Pattern HealingRegex = Pattern.compile("\\+([0-9]+)[\u2586\u2585\u2584\u2583\u2582\u2581]");
+ private static final Pattern DefenseRegex = Pattern.compile("([0-9]+)\u2748 Defense");
+ private static final Pattern ManaRegex = Pattern.compile("([0-9]+)/([0-9]+)\u270E Mana");
+ private static final Pattern ManaOverflowRegex = Pattern.compile("([0-9]+)/([0-9]+)\u270E ([0-9]+)\u02AC");
+ private static final Pattern ManaDecreaseRegex = Pattern.compile("-([0-9]+) Mana \\(");
+ private static final Pattern DrillFuelRegex = Pattern.compile("([0-9,]+)/([0-9,]+k) Drill Fuel");
+ private static final Pattern XpGainRegex = Pattern.compile("\\+(\\d*\\.?\\d*) (Farming|Mining|Combat|Foraging|Fishing|Enchanting|Alchemy|Carpentry|Runecrafting) \\((\\d*\\.?\\d*)%\\)");
+
+ private static final Pattern HealthReplaceRegex = Pattern.compile("\u00A7c([0-9]+)/([0-9]+)\u2764");
+ private static final Pattern HealingReplaceRegex = Pattern.compile("\\+\u00A7c([0-9]+)[\u2586\u2585\u2584\u2583\u2582\u2581]");
+ private static final Pattern HealthAbsorptionReplaceRegex = Pattern.compile("\u00A76([0-9]+)/([0-9]+)\u2764");
+ private static final Pattern DefenseReplaceRegex = Pattern.compile("\u00A7a([0-9]+)\u00A7a\u2748 Defense");
+ private static final Pattern ManaReplaceRegex = Pattern.compile("\u00A7b([0-9]+)/([0-9]+)\u270E Mana");
+ private static final Pattern ManaOverflowReplaceRegex = Pattern.compile("\u00A7b([0-9]+)/([0-9]+)\u270E \u00A73([0-9]+)\u02AC");
+ private static final Pattern DrillFuelReplaceRegex = Pattern.compile("\u00A72([0-9,]+)/([0-9,]+k) Drill Fuel");
+
+ private static int ticksSinceLastPrediction = 0;
+ private static boolean predict = false;
+
+ @SubscribeEvent
+ public void tick(TickEvent.ClientTickEvent event) {
+ if (predict) {
+ ticksSinceLastPrediction++;
+ if (ticksSinceLastPrediction == 20 && SkyblockHud.config.rpg.showRpgHud) {
+ ticksSinceLastPrediction = 0;
+ RPGHud.manaPredictionUpdate(true, 0);
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onStatusBarHigh(ClientChatReceivedEvent event) {
+ if (event.type == 2) {
+ if (SkyblockHud.hasSkyblockScoreboard() && SkyblockHud.config.rpg.showRpgHud) {
+ parseActionBar(event.message.getUnformattedText());
+ }
+ if (SkyblockHud.config.mining.showDrillBar) {
+ String bar = Utils.removeColor(event.message.getUnformattedText());
+ Matcher DrillFuelMatcher = DrillFuelRegex.matcher(bar);
+ if (DrillFuelMatcher.find()) {
+ try {
+ MiningHud.setFuel(Integer.parseInt(DrillFuelMatcher.group(1).replace(",", "")), Integer.parseInt(DrillFuelMatcher.group(2).replace("k", "")) * 1000);
+ } catch (Exception ignored) {
+ MiningHud.setFuel(0, 0);
+ }
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOW)
+ public void onStatusBarLow(ClientChatReceivedEvent event) {
+ if (event.type == 2) {
+ if (SkyblockHud.hasSkyblockScoreboard() && SkyblockHud.config.rpg.showRpgHud) {
+ String message = event.message.getUnformattedText();
+ if (lastLowEditedActionBar == null || !lastLowActionBar.equals(message)) {
+ lastLowActionBar = message;
+ message = HealthReplaceRegex.matcher(message).replaceAll("");
+ message = HealthAbsorptionReplaceRegex.matcher(message).replaceAll("");
+ message = DefenseReplaceRegex.matcher(message).replaceAll("");
+ message = ManaReplaceRegex.matcher(message).replaceAll("");
+ Matcher overflowMatcher = ManaOverflowReplaceRegex.matcher(message);
+ if (overflowMatcher.find() && SkyblockHud.config.renderer.addOverflowMana) {
+ message = overflowMatcher.replaceAll("\u00A73\u02AC " + overflowMatcher.group(3));
+ }
+
+ lastLowEditedActionBar = new ChatComponentText(message.trim());
+ }
+ event.message = lastLowEditedActionBar;
+ }
+ if (SkyblockHud.config.mining.showDrillBar) {
+ event.message = new ChatComponentText(DrillFuelReplaceRegex.matcher(event.message.getUnformattedText()).replaceAll("").trim());
+ }
+ }
+ }
+
+ public static void parseActionBar(String input) {
+ if (!lastActionBar.equals(input)) {
+ lastActionBar = input;
+ String bar = Utils.removeColor(input);
+
+ Matcher HealthMatcher = HealthRegex.matcher(bar);
+ Matcher DefenseMatcher = DefenseRegex.matcher(bar);
+ Matcher ManaMatcher = ManaRegex.matcher(bar);
+ Matcher ManaUseMatcher = ManaDecreaseRegex.matcher(bar);
+ Matcher ManaOverflowMatcher = ManaOverflowRegex.matcher(bar);
+ Matcher XpGainMatcher = XpGainRegex.matcher(bar);
+
+ boolean healthFound = HealthMatcher.find();
+ boolean defenseFound = DefenseMatcher.find();
+ boolean manaFound = ManaMatcher.find();
+ boolean manaUseFound = ManaUseMatcher.find();
+ boolean manaOverflowFound = ManaOverflowMatcher.find();
+ boolean xpFound = XpGainMatcher.find();
+
+ if (healthFound) {
+ try {
+ RPGHud.updateHealth(Integer.parseInt(HealthMatcher.group(1)), Integer.parseInt(HealthMatcher.group(2)));
+ } catch (Exception ignored) {}
+ }
+ if (defenseFound) {
+ try {
+ RPGHud.updateDefense(Integer.parseInt(DefenseMatcher.group(1)));
+ } catch (Exception ignored) {}
+ } else if (!xpFound && !manaUseFound) {
+ RPGHud.updateDefense(0);
+ }
+ if (manaFound) {
+ try {
+ RPGHud.updateMana(Integer.parseInt(ManaMatcher.group(1)), Integer.parseInt(ManaMatcher.group(2)));
+ } catch (Exception ignored) {}
+ }
+ if (!manaFound && manaOverflowFound) {
+ try {
+ RPGHud.updateMana(Integer.parseInt(ManaOverflowMatcher.group(1)), Integer.parseInt(ManaOverflowMatcher.group(2)));
+ RPGHud.updateOverflow(Integer.parseInt(ManaOverflowMatcher.group(3)));
+ } catch (Exception ignored) {}
+ }
+ if (!manaFound) {
+ if (manaUseFound) {
+ try {
+ RPGHud.manaPredictionUpdate(false, Integer.parseInt(ManaUseMatcher.group(1)));
+ } catch (Exception ignored) {}
+ }
+ RPGHud.manaPredictionUpdate(true, 0);
+ }
+ predict = !manaFound;
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/seasons/Season.java b/src/main/java/com/thatgravyboat/skyblockhud/seasons/Season.java
new file mode 100644
index 000000000..da14204da
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/seasons/Season.java
@@ -0,0 +1,53 @@
+package com.thatgravyboat.skyblockhud.seasons;
+
+public enum Season {
+ EARLYSPRING("earlyspring", "Early Spring", 34, 0),
+ SPRING("spring", "Spring", 34, 31),
+ LATESPRING("latespring", "Late Spring", 34, 62),
+ EARLYSUMMER("earlysummer", "Early Summer", 42, 93),
+ SUMMER("summer", "Summer", 42, 124),
+ LATESUMMER("latesummer", "Late Summer", 42, 155),
+ EARLYAUTUMN("earlyautumn", "Early Autumn", 50, 186),
+ AUTUMN("autumn", "Autumn", 50, 217),
+ LATEAUTUMN("lateautumn", "Late Autumn", 50, 248),
+ EARLYWINTER("earlywinter", "Early Winter", 58, 279),
+ WINTER("winter", "Winter", 58, 310),
+ LATEWINTER("latewinter", "Late Winter", 58, 341),
+ ERROR("error", "Error", 0, -1);
+
+ private final String name;
+ private final String displayName;
+ private final int textureX;
+ private final int yearStartDay;
+
+ Season(String name, String displayName, int textureX, int yearStartDay) {
+ this.name = name;
+ this.displayName = displayName;
+ this.textureX = textureX;
+ this.yearStartDay = yearStartDay;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ public int getTextureX() {
+ return this.textureX;
+ }
+
+ public int getYearStartDay() {
+ return yearStartDay;
+ }
+
+ public static Season get(String id) {
+ try {
+ return Season.valueOf(id);
+ } catch (IllegalArgumentException ex) {
+ return ERROR;
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/seasons/SeasonDateHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/seasons/SeasonDateHandler.java
new file mode 100644
index 000000000..02cc490ab
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/seasons/SeasonDateHandler.java
@@ -0,0 +1,73 @@
+package com.thatgravyboat.skyblockhud.seasons;
+
+import com.thatgravyboat.skyblockhud.api.events.SidebarLineUpdateEvent;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.regex.Pattern;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class SeasonDateHandler {
+
+ private static Season currentSeason = Season.ERROR;
+ private static int currentDate = 1;
+ private static String currentEvent = "";
+ private static String eventTime = "";
+
+ @SubscribeEvent
+ public void onSidebarLineUpdate(SidebarLineUpdateEvent event) {
+ if (Season.get(SeasonDateHandler.removeDate(event.formattedLine.toLowerCase()).toUpperCase()) != Season.ERROR) {
+ SeasonDateHandler.setCurrentDateAndSeason(SeasonDateHandler.removeSeason(Utils.removeColor(event.formattedLine.toLowerCase().trim())), SeasonDateHandler.removeDate(Utils.removeColor(event.formattedLine.toLowerCase().trim())).toUpperCase());
+ }
+ }
+
+ public static void setCurrentDateAndSeason(int date, String season) {
+ currentDate = date;
+ currentSeason = Season.get(season);
+ }
+
+ public static void setCurrentEvent(String event, String time) {
+ currentEvent = event;
+ eventTime = time;
+ }
+
+ public static Season getCurrentSeason() {
+ return currentSeason;
+ }
+
+ public static int getCurrentDate() {
+ return currentDate;
+ }
+
+ private static String getDataSuffix(int date) {
+ if (date > 10 && date < 14) return "th";
+ switch (date % 10) {
+ case 1:
+ return "st";
+ case 2:
+ return "nd";
+ case 3:
+ return "rd";
+ default:
+ return "th";
+ }
+ }
+
+ public static String getFancySeasonAndDate() {
+ return (currentSeason.getDisplayName() + " " + currentDate + getDataSuffix(currentDate));
+ }
+
+ public static String getCurrentEvent() {
+ return currentEvent;
+ }
+
+ public static String getCurrentEventTime() {
+ return eventTime;
+ }
+
+ public static String removeDate(String seasonDate) {
+ return Pattern.compile("[^a-zA-Z]").matcher(seasonDate.toLowerCase()).replaceAll("").replaceAll("st|nd|rd|th", "").trim();
+ }
+
+ public static int removeSeason(String seasonDate) {
+ return Integer.parseInt(Pattern.compile("[^0-9]").matcher(seasonDate.toLowerCase()).replaceAll("").trim());
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/textures/TextureObject.java b/src/main/java/com/thatgravyboat/skyblockhud/textures/TextureObject.java
new file mode 100644
index 000000000..6403e18bc
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/textures/TextureObject.java
@@ -0,0 +1,37 @@
+package com.thatgravyboat.skyblockhud.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("skyblockhud", path);
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/textures/Textures.java b/src/main/java/com/thatgravyboat/skyblockhud/textures/Textures.java
new file mode 100644
index 000000000..c77bd40f8
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/textures/Textures.java
@@ -0,0 +1,58 @@
+package com.thatgravyboat.skyblockhud.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 com.thatgravyboat.skyblockhud.SkyblockHud;
+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;
+ SkyblockHud.config.misc.style = 0;
+ } 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("skyblockhud: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) {}
+
+ if (SkyblockHud.config != null) setTexture(SkyblockHud.config.misc.style);
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerFileLoader.java b/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerFileLoader.java
new file mode 100644
index 000000000..7401dd8c1
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerFileLoader.java
@@ -0,0 +1,136 @@
+package com.thatgravyboat.skyblockhud.tracker;
+
+import com.google.gson.*;
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Locale;
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ResourceLocation;
+
+public class TrackerFileLoader {
+
+ private static final Gson gson = new GsonBuilder().create();
+
+ private static void loadTrackers(JsonObject object) {
+ for (JsonElement element : object.get("trackers").getAsJsonArray()) {
+ JsonObject tracker = element.getAsJsonObject();
+ EnumSet<Locations> locations = EnumSet.noneOf(Locations.class);
+ tracker
+ .get("location")
+ .getAsJsonArray()
+ .forEach(l -> {
+ Locations location = Locations.get(l.getAsString().toUpperCase(Locale.ENGLISH));
+ if (location != Locations.DEFAULT) {
+ locations.add(location);
+ }
+ });
+ if (tracker.has("drops")) {
+ for (JsonElement drop : tracker.get("drops").getAsJsonArray()) {
+ TrackerHandler.trackerObjects.add(new TrackerObject(drop.getAsJsonObject(), locations));
+ }
+ }
+ if (tracker.has("mobs")) {
+ for (JsonElement mob : tracker.get("mobs").getAsJsonArray()) {
+ TrackerHandler.trackerObjects.add(new TrackerObject(mob.getAsJsonObject(), locations));
+ }
+ }
+ }
+
+ for (TrackerObject trackerObject : TrackerHandler.trackerObjects) {
+ for (Locations location : trackerObject.getLocations()) {
+ if (TrackerHandler.trackers.containsKey(location)) {
+ TrackerHandler.trackers.get(location).put(trackerObject.getInternalId(), trackerObject);
+ } else {
+ HashMap<String, TrackerObject> value = new HashMap<>();
+ value.put(trackerObject.getInternalId(), trackerObject);
+ TrackerHandler.trackers.put(location, value);
+ }
+ }
+ }
+ }
+
+ public static void loadTrackersFile() {
+ TrackerHandler.trackers.clear();
+ TrackerHandler.trackerObjects.clear();
+ try {
+ ResourceLocation trackers = new ResourceLocation("skyblockhud:data/trackers.json");
+ InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(trackers).getInputStream();
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ loadTrackers(gson.fromJson(reader, JsonObject.class));
+ }
+ } catch (Exception ignored) {}
+ }
+
+ private static JsonElement getTrackerFile() {
+ JsonArray stats = new JsonArray();
+ TrackerHandler.trackerObjects.forEach(trackerObject -> {
+ if (trackerObject.getCount() > 0) {
+ JsonObject jsonObject = new JsonObject();
+ JsonArray locations = new JsonArray();
+ trackerObject.getLocations().forEach(l -> locations.add(new JsonPrimitive(l.toString().toUpperCase(Locale.ENGLISH))));
+ jsonObject.add("id", new JsonPrimitive(trackerObject.getInternalId()));
+ jsonObject.add("locations", locations);
+ jsonObject.add("count", new JsonPrimitive(trackerObject.getCount()));
+ stats.add(jsonObject);
+ }
+ });
+ return stats;
+ }
+
+ public static boolean loadTrackerStatsFile() {
+ File configFile = new File(SkyblockHud.configDirectory, "sbh-trackers-stats.json");
+
+ try {
+ if (configFile.createNewFile()) {
+ return true;
+ }
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8))) {
+ JsonObject json = gson.fromJson(reader, JsonObject.class);
+ if (json.has("trackerStats")) {
+ json
+ .getAsJsonArray("trackerStats")
+ .forEach(element -> {
+ if (element.isJsonObject()) {
+ JsonObject object = element.getAsJsonObject();
+ JsonArray locations = object.get("locations").getAsJsonArray();
+ Locations firstLocation = null;
+ for (JsonElement location : locations) {
+ firstLocation = Locations.get(location.getAsString());
+ if (!firstLocation.equals(Locations.DEFAULT)) break;
+ }
+
+ if (firstLocation != null && !firstLocation.equals(Locations.DEFAULT)) {
+ TrackerHandler.trackers.get(firstLocation).get(object.get("id").getAsString()).setCount(object.get("count").getAsInt());
+ }
+ }
+ });
+
+ TrackerHandler.trackers.forEach((location, map) -> {
+ TrackerHandler.trackers.put(location, TrackerHandler.sortTrackers(map, (entry1, entry2) -> Integer.compare(entry2.getValue().getCount(), entry1.getValue().getCount())));
+ });
+ }
+ }
+ } catch (Exception ignored) {}
+ return false;
+ }
+
+ public static void saveTrackerStatsFile() {
+ File configFile = new File(SkyblockHud.configDirectory, "sbh-trackers-stats.json");
+
+ try {
+ configFile.createNewFile();
+
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(configFile), StandardCharsets.UTF_8))) {
+ JsonObject json = new JsonObject();
+ json.add("trackerStats", getTrackerFile());
+ writer.write(gson.toJson(json));
+ }
+ } catch (IOException ignored) {}
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerHandler.java b/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerHandler.java
new file mode 100644
index 000000000..f8eaba306
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerHandler.java
@@ -0,0 +1,111 @@
+package com.thatgravyboat.skyblockhud.tracker;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import com.thatgravyboat.skyblockhud.api.events.SkyBlockEntityKilled;
+import com.thatgravyboat.skyblockhud.core.config.Position;
+import com.thatgravyboat.skyblockhud.location.LocationHandler;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import com.thatgravyboat.skyblockhud.utils.Utils;
+import java.util.*;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class TrackerHandler extends Gui {
+
+ public static Set<TrackerObject> trackerObjects = new HashSet<>();
+ public static LinkedHashMap<Locations, Map<String, TrackerObject>> trackers = new LinkedHashMap<>();
+
+ public static <K, V> Map<K, V> sortTrackers(Map<K, V> map, Comparator<? super Map.Entry<K, V>> comparator) {
+ List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
+ list.sort(comparator);
+
+ Map<K, V> result = new LinkedHashMap<>();
+ for (Map.Entry<K, V> entry : list) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+
+ return result;
+ }
+
+ public static void onItemAdded(String id, int amount, String specialId, int number) {
+ if (SkyblockHud.hasSkyblockScoreboard() && trackers.containsKey(LocationHandler.getCurrentLocation())) {
+ Map<String, TrackerObject> trackerMap = trackers.get(LocationHandler.getCurrentLocation());
+ String dropId = id;
+ if (specialId != null) {
+ dropId = specialId.toUpperCase() + ";" + number;
+ }
+
+ if (trackerMap != null && trackerMap.containsKey(dropId)) {
+ TrackerObject object = trackerMap.get(dropId);
+ object.increaseCount(amount);
+ trackers.put(LocationHandler.getCurrentLocation(), sortTrackers(trackerMap, (entry1, entry2) -> Integer.compare(entry2.getValue().getCount(), entry1.getValue().getCount())));
+ }
+ }
+ }
+
+ public static void drawItemStack(ItemStack stack, int x, int y) {
+ if (stack == null) return;
+ RenderItem itemRender = Minecraft.getMinecraft().getRenderItem();
+ RenderHelper.enableGUIStandardItemLighting();
+ itemRender.zLevel = -145;
+ itemRender.renderItemAndEffectIntoGUI(stack, x, y);
+ itemRender.zLevel = 0;
+ RenderHelper.disableStandardItemLighting();
+ }
+
+ @SubscribeEvent
+ public void onSbEntityDeath(SkyBlockEntityKilled event) {
+ if (SkyblockHud.hasSkyblockScoreboard() && trackers.containsKey(LocationHandler.getCurrentLocation())) {
+ Map<String, TrackerObject> trackerMap = trackers.get(LocationHandler.getCurrentLocation());
+ if (trackerMap.containsKey("ENTITY:" + event.id)) {
+ TrackerObject object = trackerMap.get("ENTITY:" + event.id);
+ object.increaseCount();
+ trackers.put(LocationHandler.getCurrentLocation(), sortTrackers(trackerMap, (entry1, entry2) -> Integer.compare(entry2.getValue().getCount(), entry1.getValue().getCount())));
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void renderOverlay(RenderGameOverlayEvent.Post event) {
+ if (Utils.overlayShouldRender(event.type, SkyblockHud.hasSkyblockScoreboard(), trackers.containsKey(LocationHandler.getCurrentLocation()), !SkyblockHud.config.trackers.hideTracker)) {
+ Map<String, TrackerObject> tracker = trackers.get(LocationHandler.getCurrentLocation());
+ Minecraft mc = Minecraft.getMinecraft();
+
+ if (tracker != null) {
+ Position pos = SkyblockHud.config.trackers.trackerPosition;
+ int startPos = pos.getAbsX(event.resolution, (tracker.size() >= 6 ? 130 : tracker.size() * 20));
+ int y = pos.getAbsY(event.resolution, (int) (10 + Math.ceil(tracker.size() / 5d) * 20));
+
+ Gui.drawRect(startPos, y, startPos + 130, y + 10, -1072689136);
+ mc.fontRendererObj.drawString("Tracker", startPos + 4, y + 1, 0xffffff, false);
+ y += 10;
+ Gui.drawRect(startPos, y, startPos + (tracker.size() >= 6 ? 130 : (tracker.size() * 20) + 10), (int) (y + (Math.ceil(tracker.size() / 5d) * 20)), 1610612736);
+ int x = startPos + 5;
+ for (TrackerObject object : tracker.values()) {
+ String s = Utils.formattedNumber(object.getCount(), 1000);
+ GlStateManager.disableLighting();
+ GlStateManager.enableDepth();
+ drawItemStack(object.getDisplayStack(), x, y);
+ GlStateManager.disableDepth();
+ GlStateManager.disableBlend();
+ mc.fontRendererObj.drawStringWithShadow(s, (float) (x + 19 - 2 - mc.fontRendererObj.getStringWidth(s)), (float) (y + 9), object.getCount() < 1 ? 16733525 : 16777215);
+ GlStateManager.enableBlend();
+ GlStateManager.enableDepth();
+
+ if ((x - startPos + 5) / 20 == 5) {
+ x = startPos + 5;
+ y += 20;
+ } else {
+ x += 20;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerObject.java b/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerObject.java
new file mode 100644
index 000000000..1c6e54d43
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/tracker/TrackerObject.java
@@ -0,0 +1,97 @@
+package com.thatgravyboat.skyblockhud.tracker;
+
+import com.google.gson.JsonObject;
+import com.thatgravyboat.skyblockhud.location.Locations;
+import java.util.EnumSet;
+import java.util.Locale;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.ResourceLocation;
+
+public class TrackerObject {
+
+ private final ItemStack stack;
+ private final String internalId;
+ private final EnumSet<Locations> locations;
+ private final boolean isEntity;
+ private int count;
+
+ public TrackerObject(JsonObject jsonObject, EnumSet<Locations> locations) {
+ this.stack = decodeToItemStack(jsonObject);
+ this.internalId = jsonObject.get("id").getAsString();
+ this.isEntity = jsonObject.get("id").getAsString().contains("entity:");
+ this.locations = locations;
+ }
+
+ public static ItemStack decodeToItemStack(JsonObject jsonObject) {
+ jsonObject = jsonObject.getAsJsonObject("displayItem");
+ int meta = jsonObject.get("meta").getAsInt();
+ ResourceLocation itemid = new ResourceLocation(jsonObject.get("item").getAsString());
+ ItemStack stack = new ItemStack(Item.itemRegistry.getObject(itemid), 0, meta);
+ if (jsonObject.has("displayName")) stack.setStackDisplayName(jsonObject.get("displayName").getAsString());
+ if (jsonObject.has("skullData") && itemid.getResourcePath().equals("skull") && meta == 3) {
+ stack.setTagInfo("SkullOwner", getSkullTag(jsonObject.getAsJsonObject("skullData")));
+ }
+ if (jsonObject.has("enchanted") && jsonObject.get("enchanted").getAsBoolean()) {
+ stack.setTagInfo("ench", new NBTTagList());
+ }
+ if (!jsonObject.get("id").getAsString().contains("entity:")) {
+ NBTTagCompound extraAttributes = new NBTTagCompound();
+ extraAttributes.setString("id", jsonObject.get("id").getAsString());
+ stack.setTagInfo("ExtraAttributes", extraAttributes);
+ }
+ return stack;
+ }
+
+ public static NBTBase getSkullTag(JsonObject skullObject) {
+ NBTTagCompound skullOwner = new NBTTagCompound();
+ NBTTagCompound properties = new NBTTagCompound();
+ NBTTagList textures = new NBTTagList();
+ NBTTagCompound value = new NBTTagCompound();
+
+ skullOwner.setString("Id", skullObject.get("id").getAsString());
+
+ value.setString("Value", skullObject.get("texture").getAsString());
+ textures.appendTag(value);
+
+ properties.setTag("textures", textures);
+
+ skullOwner.setTag("Properties", properties);
+ return skullOwner;
+ }
+
+ public void increaseCount(int amount) {
+ count += amount;
+ }
+
+ public void increaseCount() {
+ count++;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public ItemStack getDisplayStack() {
+ return stack;
+ }
+
+ public EnumSet<Locations> getLocations() {
+ return locations;
+ }
+
+ public String getInternalId() {
+ return internalId.toUpperCase(Locale.ENGLISH);
+ }
+
+ public boolean isEntity() {
+ return isEntity;
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/utils/ComponentBuilder.java b/src/main/java/com/thatgravyboat/skyblockhud/utils/ComponentBuilder.java
new file mode 100644
index 000000000..1a73e9558
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/utils/ComponentBuilder.java
@@ -0,0 +1,54 @@
+package com.thatgravyboat.skyblockhud.utils;
+
+public class ComponentBuilder {
+
+ public StringBuilder builder;
+
+ public ComponentBuilder() {
+ this.builder = new StringBuilder();
+ }
+
+ public ComponentBuilder apd(String text) {
+ return apd(text, '7');
+ }
+
+ public ComponentBuilder apd(String text, char... colors) {
+ for (char color : colors) {
+ builder.append("\u00A7").append(color);
+ }
+ builder.append(text).append("\u00A7").append('r');
+ return this;
+ }
+
+ public ComponentBuilder apd(String text, char color) {
+ builder.append("\u00A7").append(color).append(text).append("\u00A7").append('r');
+ return this;
+ }
+
+ public ComponentBuilder nl() {
+ builder.append("\n");
+ return this;
+ }
+
+ public ComponentBuilder nl(String text, char color) {
+ apd(text, color);
+ builder.append("\n");
+ return this;
+ }
+
+ public ComponentBuilder nl(String text, char... colors) {
+ apd(text, colors);
+ builder.append("\n");
+ return this;
+ }
+
+ public ComponentBuilder nl(String text) {
+ apd(text);
+ builder.append("\n");
+ return this;
+ }
+
+ public String build() {
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/thatgravyboat/skyblockhud/utils/SpecialColour.java b/src/main/java/com/thatgravyboat/skyblockhud/utils/SpecialColour.java
new file mode 100644
index 000000000..9c1b723c8
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/utils/SpecialColour.java
@@ -0,0 +1,93 @@
+package com.thatgravyboat.skyblockhud.utils;
+
+import java.awt.*;
+
+public class SpecialColour {
+
+ 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/com/thatgravyboat/skyblockhud/utils/Utils.java b/src/main/java/com/thatgravyboat/skyblockhud/utils/Utils.java
new file mode 100644
index 000000000..3135b9b29
--- /dev/null
+++ b/src/main/java/com/thatgravyboat/skyblockhud/utils/Utils.java
@@ -0,0 +1,380 @@
+package com.thatgravyboat.skyblockhud.utils;
+
+import com.thatgravyboat.skyblockhud.SkyblockHud;
+import java.math.RoundingMode;
+import java.nio.FloatBuffer;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Locale;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+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.init.Items;
+import net.minecraft.item.Item;
+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 LinkedList<Integer> guiScales = new LinkedList<>();
+ private static ScaledResolution lastScale = new ScaledResolution(Minecraft.getMinecraft());
+ //Labymod compatibility
+ private static FloatBuffer projectionMatrixOld = BufferUtils.createFloatBuffer(16);
+ private static 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 isPlayerHoldingRedstone(EntityPlayerSP player) {
+ if (!SkyblockHud.config.main.requireRedstone) return true;
+ ArrayList<Item> redstoneItems = new ArrayList<>(Arrays.asList(Items.redstone, Items.repeater, Items.comparator, Item.getByNameOrId("minecraft:redstone_torch")));
+ if (player.getHeldItem() != null) return redstoneItems.contains(player.getHeldItem().getItem());
+ return false;
+ }
+
+ 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");
+ }
+}