aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenz <ESs95s3P5z8Pheb>2022-07-07 00:31:50 +0200
committerLorenz <ESs95s3P5z8Pheb>2022-07-07 00:31:50 +0200
commit99773d6a593c444151503de315f127bea6f74d49 (patch)
tree9ee1ae505e5f82aba62f10c882af85a3acd6e483
downloadskyhanni-99773d6a593c444151503de315f127bea6f74d49.tar.gz
skyhanni-99773d6a593c444151503de315f127bea6f74d49.tar.bz2
skyhanni-99773d6a593c444151503de315f127bea6f74d49.zip
init lorenz mod
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/build.yml61
-rw-r--r--.gitignore23
-rw-r--r--.prettierignore5
-rw-r--r--.prettierrc.yml13
-rw-r--r--LICENSE504
-rw-r--r--README.md18
-rw-r--r--build.gradle129
-rw-r--r--gradle.properties1
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 54417 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties5
-rw-r--r--gradlew172
-rw-r--r--gradlew.bat84
-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
-rw-r--r--src/main/resources/assets/skyblockhud/bars.pngbin0 -> 4298 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/button.pngbin0 -> 2664 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/button_white.pngbin0 -> 6570 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/bar.pngbin0 -> 2095 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/colour_selector_bar.pngbin0 -> 245 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/colour_selector_bar_alpha.pngbin0 -> 240 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/colour_selector_chroma.pngbin0 -> 211 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/colour_selector_dot.pngbin0 -> 3677 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/delete.pngbin0 -> 5700 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_button.pngbin0 -> 1439 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_off_cap.pngbin0 -> 1462 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_off_notch.pngbin0 -> 1380 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_off_segment.pngbin0 -> 1439 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_on_cap.pngbin0 -> 1467 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_on_notch.pngbin0 -> 1376 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/slider/slider_on_segment.pngbin0 -> 1434 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/toggle_1.pngbin0 -> 592 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/toggle_2.pngbin0 -> 590 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/toggle_3.pngbin0 -> 592 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/toggle_off.pngbin0 -> 594 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/core/toggle_on.pngbin0 -> 593 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/data/trackers.json681
-rw-r--r--src/main/resources/assets/skyblockhud/dialogue.pngbin0 -> 1537 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/discord.pngbin0 -> 6690 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/dungeon.pngbin0 -> 3683 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/barn.pngbin0 -> 18328 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/crystal.pngbin0 -> 190625 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/dwarven.pngbin0 -> 168793 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/fort.pngbin0 -> 50347 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/hub.pngbin0 -> 193702 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/adventurer.pngbin0 -> 390 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/ah.pngbin0 -> 294 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/armor.pngbin0 -> 362 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/balloon.pngbin0 -> 347 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/bank.pngbin0 -> 434 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/bank_upgrader.pngbin0 -> 445 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/bar.pngbin0 -> 395 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/bazaar.pngbin0 -> 306 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/blacksmith.pngbin0 -> 280 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/building.pngbin0 -> 278 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/community.pngbin0 -> 333 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/crown.pngbin0 -> 386 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/dark_ah.pngbin0 -> 293 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/dark_bar.pngbin0 -> 380 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/dungeon_portal.pngbin0 -> 296 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/enter.pngbin0 -> 347 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/exit.pngbin0 -> 319 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/fairy.pngbin0 -> 380 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/fishing.pngbin0 -> 280 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/fishing_merchant.pngbin0 -> 362 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/lumber.pngbin0 -> 336 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/maddox.pngbin0 -> 318 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/metal_merchants.pngbin0 -> 506 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/mine.pngbin0 -> 337 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/painter.pngbin0 -> 267 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/pointer.pngbin0 -> 265 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/portal.pngbin0 -> 296 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/puzzle.pngbin0 -> 305 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/redstone.pngbin0 -> 288 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/reforge.pngbin0 -> 392 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/runes.pngbin0 -> 318 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/scroll.pngbin0 -> 282 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/special.pngbin0 -> 367 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/tux.pngbin0 -> 306 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/vet.pngbin0 -> 526 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/warp.pngbin0 -> 270 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/weapon.pngbin0 -> 306 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/witch.pngbin0 -> 340 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/wizard.pngbin0 -> 375 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/icons/wool.pngbin0 -> 258 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/map_overlay.pngbin0 -> 1992 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/mushroom.pngbin0 -> 82957 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/park.pngbin0 -> 49589 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/maps/readme.txt4
-rw-r--r--src/main/resources/assets/skyblockhud/maps/spidersden.pngbin0 -> 47744 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/mines.pngbin0 -> 3211 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/playerstats.pngbin0 -> 4816 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/stats.pngbin0 -> 7870 bytes
-rw-r--r--src/main/resources/assets/skyblockhud/twitter.pngbin0 -> 5816 bytes
-rw-r--r--src/main/resources/mcmod.info16
-rw-r--r--src/main/resources/mixins.skyblockhud.json8
-rw-r--r--src/main/resources/pack.mcmeta6
-rw-r--r--trackers.json272
233 files changed, 14551 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..94f480de9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf \ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..4d0c2f9da
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,61 @@
+name: Build
+
+on:
+ push:
+ branches:
+ - "*"
+ paths-ignore:
+ - ".gitignore"
+ pull_request:
+ branches:
+ - "*"
+ paths-ignore:
+ - ".gitignore"
+ workflow_dispatch:
+permissions: write-all
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle
+ run: ./gradlew build
+ - uses: actions/upload-artifact@v2
+ with:
+ path: build/libs/*.jar
+ - name: Stop gradle daemons
+ run: ./gradlew --stop
+
+ Check-Formating:
+ runs-on: ubuntu-latest
+ name: Check Formatting
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Prettify the Java Code
+ uses: lwerner-lshigh/prettier_action_java@v1.1.1
+ with:
+ prettier_options: "--write ."
+ branch: ${{ github.head_ref }}
+ dry: ${{ github.event_name != 'push' }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..56c1bb7a2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# eclipse
+bin
+*.launch
+.settings
+.metadata
+.classpath
+.project
+
+# idea
+out
+*.ipr
+*.iws
+*.iml
+.idea
+
+# gradle
+build
+.gradle
+deps
+
+# other
+eclipse
+run
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..3dbc8a989
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+build
+.gradle
+.idea
+run
+gradlew* \ No newline at end of file
diff --git a/.prettierrc.yml b/.prettierrc.yml
new file mode 100644
index 000000000..9b94be6cf
--- /dev/null
+++ b/.prettierrc.yml
@@ -0,0 +1,13 @@
+overrides:
+ - files:
+ - "*.java"
+ options:
+ useTabs: false
+ trailingComma: none
+ tabWidth: 4
+ endOfLine: lf
+ printWidth: 999999999 # >:c fine gravy
+ - files:
+ - "*.json"
+ options:
+ printWidth: 10
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..8000a6faa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random
+ Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..d255fe8ad
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+<h1 align = "center">
+ SBHud: HUD mod for hypixel
+</h1>
+
+<div align="center">
+
+[![discord badge](https://img.shields.io/discord/516977525906341928?label=discord&color=9089DA&logo=discord&style=for-the-badge)](https://discord.gg/moulberry)
+[![made with java](https://img.shields.io/badge/Made%20With-Java-orange?style=for-the-badge&logo=java&logocolor=white)](https://www.java.com/)
+
+<!-- Can't do this until public repo
+[![line count](https://img.shields.io/tokei/lines/github/ThatGravyBoat/SkyblockHud?style=for-the-badge&logo=github&logocolor=white)](https://github.com/Moulberry/Hychat)
+[![workflow status](https://img.shields.io/github/workflow/status/ThatGravyBoat/SkyblockHud/JSON/master?label=Workflow%20status&style=for-the-badge&logo=github&logocolor=white)](https://github.com/Moulberry/Hychat)
+-->
+</div>
+
+SBHud is a beta mod developed by ThatGravyBoat to add a better HUD to hypixel skyblock. It is in beta so expect bugs.
+
+This project has been discontinued and is now open source for anyone to fork and work on it but as of right now it wont be worked on by the original developer ThatGravyBoat
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..3bd0ac747
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,129 @@
+buildscript {
+ ext.kotlin_version = '1.7.0'
+ repositories {
+ maven {
+ name = 'jitpack'
+ url = 'https://jitpack.io/'
+ }
+ maven { url = 'https://maven.minecraftforge.net/' }
+ maven { url = 'https://repo.spongepowered.org/maven' }
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.github.asbyth:ForgeGradle:8708bf3e01'
+ classpath 'com.github.xcfrg:MixinGradle:0.6-SNAPSHOT'
+ classpath 'com.github.jengelman.gradle.plugins:shadow:6.1.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'kotlin'
+apply plugin: 'net.minecraftforge.gradle.forge'
+apply plugin: 'org.spongepowered.mixin'
+apply plugin: 'com.github.johnrengelman.shadow'
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+version = '0.1'
+group= 'com.thatgravyboat.skyblockhud'
+archivesBaseName = 'LorenzMod'
+String mixinClassifier = 'dep'
+
+minecraft {
+ version = '1.8.9-11.15.1.2318-1.8.9'
+ runDir = 'run'
+ mappings = 'stable_22'
+// clientRunArgs.addAll(
+// arrayOf(
+// "--mixin mixins.skytils.json"
+// )
+// )
+}
+
+repositories {
+ maven { url 'https://repo.spongepowered.org/maven/' }
+ flatDir {
+ dirs 'deps'
+ }
+ mavenCentral()
+}
+
+dependencies {
+ compile('org.spongepowered:mixin:0.7.11-SNAPSHOT')
+ annotationProcessor('org.spongepowered:mixin:0.7.11-SNAPSHOT')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+}
+
+compileJava {
+ [compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
+}
+
+mixin {
+ add sourceSets.main, 'mixins.skyblockhud.refmap.json'
+}
+
+jar {
+ manifest.attributes(
+ 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
+ 'MixinConfigs': 'mixins.skyblockhud.json',
+ 'FMLCorePluginContainsFMLMod': true,
+ 'ForceLoadAsMod': true
+ )
+}
+
+shadowJar {
+ dependencies {
+ include(dependency('org.spongepowered:mixin:0.7.11-SNAPSHOT'))
+ }
+ exclude 'module-info.class'
+ exclude 'dummyThing'
+ exclude 'LICENSE.txt'
+
+ archiveClassifier.set(mixinClassifier)
+}
+
+reobf {
+ shadowJar {
+ mappingType = 'SEARGE'
+ }
+}
+
+build.dependsOn(shadowJar)
+runClient.dependsOn(build)
+
+processResources
+ {
+ inputs.property 'version', project.version
+ inputs.property 'mcversion', project.minecraft.version
+
+ from(sourceSets.main.resources.srcDirs) {
+ include 'mcmod.info'
+ expand 'version':project.version, 'mcversion':project.minecraft.version
+ }
+
+ from(sourceSets.main.resources.srcDirs) {
+ exclude 'mcmod.info'
+ }
+
+ rename '(.+_at.cfg)', 'META-INF/$1'
+ }
+task moveResources {
+ doLast {
+ ant.move file: "${buildDir}/resources/main",
+ todir: "${buildDir}/classes/java"
+ }
+}
+moveResources.dependsOn processResources
+classes.dependsOn moveResources
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+compileTestKotlin {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+} \ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 000000000..bf86fb715
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx2G \ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..758de960e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..71a1ffc9e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 000000000..cccdd3d51
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..f9553162f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
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");
+ }
+}
diff --git a/src/main/resources/assets/skyblockhud/bars.png b/src/main/resources/assets/skyblockhud/bars.png
new file mode 100644
index 000000000..edf1b70ed
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/bars.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/button.png b/src/main/resources/assets/skyblockhud/button.png
new file mode 100644
index 000000000..e96f4284c
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/button.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/button_white.png b/src/main/resources/assets/skyblockhud/button_white.png
new file mode 100644
index 000000000..7763716d0
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/button_white.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/bar.png b/src/main/resources/assets/skyblockhud/core/bar.png
new file mode 100644
index 000000000..664c0f325
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/bar.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/colour_selector_bar.png b/src/main/resources/assets/skyblockhud/core/colour_selector_bar.png
new file mode 100644
index 000000000..f176af90b
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/colour_selector_bar.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/colour_selector_bar_alpha.png b/src/main/resources/assets/skyblockhud/core/colour_selector_bar_alpha.png
new file mode 100644
index 000000000..7a89510c3
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/colour_selector_bar_alpha.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/colour_selector_chroma.png b/src/main/resources/assets/skyblockhud/core/colour_selector_chroma.png
new file mode 100644
index 000000000..ea273959b
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/colour_selector_chroma.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/colour_selector_dot.png b/src/main/resources/assets/skyblockhud/core/colour_selector_dot.png
new file mode 100644
index 000000000..1150c8bb7
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/colour_selector_dot.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/delete.png b/src/main/resources/assets/skyblockhud/core/delete.png
new file mode 100644
index 000000000..8183ee8f0
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/delete.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_button.png b/src/main/resources/assets/skyblockhud/core/slider/slider_button.png
new file mode 100644
index 000000000..d49d24adc
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_button.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_off_cap.png b/src/main/resources/assets/skyblockhud/core/slider/slider_off_cap.png
new file mode 100644
index 000000000..16c89f728
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_off_cap.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_off_notch.png b/src/main/resources/assets/skyblockhud/core/slider/slider_off_notch.png
new file mode 100644
index 000000000..cd7e1c54a
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_off_notch.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_off_segment.png b/src/main/resources/assets/skyblockhud/core/slider/slider_off_segment.png
new file mode 100644
index 000000000..ff0e6b919
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_off_segment.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_on_cap.png b/src/main/resources/assets/skyblockhud/core/slider/slider_on_cap.png
new file mode 100644
index 000000000..21e50b714
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_on_cap.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_on_notch.png b/src/main/resources/assets/skyblockhud/core/slider/slider_on_notch.png
new file mode 100644
index 000000000..947389dd6
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_on_notch.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/slider/slider_on_segment.png b/src/main/resources/assets/skyblockhud/core/slider/slider_on_segment.png
new file mode 100644
index 000000000..86a311ba8
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/slider/slider_on_segment.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/toggle_1.png b/src/main/resources/assets/skyblockhud/core/toggle_1.png
new file mode 100644
index 000000000..618e2c62a
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/toggle_1.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/toggle_2.png b/src/main/resources/assets/skyblockhud/core/toggle_2.png
new file mode 100644
index 000000000..4bb5ee394
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/toggle_2.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/toggle_3.png b/src/main/resources/assets/skyblockhud/core/toggle_3.png
new file mode 100644
index 000000000..50164cc30
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/toggle_3.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/toggle_off.png b/src/main/resources/assets/skyblockhud/core/toggle_off.png
new file mode 100644
index 000000000..ed7d5e686
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/toggle_off.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/core/toggle_on.png b/src/main/resources/assets/skyblockhud/core/toggle_on.png
new file mode 100644
index 000000000..68ce1c0c0
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/core/toggle_on.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/data/trackers.json b/src/main/resources/assets/skyblockhud/data/trackers.json
new file mode 100644
index 000000000..cb95d0c3f
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/data/trackers.json
@@ -0,0 +1,681 @@
+{
+ "trackers": [
+ {
+ "location": [
+ "DRAGONSNEST"
+ ],
+ "drops": [
+ {
+ "id": "WISE_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "ca156a9a-0286-3005-a839-a6c4dd4c671e",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzFkNzYyMGIyZTQ5MzQ5NjNiYjEyNTA4MzEwZDA1NDk0YzA2N2RjMzNlMDA4Y2VjZjJjZDdiNDU0OTY1NGZhYjMifX19"
+ }
+ }
+ },
+ {
+ "id": "UNSTABLE_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "59b41a23-b7d0-33df-a196-968957aa0b48",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzk4MjI4YzIzNGMzOTAzYzUxMmE1YTBhYTQ1MjYwZTdiNTY3ZTBlMjBlZWZjN2Q1NjFjY2VjOTdiMjk1ODcxYWYifX19"
+ }
+ }
+ },
+ {
+ "id": "PROTECTOR_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "a228e918-722d-3a31-bc13-c9f905a0d3a2",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzQ4ZGUzMzlhZjYzYTIyOWM5MjM4ZDAyN2U0N2Y1M2VlYjU2MTQxYTQxOWY1MWIzNWMzMWVhMTQ5NGI0MzVkZDMifX19"
+ }
+ }
+ },
+ {
+ "id": "OLD_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "a3d7724b-faaf-3472-8010-4a67aba84d7c",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhYTA5YWQxNzdmYmNjYzUzZmEzMTZjYzA0YmRkMmM5MzY2YmFlZDg4OWRmNzZjNWEyOWRlZmVhODE3MGRlZjUifX19"
+ }
+ }
+ },
+ {
+ "id": "YOUNG_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "b896dea7-c289-3450-97b6-6de89f48ae4b",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzRiNWJkNmI2NGU4YmQ2YzU4ZjVjZDFlNzlhNTUwMmQ0NDQ4YmFmYzAwNmQyZmUwNTY4ZjZhMGQ2Yjg2ZDQ0OWUifX19"
+ }
+ }
+ },
+ {
+ "id": "STRONG_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "ddc6eaac-9166-3188-a081-7a33ebaaa3cd",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzZlZTMyZmJkNGM3YjAzYjg2OTA3OGFhMWY0OTNhMzkwZTZlMTNiNDYxZDYxMzcwN2VhZmIzMjZkYmNkMmI0YjUifX19"
+ }
+ }
+ },
+ {
+ "id": "SUPERIOR_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "51e54c0e-92ce-3b9f-9ee0-e35e8a302396",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzZmODliMTUwYmU5YzRjNTI0OWYzNTVmNjhlYTBjNDM5MTMwMGE5YmUxZjI2MGQ3NTBmYzM1YTE4MTdhZDc5NmUifX19"
+ }
+ }
+ },
+ {
+ "id": "CRYSTAL_FRAGMENT",
+ "displayItem": {
+ "item": "minecraft:quartz",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "SUMMONING_EYE",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "00a702b9-7bad-3205-a04b-52478d8c0e7f",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGFhOGZjOGRlNjQxN2I0OGQ0OGM4MGI0NDNjZjUzMjZlM2Q5ZGE0ZGJlOWIyNWZjZDQ5NTQ5ZDk2MTY4ZmMwIn19fQ=="
+ }
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "RUINS",
+ "HOWLINGCAVE"
+ ],
+ "drops": [
+ {
+ "id": "WOLF_TOOTH",
+ "displayItem": {
+ "item": "minecraft:ghast_tear",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "HAMSTER_WHEEL",
+ "displayItem": {
+ "item": "minecraft:trapdoor",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "CRITICAL;6",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "RED_CLAW_EGG",
+ "displayItem": {
+ "item": "minecraft:spawn_egg",
+ "meta": 96
+ }
+ },
+ {
+ "id": "GRIZZLY_BAIT",
+ "displayItem": {
+ "item": "minecraft:fish",
+ "meta": 1
+ }
+ },
+ {
+ "id": "OVERFLUX_CAPACITOR",
+ "displayItem": {
+ "item": "minecraft:quartz",
+ "meta": 0,
+ "enchanted": true
+ }
+ }
+ ],
+ "mobs": [
+ {
+ "id": "entity:WOLF_SLAYER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "42482f09-bf1b-452e-aa91-0b9f9dc6868f",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQxYWE3ZTNiOTU2NGIzODQ2ZjFkZWExNGYxYjFjY2JmMzk5YmJiMjNiOTUyZGJkN2VlYzQxODAyYTI4OWM5NiJ9fX0="
+ }
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "SPIDERSDEN"
+ ],
+ "drops": [
+ {
+ "id": "TARANTULA_WEB",
+ "displayItem": {
+ "item": "minecraft:string",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "TOXIC_ARROW_POISON",
+ "displayItem": {
+ "item": "minecraft:dye",
+ "meta": 10
+ }
+ },
+ {
+ "id": "SPIDER_CATALYST",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "3fe28c63-f3fc-30c2-8e74-ff1297977213",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTgzYjMwZTlkMTM1YjA1MTkwZWVhMmMzYWM2MWUyYWI1NWEyZDgxZTFhNThkYmIyNjk4M2ExNDA4MjY2NCJ9fX0="
+ }
+ }
+ },
+ {
+ "id": "BANE_OF_ARTHROPODS;6",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "FLY_SWATTER",
+ "displayItem": {
+ "item": "minecraft:golden_shovel",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "TARANTULA_TALISMAN",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "c89b16d8-4122-31e0-bb59-15cc95cdfe2c",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDQyY2Y4Y2U0ODdiNzhmYTIwM2Q1NmNmMDE0OTE0MzRiNGMzM2U1ZDIzNjgwMmM2ZDY5MTQ2YTUxNDM1YjAzZCJ9fX0="
+ }
+ }
+ },
+ {
+ "id": "DIGESTED_MOSQUITO",
+ "displayItem": {
+ "item": "minecraft:rotten_flesh",
+ "meta": 0
+ }
+ }
+ ],
+ "mobs": [
+ {
+ "id": "entity:SPIDER_SLAYER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "9bedf23e-8a90-410d-bd1f-333d3929821e",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTdjNzkxY2IwNzk3ZGRkMTAzOTUyYjU0OGQzZDU2MGE3NzJiNGU5NDZjYzA5NjQzODU3Y2ViZGFhOWQxYmNjNCJ9fX0="
+ }
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "GRAVEYARD",
+ "COALMINE"
+ ],
+ "drops": [
+ {
+ "id": "REVENANT_FLESH",
+ "displayItem": {
+ "item": "minecraft:rotten_flesh",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "FOUL_FLESH",
+ "displayItem": {
+ "item": "minecraft:coal",
+ "meta": 1
+ }
+ },
+ {
+ "id": "UNDEAD_CATALYST",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "5bf9191d-dcdf-3c0f-aff9-7fcc5cb0a001",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODA2MjUzNjliMGE3YjA1MjYzMmRiNmI5MjZhODc2NzAyMTk1Mzk5MjI4MzZhYzU5NDBiZTI2ZDM0YmYxNGUxMCJ9fX0="
+ }
+ }
+ },
+ {
+ "id": "SMITE;6",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "BEHEADED_HORROR",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "0862e0b0-a14f-3f93-894f-013502936b59",
+ "texture": "eyJ0aW1lc3RhbXAiOjE1Njg0NTc0MjAxMzcsInByb2ZpbGVJZCI6IjQxZDNhYmMyZDc0OTQwMGM5MDkwZDU0MzRkMDM4MzFiIiwicHJvZmlsZU5hbWUiOiJNZWdha2xvb24iLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RiYWQ5OWVkM2M4MjBiNzk3ODE5MGFkMDhhOTM0YTY4ZGZhOTBkOTk4NjgyNWRhMWM5N2Y2ZjIxZjQ5YWQ2MjYifX19"
+ }
+ }
+ },
+ {
+ "id": "REVENANT_CATALYST",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "5ace63c5-b3c9-306a-887c-16db7efea0f0",
+ "texture": "eyJ0aW1lc3RhbXAiOjE1NjgzNjYzMjYwNzEsInByb2ZpbGVJZCI6IjQxZDNhYmMyZDc0OTQwMGM5MDkwZDU0MzRkMDM4MzFiIiwicHJvZmlsZU5hbWUiOiJNZWdha2xvb24iLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2I4OGNmYWZhNWYwM2Y4YWVmMDQyYTE0Mzc5OWU5NjQzNDJkZjc2YjdjMWViNDYxZjYxOGUzOThmODRhOTlhNjMifX19"
+ }
+ }
+ },
+ {
+ "id": "SHARD_OF_THE_SHREDDED",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "9ddf6967-40de-3534-903f-4d5d9c933d55",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzBjNWNjNzI4Yzg2OWVjZjNjNmUwOTc5ZThhYTA5YzEwMTQ3ZWQ3NzA0MTdlNGJhNTQxYWFjMzgyZjAifX19"
+ }
+ }
+ },
+ {
+ "id": "SMITE;7",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "WARDEN_HEART",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "7adc7613-256a-3593-899b-d4d9bbf50387",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDQ1ZjRkMTM5YzllODkyNjJlYzA2YjI3YWFhZDczZmE0ODhhYjQ5MjkwZDJjY2Q2ODVhMjU1NDcyNTM3M2M5YiJ9fX0="
+ }
+ }
+ },
+ {
+ "id": "SCYTHE_BLADE",
+ "displayItem": {
+ "item": "minecraft:diamond",
+ "meta": 0,
+ "enchanted": true
+ }
+ }
+ ],
+ "mobs": [
+ {
+ "id": "entity:ZOMBIE_SLAYER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "a593ac46-8eb5-46f0-ab18-5a5c496a94fc",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjY3YjI3ZmI3ZTI5ZWM5OGUxY2Q0YThmODQ2Njg1NmQ5ZWYzZjJlOWZiZDlhZWQ2MzExZjhhYmU1NGI2YWIyIn19fQ=="
+ }
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "THEMIST"
+ ],
+ "drops": [
+ {
+ "id": "SORROW",
+ "displayItem": {
+ "item": "minecraft:ghast_tear",
+ "meta": 0
+ }
+ },
+ {
+ "id": "VOLTA",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "16430ce7-0369-3383-881f-0dc62c684d65",
+ "texture": "ewogICJ0aW1lc3RhbXAiIDogMTYxMDY0MjE1MzExNCwKICAicHJvZmlsZUlkIiA6ICI5ZDEzZjcyMTcxM2E0N2U0OTAwZTMyZGVkNjBjNDY3MyIsCiAgInByb2ZpbGVOYW1lIiA6ICJUYWxvZGFvIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzYzYTQwNWZiMjg2ZGJiMzJlOWIzOTA4ZjYwOTQ4ZjAyMDczMDZjODI1ZTYzYWM5ZTYyNmVkMWRiYjJmN2EyYmUiCiAgICB9CiAgfQp9"
+ }
+ }
+ },
+ {
+ "id": "PLASMA",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "3498885a-3841-3748-8183-893f766ad136",
+ "texture": "ewogICJ0aW1lc3RhbXAiIDogMTYxMDY0MjExNzM0NywKICAicHJvZmlsZUlkIiA6ICJkZTU3MWExMDJjYjg0ODgwOGZlN2M5ZjQ0OTZlY2RhZCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNSEZfTWluZXNraW4iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzVhYTgzMzJiYmVjNGE5NThmZWEyYmU2NGI4ZjE2ODJmNWQ4MjQ3NDUxYWEyYjc1NjllZGQwNDk4NDM3ZDcwNiIKICAgIH0KICB9Cn0="
+ }
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "THEEND",
+ "DRAGONSNEST",
+ "VOIDSEPULTURE"
+ ],
+ "drops": [
+ {
+ "id": "NULL_SPHERE",
+ "displayItem": {
+ "item": "minecraft:firework_charge",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "TWILIGHT_ARROW_POISON",
+ "displayItem": {
+ "item": "minecraft:dye",
+ "meta": 5,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "SUMMONING_EYE",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "00a702b9-7bad-3205-a04b-52478d8c0e7f",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGFhOGZjOGRlNjQxN2I0OGQ0OGM4MGI0NDNjZjUzMjZlM2Q5ZGE0ZGJlOWIyNWZjZDQ5NTQ5ZDk2MTY4ZmMwIn19fQ=="
+ }
+ }
+ },
+ {
+ "id": "TRANSMISSION_TUNER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "df5671b6-329a-3fd7-9f56-71b6329a9fd7",
+ "texture": "e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGFlNTRkMDNjZTA1MTA2ZjZmNzQ1YjhmODUxMzQ0ZWMzOGU2OGRkMzMwN2EzMWM4NDNiMDgyMTJkZjU0NmRkOSJ9fX0="
+ }
+ }
+ },
+ {
+ "id": "MANA_STEAL;1",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "NULL_ATOM",
+ "displayItem": {
+ "item": "minecraft:wooden_button",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "POCKET_ESPRESSO_MACHINE",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "9bf867c8-d5b6-33e3-8fa3-f4a573979ebe",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjY2MDcwY2UwM2E1NDVlZTRkMjYzYmNmMjdmMzYzMzhkMjQ5ZDdjYjdhMjM3NmY5MmMxNjczYWUxMzRlMDRiNiJ9fX0="
+ }
+ }
+ },
+ {
+ "id": "PET_SKIN_ENDERMAN_SLAYER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "301afb75-07dd-37ce-94a1-7c5c40ab2512",
+ "texture": "ewogICJ0aW1lc3RhbXAiIDogMTYyMTk4NDE5NTUwNCwKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGZmZjQxZTFhZmM1OTdiMTRmNzdiOGU0NGUyYTEzNGRhYmUxNjFhMTUyNmFkZTgwZTYyOTBmMmRmMzMxZGMxMSIKICAgIH0KICB9Cn0"
+ }
+ }
+ },
+ {
+ "id": "ETHERWARP_MERGER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "209e7834-3376-36e1-84eb-da13ef083836",
+ "texture": "ewogICJ0aW1lc3RhbXAiIDogMTYyMTYxNzYxOTExMywKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2U1MzE0ZjQ5MTk2OTFjY2JmODA3NzQzZGFlNDdhZTQ1YWMyZTNmZjA4Zjc5ZWVjZGQ0NTJmZTYwMmVmZjdmNiIKICAgIH0KICB9Cn0"
+ }
+ }
+ },
+ {
+ "id": "JUDGEMENT_CORE",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "ed896594-8655-3212-933e-c67bca300084",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmYzZGRkN2Y4MTA4OWM4NWIyNmVkNTk3Njc1NTE5ZjAzYTFkY2Q2ZDE3MTNlMGNmYzY2YWZiODc0M2NiZTAifX19"
+ }
+ }
+ },
+ {
+ "id": "EXCEEDINGLY_RARE_ENDER_ARTIFACT_UPGRADER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "eac161df-59c5-3647-92eb-950d53331e0e",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTI1OTIzMWE5NDY5ODdlYTUzMTQxNzg5YTA5NDk2ZjA5OGQ2ZWNhYzQxMmEwMWUwYTI0YzkwNmE5OWZkYmQ5YSJ9fX0"
+ }
+ }
+ },
+ {
+ "id": "ENDER_SLAYER;7",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "HANDY_BLOOD_CHALICE",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "d17ab030-ec6c-3a88-9805-50b5812690fb",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDMxY2Q3ZWQ0ZTRiZjA3YzNkZmQ5YmE0OTg3MDhlNzMwZTY5ZDgwNzMzNWFmZmFiYzEyZDg3ZmY1NDJmNmE4OCJ9fX0"
+ }
+ }
+ },
+ {
+ "id": "SINFUL_DICE",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "05ab8a23-a718-3dbb-8307-d999ebed1e24",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmUyMmMyOThlN2M2MzM2YWYxNzkwOWFjMWYxZWU2ODM0YjU4YjFhM2NjOTlhYmEyNTVjYTdlYWViNDc2MTczIn19fQ"
+ }
+ }
+ },
+ {
+ "id": "SMARTY_PANTS;1",
+ "displayItem": {
+ "item": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ }
+ ],
+ "mobs": [
+ {
+ "id": "entity:ENDERMAN_SLAYER",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "fcee8e82-d1d5-44da-9d98-157a52abce20",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjk2YTAwMmZkNmRhMGNiNDI3YWUzNWNiZGZhMjg2Y2NmNTIwMTFiYWE3Y2FlZGRhOTRkNjRjZGExOThiNWFhYyJ9fX0="
+ }
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "JERRYSWORKSHOP",
+ "JERRYPOND"
+ ],
+ "drops": [
+ {
+ "id": "HILT_OF_TRUE_ICE",
+ "displayItem": {
+ "item": "minecraft:ice",
+ "meta": 0,
+ "enchanted": true
+ }
+ },
+ {
+ "id": "BABY_YETI;3",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "7895e21a-8f3b-3e30-bea6-06108f64d5dc",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWIxMjY4MTRmYzNmYTg0NmRhZDkzNGMzNDk2MjhhN2ExZGU1YjQxNTAyMWEwM2VmNDIxMWQ2MjUxNGQ1In19fQ"
+ }
+ }
+ },
+ {
+ "id": "BABY_YETI;4",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "7895e21a-8f3b-3e30-bea6-06108f64d5dc",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWIxMjY4MTRmYzNmYTg0NmRhZDkzNGMzNDk2MjhhN2ExZGU1YjQxNTAyMWEwM2VmNDIxMWQ2MjUxNGQ1In19fQ"
+ }
+ }
+ },
+ {
+ "id": "YETI_ROD",
+ "displayItem": {
+ "item": "minecraft:fishing_rod",
+ "meta": 0
+ }
+ },
+ {
+ "id": "RED_GIFT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "bc74cb05-2758-3395-93ec-70452a983604",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjczYTIxMTQxMzZiOGVlNDkyNmNhYTUxNzg1NDE0MDM2YTJiNzZlNGYxNjY4Y2I4OWQ5OTcxNmM0MjEifX19"
+ }
+ }
+ },
+ {
+ "id": "GREEN_GIFT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "d5eb6a2a-3f10-3d6b-ba6a-4d46bb58a5cb",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWQ5N2Y0ZjQ0ZTc5NmY3OWNhNDMwOTdmYWE3YjRmZTkxYzQ0NWM3NmU1YzI2YTVhZDc5NGY1ZTQ3OTgzNyJ9fX0"
+ }
+ }
+ },
+ {
+ "id": "WHITE_GIFT",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "7732c5e4-1800-3b90-a70f-727d2969254b",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTBmNTM5ODUxMGIxYTA1YWZjNWIyMDFlYWQ4YmZjNTgzZTU3ZDcyMDJmNTE5M2IwYjc2MWZjYmQwYWUyIn19fQ=="
+ }
+ }
+ },
+ {
+ "id": "ICE_ROD",
+ "displayItem": {
+ "item": "minecraft:fishing_rod",
+ "meta": 0
+ }
+ },
+ {
+ "id": "ICE_HUNK",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "8151ccb0-c40c-3665-8f5d-80b6a74d6c81",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGRiYTY0MmVmZmZhMTNlYzM3MzBlYWZjNTkxNGFiNjgxMTVjMWY5OTg4MDNmNzQ0NTJlMmUwY2QyNmFmMGI4In19fQ"
+ }
+ }
+ },
+ {
+ "id": "BLUE_ICE_HUNK",
+ "displayItem": {
+ "item": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "070f6268-39a5-3c12-b129-355c8f66fc8a",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWFiMDllZTE0ZTNhNWRmNTk5Y2ZhNGQxNmM1Y2U4ZDA1NGJlZjg0Njk1MTMyMGY3MjFhYTRhNzE4MWY4ZGI1ZSJ9fX0"
+ }
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/main/resources/assets/skyblockhud/dialogue.png b/src/main/resources/assets/skyblockhud/dialogue.png
new file mode 100644
index 000000000..b637193d1
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/dialogue.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/discord.png b/src/main/resources/assets/skyblockhud/discord.png
new file mode 100644
index 000000000..27962167d
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/discord.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/dungeon.png b/src/main/resources/assets/skyblockhud/dungeon.png
new file mode 100644
index 000000000..4eb4fb641
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/dungeon.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/barn.png b/src/main/resources/assets/skyblockhud/maps/barn.png
new file mode 100644
index 000000000..45a830eac
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/barn.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/crystal.png b/src/main/resources/assets/skyblockhud/maps/crystal.png
new file mode 100644
index 000000000..7559f4039
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/crystal.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/dwarven.png b/src/main/resources/assets/skyblockhud/maps/dwarven.png
new file mode 100644
index 000000000..4c99ef644
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/dwarven.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/fort.png b/src/main/resources/assets/skyblockhud/maps/fort.png
new file mode 100644
index 000000000..35a3f3988
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/fort.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/hub.png b/src/main/resources/assets/skyblockhud/maps/hub.png
new file mode 100644
index 000000000..0f75d6f74
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/hub.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/adventurer.png b/src/main/resources/assets/skyblockhud/maps/icons/adventurer.png
new file mode 100644
index 000000000..0415d6ec0
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/adventurer.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/ah.png b/src/main/resources/assets/skyblockhud/maps/icons/ah.png
new file mode 100644
index 000000000..7881aa7e6
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/ah.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/armor.png b/src/main/resources/assets/skyblockhud/maps/icons/armor.png
new file mode 100644
index 000000000..406cabe5c
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/armor.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/balloon.png b/src/main/resources/assets/skyblockhud/maps/icons/balloon.png
new file mode 100644
index 000000000..12f649ff5
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/balloon.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/bank.png b/src/main/resources/assets/skyblockhud/maps/icons/bank.png
new file mode 100644
index 000000000..7ea62d48c
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/bank.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/bank_upgrader.png b/src/main/resources/assets/skyblockhud/maps/icons/bank_upgrader.png
new file mode 100644
index 000000000..041686a38
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/bank_upgrader.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/bar.png b/src/main/resources/assets/skyblockhud/maps/icons/bar.png
new file mode 100644
index 000000000..6e07819c2
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/bar.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/bazaar.png b/src/main/resources/assets/skyblockhud/maps/icons/bazaar.png
new file mode 100644
index 000000000..ce55f87ce
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/bazaar.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/blacksmith.png b/src/main/resources/assets/skyblockhud/maps/icons/blacksmith.png
new file mode 100644
index 000000000..3b67ce269
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/blacksmith.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/building.png b/src/main/resources/assets/skyblockhud/maps/icons/building.png
new file mode 100644
index 000000000..9f36f3138
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/building.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/community.png b/src/main/resources/assets/skyblockhud/maps/icons/community.png
new file mode 100644
index 000000000..8e93d2d94
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/community.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/crown.png b/src/main/resources/assets/skyblockhud/maps/icons/crown.png
new file mode 100644
index 000000000..08a9b02ee
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/crown.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/dark_ah.png b/src/main/resources/assets/skyblockhud/maps/icons/dark_ah.png
new file mode 100644
index 000000000..e8cc0d439
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/dark_ah.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/dark_bar.png b/src/main/resources/assets/skyblockhud/maps/icons/dark_bar.png
new file mode 100644
index 000000000..6aa2be485
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/dark_bar.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/dungeon_portal.png b/src/main/resources/assets/skyblockhud/maps/icons/dungeon_portal.png
new file mode 100644
index 000000000..b4cad6d33
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/dungeon_portal.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/enter.png b/src/main/resources/assets/skyblockhud/maps/icons/enter.png
new file mode 100644
index 000000000..53cb4cada
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/enter.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/exit.png b/src/main/resources/assets/skyblockhud/maps/icons/exit.png
new file mode 100644
index 000000000..13ba7db5f
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/exit.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/fairy.png b/src/main/resources/assets/skyblockhud/maps/icons/fairy.png
new file mode 100644
index 000000000..93ada2b78
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/fairy.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/fishing.png b/src/main/resources/assets/skyblockhud/maps/icons/fishing.png
new file mode 100644
index 000000000..eba133d97
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/fishing.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/fishing_merchant.png b/src/main/resources/assets/skyblockhud/maps/icons/fishing_merchant.png
new file mode 100644
index 000000000..b0fcfd179
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/fishing_merchant.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/lumber.png b/src/main/resources/assets/skyblockhud/maps/icons/lumber.png
new file mode 100644
index 000000000..9794416f4
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/lumber.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/maddox.png b/src/main/resources/assets/skyblockhud/maps/icons/maddox.png
new file mode 100644
index 000000000..cad345913
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/maddox.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/metal_merchants.png b/src/main/resources/assets/skyblockhud/maps/icons/metal_merchants.png
new file mode 100644
index 000000000..eb26bb2b3
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/metal_merchants.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/mine.png b/src/main/resources/assets/skyblockhud/maps/icons/mine.png
new file mode 100644
index 000000000..96dc07004
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/mine.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/painter.png b/src/main/resources/assets/skyblockhud/maps/icons/painter.png
new file mode 100644
index 000000000..5fe8d1bad
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/painter.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/pointer.png b/src/main/resources/assets/skyblockhud/maps/icons/pointer.png
new file mode 100644
index 000000000..f124963b7
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/pointer.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/portal.png b/src/main/resources/assets/skyblockhud/maps/icons/portal.png
new file mode 100644
index 000000000..cdc6b2e77
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/portal.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/puzzle.png b/src/main/resources/assets/skyblockhud/maps/icons/puzzle.png
new file mode 100644
index 000000000..7d1128ecb
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/puzzle.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/redstone.png b/src/main/resources/assets/skyblockhud/maps/icons/redstone.png
new file mode 100644
index 000000000..bbdc9ad67
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/redstone.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/reforge.png b/src/main/resources/assets/skyblockhud/maps/icons/reforge.png
new file mode 100644
index 000000000..672ce5a61
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/reforge.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/runes.png b/src/main/resources/assets/skyblockhud/maps/icons/runes.png
new file mode 100644
index 000000000..82b99aa7c
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/runes.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/scroll.png b/src/main/resources/assets/skyblockhud/maps/icons/scroll.png
new file mode 100644
index 000000000..ce2bd2bf9
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/scroll.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/special.png b/src/main/resources/assets/skyblockhud/maps/icons/special.png
new file mode 100644
index 000000000..0e3c8298d
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/special.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/tux.png b/src/main/resources/assets/skyblockhud/maps/icons/tux.png
new file mode 100644
index 000000000..d01f11686
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/tux.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/vet.png b/src/main/resources/assets/skyblockhud/maps/icons/vet.png
new file mode 100644
index 000000000..dc400650d
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/vet.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/warp.png b/src/main/resources/assets/skyblockhud/maps/icons/warp.png
new file mode 100644
index 000000000..f5f110eef
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/warp.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/weapon.png b/src/main/resources/assets/skyblockhud/maps/icons/weapon.png
new file mode 100644
index 000000000..00050931a
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/weapon.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/witch.png b/src/main/resources/assets/skyblockhud/maps/icons/witch.png
new file mode 100644
index 000000000..fd063e031
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/witch.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/wizard.png b/src/main/resources/assets/skyblockhud/maps/icons/wizard.png
new file mode 100644
index 000000000..5ccfe009b
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/wizard.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/icons/wool.png b/src/main/resources/assets/skyblockhud/maps/icons/wool.png
new file mode 100644
index 000000000..d6446f99f
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/icons/wool.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/map_overlay.png b/src/main/resources/assets/skyblockhud/maps/map_overlay.png
new file mode 100644
index 000000000..76d4ef275
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/map_overlay.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/mushroom.png b/src/main/resources/assets/skyblockhud/maps/mushroom.png
new file mode 100644
index 000000000..be97e3518
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/mushroom.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/park.png b/src/main/resources/assets/skyblockhud/maps/park.png
new file mode 100644
index 000000000..f70e03f46
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/park.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/maps/readme.txt b/src/main/resources/assets/skyblockhud/maps/readme.txt
new file mode 100644
index 000000000..8a8aca2fe
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/readme.txt
@@ -0,0 +1,4 @@
+Pack Devs please only change the colors on these maps
+as changing the size may screw up the player offsets
+and I dont want people to complain because their resourcepack changed the
+map size. You can change everything else but dont change the size. \ No newline at end of file
diff --git a/src/main/resources/assets/skyblockhud/maps/spidersden.png b/src/main/resources/assets/skyblockhud/maps/spidersden.png
new file mode 100644
index 000000000..066ddd226
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/maps/spidersden.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/mines.png b/src/main/resources/assets/skyblockhud/mines.png
new file mode 100644
index 000000000..a4e61447e
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/mines.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/playerstats.png b/src/main/resources/assets/skyblockhud/playerstats.png
new file mode 100644
index 000000000..71a47b400
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/playerstats.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/stats.png b/src/main/resources/assets/skyblockhud/stats.png
new file mode 100644
index 000000000..d93e6da5f
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/stats.png
Binary files differ
diff --git a/src/main/resources/assets/skyblockhud/twitter.png b/src/main/resources/assets/skyblockhud/twitter.png
new file mode 100644
index 000000000..5f249b80b
--- /dev/null
+++ b/src/main/resources/assets/skyblockhud/twitter.png
Binary files differ
diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info
new file mode 100644
index 000000000..28064d720
--- /dev/null
+++ b/src/main/resources/mcmod.info
@@ -0,0 +1,16 @@
+[
+ {
+ "modid": "skyblockhud",
+ "name": "Skyblock Hud",
+ "description": "Hypixel Skyblock RPG HUD",
+ "version": "${version}",
+ "mcversion": "${mcversion}",
+ "url": "",
+ "updateUrl": "",
+ "authorList": ["ThatGravyBoat"],
+ "credits": "Map Icons by ModCruel",
+ "logoFile": "",
+ "screenshots": [],
+ "dependencies": []
+ }
+]
diff --git a/src/main/resources/mixins.skyblockhud.json b/src/main/resources/mixins.skyblockhud.json
new file mode 100644
index 000000000..d78ee962f
--- /dev/null
+++ b/src/main/resources/mixins.skyblockhud.json
@@ -0,0 +1,8 @@
+{
+ "package": "at.lorenz.mod.mixins",
+ "refmap": "mixins.skyblockhud.refmap.json",
+ "compatibilityLevel": "JAVA_8",
+ "mixins": [
+ "MixinGuiContainer"
+ ]
+}
diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta
new file mode 100644
index 000000000..fad033469
--- /dev/null
+++ b/src/main/resources/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "description": "skyblock hud resources",
+ "pack_format": 1
+ }
+}
diff --git a/trackers.json b/trackers.json
new file mode 100644
index 000000000..1fdd53f40
--- /dev/null
+++ b/trackers.json
@@ -0,0 +1,272 @@
+{
+ "trackers": [
+ {
+ "location": [
+ "DRAGONSNEST"
+ ],
+ "drops": [
+ {
+ "id": "WISE_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "ca156a9a-0286-3005-a839-a6c4dd4c671e",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzFkNzYyMGIyZTQ5MzQ5NjNiYjEyNTA4MzEwZDA1NDk0YzA2N2RjMzNlMDA4Y2VjZjJjZDdiNDU0OTY1NGZhYjMifX19"
+ }
+ },
+ {
+ "id": "UNSTABLE_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "59b41a23-b7d0-33df-a196-968957aa0b48",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzk4MjI4YzIzNGMzOTAzYzUxMmE1YTBhYTQ1MjYwZTdiNTY3ZTBlMjBlZWZjN2Q1NjFjY2VjOTdiMjk1ODcxYWYifX19"
+ }
+ },
+ {
+ "id": "PROTECTOR_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "a228e918-722d-3a31-bc13-c9f905a0d3a2",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzQ4ZGUzMzlhZjYzYTIyOWM5MjM4ZDAyN2U0N2Y1M2VlYjU2MTQxYTQxOWY1MWIzNWMzMWVhMTQ5NGI0MzVkZDMifX19"
+ }
+ },
+ {
+ "id": "OLD_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "a3d7724b-faaf-3472-8010-4a67aba84d7c",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhYTA5YWQxNzdmYmNjYzUzZmEzMTZjYzA0YmRkMmM5MzY2YmFlZDg4OWRmNzZjNWEyOWRlZmVhODE3MGRlZjUifX19"
+ }
+ },
+ {
+ "id": "YOUNG_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "b896dea7-c289-3450-97b6-6de89f48ae4b",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzRiNWJkNmI2NGU4YmQ2YzU4ZjVjZDFlNzlhNTUwMmQ0NDQ4YmFmYzAwNmQyZmUwNTY4ZjZhMGQ2Yjg2ZDQ0OWUifX19"
+ }
+ },
+ {
+ "id": "STRONG_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "ddc6eaac-9166-3188-a081-7a33ebaaa3cd",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzZlZTMyZmJkNGM3YjAzYjg2OTA3OGFhMWY0OTNhMzkwZTZlMTNiNDYxZDYxMzcwN2VhZmIzMjZkYmNkMmI0YjUifX19"
+ }
+ },
+ {
+ "id": "SUPERIOR_FRAGMENT",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "51e54c0e-92ce-3b9f-9ee0-e35e8a302396",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzZmODliMTUwYmU5YzRjNTI0OWYzNTVmNjhlYTBjNDM5MTMwMGE5YmUxZjI2MGQ3NTBmYzM1YTE4MTdhZDc5NmUifX19"
+ }
+ },
+ {
+ "id": "CRYSTAL_FRAGMENT",
+ "displayItem": "minecraft:quartz",
+ "meta": 0,
+ "skullData": {
+ "id": "51e54c0e-92ce-3b9f-9ee0-e35e8a302396",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzZmODliMTUwYmU5YzRjNTI0OWYzNTVmNjhlYTBjNDM5MTMwMGE5YmUxZjI2MGQ3NTBmYzM1YTE4MTdhZDc5NmUifX19"
+ },
+ "enchanted": true
+ },
+ {
+ "id": "SUMMONING_EYE",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "00a702b9-7bad-3205-a04b-52478d8c0e7f",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGFhOGZjOGRlNjQxN2I0OGQ0OGM4MGI0NDNjZjUzMjZlM2Q5ZGE0ZGJlOWIyNWZjZDQ5NTQ5ZDk2MTY4ZmMwIn19fQ=="
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "RUINS",
+ "HOWLINGCAVE"
+ ],
+ "drops": [
+ {
+ "id": "WOLF_TOOTH",
+ "displayItem": "minecraft:ghast_tear",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "HAMSTER_WHEEL",
+ "displayItem": "minecraft:trapdoor",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "CRITICAL;6",
+ "displayItem": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "RED_CLAW_EGG",
+ "displayItem": "minecraft:spawn_egg",
+ "meta": 96
+ },
+ {
+ "id": "GRIZZLY_BAIT",
+ "displayItem": "minecraft:fish",
+ "meta": 1
+ },
+ {
+ "id": "OVERFLUX_CAPACITOR",
+ "displayItem": "minecraft:quartz",
+ "meta": 0,
+ "enchanted": true
+ }
+ ]
+ },
+ {
+ "location": [
+ "SPIDERSDEN"
+ ],
+ "drops": [
+ {
+ "id": "TOXIC_ARROW_POISON",
+ "displayItem": "minecraft:dye",
+ "meta": 10
+ },
+ {
+ "id": "SPIDER_CATALYST",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "3fe28c63-f3fc-30c2-8e74-ff1297977213",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTgzYjMwZTlkMTM1YjA1MTkwZWVhMmMzYWM2MWUyYWI1NWEyZDgxZTFhNThkYmIyNjk4M2ExNDA4MjY2NCJ9fX0="
+ }
+ },
+ {
+ "id": "BANE_OF_ARTHROPODS;6",
+ "displayItem": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "FLY_SWATTER",
+ "displayItem": "minecraft:golden_shovel",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "TARANTULA_TALISMAN",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "c89b16d8-4122-31e0-bb59-15cc95cdfe2c",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDQyY2Y4Y2U0ODdiNzhmYTIwM2Q1NmNmMDE0OTE0MzRiNGMzM2U1ZDIzNjgwMmM2ZDY5MTQ2YTUxNDM1YjAzZCJ9fX0="
+ }
+ },
+ {
+ "id": "DIGESTED_MOSQUITO",
+ "displayItem": "minecraft:rotten_flesh",
+ "meta": 0
+ }
+ ]
+ },
+ {
+ "location": [
+ "GRAVEYARD",
+ "COALMINE"
+ ],
+ "drops": [
+ {
+ "id": "FOUL_FLESH",
+ "displayItem": "minecraft:coal",
+ "meta": 1
+ },
+ {
+ "id": "UNDEAD_CATALYST",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "5bf9191d-dcdf-3c0f-aff9-7fcc5cb0a001",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODA2MjUzNjliMGE3YjA1MjYzMmRiNmI5MjZhODc2NzAyMTk1Mzk5MjI4MzZhYzU5NDBiZTI2ZDM0YmYxNGUxMCJ9fX0="
+ }
+ },
+ {
+ "id": "SMITE;6",
+ "displayItem": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "BEHEADED_HORROR",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "0862e0b0-a14f-3f93-894f-013502936b59",
+ "texture": "eyJ0aW1lc3RhbXAiOjE1Njg0NTc0MjAxMzcsInByb2ZpbGVJZCI6IjQxZDNhYmMyZDc0OTQwMGM5MDkwZDU0MzRkMDM4MzFiIiwicHJvZmlsZU5hbWUiOiJNZWdha2xvb24iLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RiYWQ5OWVkM2M4MjBiNzk3ODE5MGFkMDhhOTM0YTY4ZGZhOTBkOTk4NjgyNWRhMWM5N2Y2ZjIxZjQ5YWQ2MjYifX19"
+ }
+ },
+ {
+ "id": "REVENANT_CATALYST",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "5ace63c5-b3c9-306a-887c-16db7efea0f0",
+ "texture": "eyJ0aW1lc3RhbXAiOjE1NjgzNjYzMjYwNzEsInByb2ZpbGVJZCI6IjQxZDNhYmMyZDc0OTQwMGM5MDkwZDU0MzRkMDM4MzFiIiwicHJvZmlsZU5hbWUiOiJNZWdha2xvb24iLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2I4OGNmYWZhNWYwM2Y4YWVmMDQyYTE0Mzc5OWU5NjQzNDJkZjc2YjdjMWViNDYxZjYxOGUzOThmODRhOTlhNjMifX19"
+ }
+ },
+ {
+ "id": "REVENANT_VISCERA",
+ "displayItem": "minecraft:cooked_porkchop",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "SHARD_OF_THE_SHREDDED",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "9ddf6967-40de-3534-903f-4d5d9c933d55",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzBjNWNjNzI4Yzg2OWVjZjNjNmUwOTc5ZThhYTA5YzEwMTQ3ZWQ3NzA0MTdlNGJhNTQxYWFjMzgyZjAifX19"
+ }
+ },
+ {
+ "id": "SMITE;7",
+ "displayItem": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ },
+ {
+ "id": "WARDEN_HEART",
+ "displayItem": "minecraft:skull",
+ "meta": 3,
+ "skullData": {
+ "id": "7adc7613-256a-3593-899b-d4d9bbf50387",
+ "texture": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDQ1ZjRkMTM5YzllODkyNjJlYzA2YjI3YWFhZDczZmE0ODhhYjQ5MjkwZDJjY2Q2ODVhMjU1NDcyNTM3M2M5YiJ9fX0="
+ }
+ }
+ ]
+ },
+ {
+ "location": [
+ "YOURISLAND"
+ ],
+ "event": "traveling zoo",
+ "drops": [
+ {
+ "id": "SMITE;5",
+ "displayItem": "minecraft:enchanted_book",
+ "meta": 0,
+ "enchanted": true
+ }
+ ]
+ }
+ ]
+}