From 68abc8ade546de62aee76e0b786993fc59fbc3cc Mon Sep 17 00:00:00 2001 From: Walker Selby Date: Sat, 21 Oct 2023 07:31:19 +0100 Subject: Internal Change: Misc Refactoring (#551) Internal Change: Misc Refactoring #551 --- src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 44 +- .../java/at/hannibal2/skyhanni/config/Storage.java | 8 +- .../hannibal2/skyhanni/config/commands/Commands.kt | 2 +- .../skyhanni/config/features/CombatConfig.java | 4 +- .../skyhanni/events/BossHealthChangeEvent.kt | 2 +- .../events/DamageIndicatorDetectedEvent.kt | 2 +- .../hannibal2/skyhanni/features/chat/ChatPeek.kt | 30 + .../features/chat/CompactSplashPotionMessage.kt | 32 + .../skyhanni/features/combat/BestiaryData.kt | 489 ++++++++++++ .../skyhanni/features/combat/HideDamageSplash.kt | 28 + .../features/combat/damageindicator/BossType.kt | 111 +++ .../combat/damageindicator/DamageCounter.kt | 12 + .../damageindicator/DamageIndicatorManager.kt | 862 +++++++++++++++++++++ .../features/combat/damageindicator/EntityData.kt | 30 + .../combat/damageindicator/EntityResult.kt | 8 + .../features/combat/damageindicator/MobFinder.kt | 513 ++++++++++++ .../features/combat/damageindicator/OldDamage.kt | 3 + .../features/combat/endernodetracker/EnderNode.kt | 33 + .../combat/endernodetracker/EnderNodeTracker.kt | 239 ++++++ .../features/combat/ghostcounter/GhostCounter.kt | 494 ++++++++++++ .../features/combat/ghostcounter/GhostData.kt | 90 +++ .../combat/ghostcounter/GhostFormatting.kt | 182 +++++ .../features/combat/ghostcounter/GhostUtil.kt | 144 ++++ .../features/combat/mobs/AreaMiniBossFeatures.kt | 121 +++ .../combat/mobs/AshfangMinisNametagHider.kt | 28 + .../skyhanni/features/combat/mobs/MobHighlight.kt | 99 +++ .../skyhanni/features/combat/mobs/SpawnTimers.kt | 98 +++ .../tabcomplete/GetFromSacksTabComplete.kt | 45 ++ .../commands/tabcomplete/PlayerTabComplete.kt | 86 ++ .../features/commands/tabcomplete/TabComplete.kt | 38 + .../commands/tabcomplete/WarpTabComplete.kt | 26 + .../skyhanni/features/damageindicator/BossType.kt | 111 --- .../features/damageindicator/DamageCounter.kt | 12 - .../damageindicator/DamageIndicatorManager.kt | 862 --------------------- .../features/damageindicator/EntityData.kt | 30 - .../features/damageindicator/EntityResult.kt | 8 - .../skyhanni/features/damageindicator/MobFinder.kt | 513 ------------ .../skyhanni/features/damageindicator/OldDamage.kt | 3 - .../dungeon/DungeonBossHideDamageSplash.kt | 2 +- .../event/jerry/frozentreasure/FrozenTreasure.kt | 19 + .../jerry/frozentreasure/FrozenTreasureTracker.kt | 167 ++++ .../skyhanni/features/fishing/ChumBucketHider.kt | 83 ++ .../features/fishing/SeaCreatureFeatures.kt | 2 +- .../features/fishing/ThunderSparksHighlight.kt | 77 ++ .../skyhanni/features/inventory/ChestValue.kt | 277 +++++++ .../skyhanni/features/inventory/HarpFeatures.kt | 78 ++ .../skyhanni/features/inventory/tiarelay/Relay.kt | 48 ++ .../features/inventory/tiarelay/TiaRelayHelper.kt | 146 ++++ .../inventory/tiarelay/TiaRelayWaypoints.kt | 63 ++ .../features/itemabilities/ChickenHeadTimer.kt | 75 ++ .../mining/powdertracker/PowderChestReward.kt | 148 ++++ .../features/mining/powdertracker/PowderTracker.kt | 366 +++++++++ .../skyhanni/features/misc/BestiaryData.kt | 489 ------------ .../hannibal2/skyhanni/features/misc/ChatPeek.kt | 30 - .../hannibal2/skyhanni/features/misc/ChestValue.kt | 277 ------- .../skyhanni/features/misc/ChickenHeadTimer.kt | 75 -- .../skyhanni/features/misc/ChumBucketHider.kt | 83 -- .../features/misc/CompactSplashPotionMessage.kt | 32 - .../hannibal2/skyhanni/features/misc/EnderNode.kt | 33 - .../skyhanni/features/misc/EnderNodeTracker.kt | 239 ------ .../skyhanni/features/misc/FrozenTreasure.kt | 19 - .../features/misc/FrozenTreasureTracker.kt | 167 ---- .../skyhanni/features/misc/HarpFeatures.kt | 78 -- .../skyhanni/features/misc/HideDamageSplash.kt | 28 - .../features/misc/ThunderSparksHighlight.kt | 77 -- .../features/misc/ghostcounter/GhostCounter.kt | 494 ------------ .../features/misc/ghostcounter/GhostData.kt | 90 --- .../features/misc/ghostcounter/GhostFormatting.kt | 182 ----- .../features/misc/ghostcounter/GhostUtil.kt | 144 ---- .../misc/powdertracker/PowderChestReward.kt | 148 ---- .../features/misc/powdertracker/PowderTracker.kt | 366 --------- .../misc/tabcomplete/GetFromSacksTabComplete.kt | 45 -- .../features/misc/tabcomplete/PlayerTabComplete.kt | 86 -- .../features/misc/tabcomplete/TabComplete.kt | 38 - .../features/misc/tabcomplete/WarpTabComplete.kt | 26 - .../skyhanni/features/misc/tiarelay/Relay.kt | 48 -- .../features/misc/tiarelay/TiaRelayHelper.kt | 146 ---- .../features/misc/tiarelay/TiaRelayWaypoints.kt | 63 -- .../skyhanni/features/mobs/AreaMiniBossFeatures.kt | 121 --- .../features/mobs/AshfangMinisNametagHider.kt | 28 - .../skyhanni/features/mobs/MobHighlight.kt | 99 --- .../skyhanni/features/mobs/SpawnTimers.kt | 98 --- .../features/nether/ashfang/AshfangBlazes.kt | 4 +- .../features/nether/ashfang/AshfangBlazingSouls.kt | 4 +- .../nether/ashfang/AshfangFreezeCooldown.kt | 4 +- .../features/nether/ashfang/AshfangGravityOrbs.kt | 4 +- .../nether/ashfang/AshfangHideDamageIndicator.kt | 4 +- .../nether/ashfang/AshfangHideParticles.kt | 4 +- .../nether/ashfang/AshfangNextResetCooldown.kt | 4 +- .../miniboss/DailyMiniBossHelper.kt | 2 +- .../features/slayer/SlayerMiniBossFeatures.kt | 2 +- .../features/slayer/blaze/BlazeSlayerClearView.kt | 4 +- .../slayer/blaze/BlazeSlayerFirePitsWarning.kt | 4 +- .../skyhanni/mixins/transformers/MixinGuiChat.java | 2 +- .../mixins/transformers/gui/MixinGuiNewChat.java | 2 +- .../at/hannibal2/skyhanni/utils/CombatUtils.kt | 4 +- 96 files changed, 5445 insertions(+), 5445 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/chat/ChatPeek.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityData.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityResult.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/MobFinder.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/OldDamage.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNode.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNodeTracker.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostData.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostFormatting.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AreaMiniBossFeatures.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AshfangMinisNametagHider.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/mobs/MobHighlight.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/mobs/SpawnTimers.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/GetFromSacksTabComplete.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/BossType.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageCounter.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageIndicatorManager.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityData.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityResult.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/MobFinder.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/damageindicator/OldDamage.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasure.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasureTracker.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/fishing/ChumBucketHider.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/fishing/ThunderSparksHighlight.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/inventory/ChestValue.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/inventory/HarpFeatures.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/Relay.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayHelper.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayWaypoints.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/itemabilities/ChickenHeadTimer.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderChestReward.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/BestiaryData.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ChatPeek.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ChestValue.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ChickenHeadTimer.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ChumBucketHider.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/CompactSplashPotionMessage.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/EnderNode.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/EnderNodeTracker.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/FrozenTreasure.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/FrozenTreasureTracker.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/HarpFeatures.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/HideDamageSplash.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ThunderSparksHighlight.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ghostcounter/GhostCounter.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ghostcounter/GhostData.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ghostcounter/GhostFormatting.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ghostcounter/GhostUtil.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/powdertracker/PowderChestReward.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/powdertracker/PowderTracker.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tabcomplete/GetFromSacksTabComplete.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tabcomplete/PlayerTabComplete.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tabcomplete/TabComplete.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tabcomplete/WarpTabComplete.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tiarelay/Relay.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tiarelay/TiaRelayHelper.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/tiarelay/TiaRelayWaypoints.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mobs/AreaMiniBossFeatures.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mobs/AshfangMinisNametagHider.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mobs/MobHighlight.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mobs/SpawnTimers.kt diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 7d4fd9d05..174cf38fa 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -62,7 +62,7 @@ import at.hannibal2.skyhanni.features.commands.SendCoordinatedCommand import at.hannibal2.skyhanni.features.commands.WarpIsCommand import at.hannibal2.skyhanni.features.commands.WikiCommand import at.hannibal2.skyhanni.features.cosmetics.CosmeticFollowingLine -import at.hannibal2.skyhanni.features.damageindicator.DamageIndicatorManager +import at.hannibal2.skyhanni.features.combat.damageindicator.DamageIndicatorManager import at.hannibal2.skyhanni.features.dungeon.CroesusUnopenedChestTracker import at.hannibal2.skyhanni.features.dungeon.DungeonAPI import at.hannibal2.skyhanni.features.dungeon.DungeonBossHideDamageSplash @@ -162,23 +162,23 @@ import at.hannibal2.skyhanni.features.mining.KingTalismanHelper import at.hannibal2.skyhanni.features.mining.crystalhollows.CrystalHollowsNamesInCore import at.hannibal2.skyhanni.features.minion.MinionCollectLogic import at.hannibal2.skyhanni.features.minion.MinionFeatures -import at.hannibal2.skyhanni.features.misc.BestiaryData +import at.hannibal2.skyhanni.features.combat.BestiaryData import at.hannibal2.skyhanni.features.misc.BrewingStandOverlay import at.hannibal2.skyhanni.features.misc.ButtonOnPause -import at.hannibal2.skyhanni.features.misc.ChestValue -import at.hannibal2.skyhanni.features.misc.ChickenHeadTimer -import at.hannibal2.skyhanni.features.misc.ChumBucketHider +import at.hannibal2.skyhanni.features.inventory.ChestValue +import at.hannibal2.skyhanni.features.itemabilities.ChickenHeadTimer +import at.hannibal2.skyhanni.features.fishing.ChumBucketHider import at.hannibal2.skyhanni.features.misc.CollectionTracker -import at.hannibal2.skyhanni.features.misc.CompactSplashPotionMessage +import at.hannibal2.skyhanni.features.chat.CompactSplashPotionMessage import at.hannibal2.skyhanni.features.misc.CurrentPetDisplay import at.hannibal2.skyhanni.features.misc.CustomTextBox -import at.hannibal2.skyhanni.features.misc.EnderNodeTracker +import at.hannibal2.skyhanni.features.combat.endernodetracker.EnderNodeTracker import at.hannibal2.skyhanni.features.misc.ExpOrbsOnGroundHider import at.hannibal2.skyhanni.features.misc.FixNEUHeavyPearls -import at.hannibal2.skyhanni.features.misc.FrozenTreasureTracker -import at.hannibal2.skyhanni.features.misc.HarpFeatures +import at.hannibal2.skyhanni.features.event.jerry.frozentreasure.FrozenTreasureTracker +import at.hannibal2.skyhanni.features.inventory.HarpFeatures import at.hannibal2.skyhanni.features.misc.HideArmor -import at.hannibal2.skyhanni.features.misc.HideDamageSplash +import at.hannibal2.skyhanni.features.combat.HideDamageSplash import at.hannibal2.skyhanni.features.misc.InGameDateDisplay import at.hannibal2.skyhanni.features.misc.JoinCrystalHollows import at.hannibal2.skyhanni.features.misc.LimboTimeTracker @@ -200,35 +200,35 @@ import at.hannibal2.skyhanni.features.misc.RestorePieceOfWizardPortalLore import at.hannibal2.skyhanni.features.misc.ServerRestartTitle import at.hannibal2.skyhanni.features.misc.SkyBlockKickDuration import at.hannibal2.skyhanni.features.misc.SuperpairsClicksAlert -import at.hannibal2.skyhanni.features.misc.ThunderSparksHighlight +import at.hannibal2.skyhanni.features.fishing.ThunderSparksHighlight import at.hannibal2.skyhanni.features.misc.TimeFeatures import at.hannibal2.skyhanni.features.misc.TpsCounter import at.hannibal2.skyhanni.features.misc.compacttablist.AdvancedPlayerList import at.hannibal2.skyhanni.features.misc.compacttablist.TabListReader import at.hannibal2.skyhanni.features.misc.compacttablist.TabListRenderer import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager -import at.hannibal2.skyhanni.features.misc.ghostcounter.GhostCounter +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostCounter import at.hannibal2.skyhanni.features.misc.items.EstimatedItemValue import at.hannibal2.skyhanni.features.misc.items.EstimatedWardrobePrice import at.hannibal2.skyhanni.features.misc.items.GlowingDroppedItems import at.hannibal2.skyhanni.features.misc.massconfiguration.DefaultConfigFeatures -import at.hannibal2.skyhanni.features.misc.powdertracker.PowderTracker -import at.hannibal2.skyhanni.features.misc.tabcomplete.GetFromSacksTabComplete -import at.hannibal2.skyhanni.features.misc.tabcomplete.PlayerTabComplete -import at.hannibal2.skyhanni.features.misc.tabcomplete.WarpTabComplete +import at.hannibal2.skyhanni.features.mining.powdertracker.PowderTracker +import at.hannibal2.skyhanni.features.commands.tabcomplete.GetFromSacksTabComplete +import at.hannibal2.skyhanni.features.commands.tabcomplete.PlayerTabComplete +import at.hannibal2.skyhanni.features.commands.tabcomplete.WarpTabComplete import at.hannibal2.skyhanni.features.misc.teleportpad.TeleportPadCompactName import at.hannibal2.skyhanni.features.misc.teleportpad.TeleportPadInventoryNumber -import at.hannibal2.skyhanni.features.misc.tiarelay.TiaRelayHelper -import at.hannibal2.skyhanni.features.misc.tiarelay.TiaRelayWaypoints +import at.hannibal2.skyhanni.features.inventory.tiarelay.TiaRelayHelper +import at.hannibal2.skyhanni.features.inventory.tiarelay.TiaRelayWaypoints import at.hannibal2.skyhanni.features.misc.trevor.TrevorFeatures import at.hannibal2.skyhanni.features.misc.trevor.TrevorSolver import at.hannibal2.skyhanni.features.misc.trevor.TrevorTracker import at.hannibal2.skyhanni.features.misc.update.UpdateManager import at.hannibal2.skyhanni.features.misc.visualwords.ModifyVisualWords -import at.hannibal2.skyhanni.features.mobs.AreaMiniBossFeatures -import at.hannibal2.skyhanni.features.mobs.AshfangMinisNametagHider -import at.hannibal2.skyhanni.features.mobs.MobHighlight -import at.hannibal2.skyhanni.features.mobs.SpawnTimers +import at.hannibal2.skyhanni.features.combat.mobs.AreaMiniBossFeatures +import at.hannibal2.skyhanni.features.combat.mobs.AshfangMinisNametagHider +import at.hannibal2.skyhanni.features.combat.mobs.MobHighlight +import at.hannibal2.skyhanni.features.combat.mobs.SpawnTimers import at.hannibal2.skyhanni.features.nether.QuestItemHelper import at.hannibal2.skyhanni.features.nether.ashfang.AshfangBlazes import at.hannibal2.skyhanni.features.nether.ashfang.AshfangBlazingSouls diff --git a/src/main/java/at/hannibal2/skyhanni/config/Storage.java b/src/main/java/at/hannibal2/skyhanni/config/Storage.java index 6f3fd7ddf..21ed83b00 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/Storage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/Storage.java @@ -8,10 +8,10 @@ import at.hannibal2.skyhanni.features.garden.CropType; import at.hannibal2.skyhanni.features.garden.farming.FarmingArmorDrops; import at.hannibal2.skyhanni.features.garden.fortuneguide.FarmingItems; import at.hannibal2.skyhanni.features.garden.visitor.VisitorReward; -import at.hannibal2.skyhanni.features.misc.EnderNode; -import at.hannibal2.skyhanni.features.misc.FrozenTreasure; -import at.hannibal2.skyhanni.features.misc.ghostcounter.GhostData; -import at.hannibal2.skyhanni.features.misc.powdertracker.PowderChestReward; +import at.hannibal2.skyhanni.features.combat.endernodetracker.EnderNode; +import at.hannibal2.skyhanni.features.event.jerry.frozentreasure.FrozenTreasure; +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostData; +import at.hannibal2.skyhanni.features.mining.powdertracker.PowderChestReward; import at.hannibal2.skyhanni.features.misc.trevor.TrevorTracker; import at.hannibal2.skyhanni.features.misc.visualwords.VisualWord; import at.hannibal2.skyhanni.features.rift.area.westvillage.KloonTerminal; diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt index 5eccdf488..0ba46c155 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -27,7 +27,7 @@ import at.hannibal2.skyhanni.features.misc.CollectionTracker import at.hannibal2.skyhanni.features.misc.LockMouseLook import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager -import at.hannibal2.skyhanni.features.misc.ghostcounter.GhostUtil +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil import at.hannibal2.skyhanni.features.misc.massconfiguration.DefaultConfigFeatures import at.hannibal2.skyhanni.features.misc.visualwords.VisualWordGui import at.hannibal2.skyhanni.features.slayer.SlayerItemProfitTracker diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/CombatConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/CombatConfig.java index a54feefe5..458e3daf7 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/CombatConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/CombatConfig.java @@ -2,8 +2,8 @@ package at.hannibal2.skyhanni.config.features; import at.hannibal2.skyhanni.config.FeatureToggle; import at.hannibal2.skyhanni.config.core.config.Position; -import at.hannibal2.skyhanni.features.misc.ghostcounter.GhostFormatting; -import at.hannibal2.skyhanni.features.misc.ghostcounter.GhostUtil; +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostFormatting; +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil; import com.google.gson.annotations.Expose; import io.github.moulberry.moulconfig.annotations.Accordion; import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; diff --git a/src/main/java/at/hannibal2/skyhanni/events/BossHealthChangeEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/BossHealthChangeEvent.kt index e2837ea3f..10efc2823 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/BossHealthChangeEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/BossHealthChangeEvent.kt @@ -1,6 +1,6 @@ package at.hannibal2.skyhanni.events -import at.hannibal2.skyhanni.features.damageindicator.EntityData +import at.hannibal2.skyhanni.features.combat.damageindicator.EntityData class BossHealthChangeEvent(val entityData: EntityData, val lastHealth: Long, val health: Long, val maxHealth: Long) : LorenzEvent() \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorDetectedEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorDetectedEvent.kt index 2203ab724..dbf2fd130 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorDetectedEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/DamageIndicatorDetectedEvent.kt @@ -1,5 +1,5 @@ package at.hannibal2.skyhanni.events -import at.hannibal2.skyhanni.features.damageindicator.EntityData +import at.hannibal2.skyhanni.features.combat.damageindicator.EntityData class DamageIndicatorDetectedEvent(val entityData: EntityData) : LorenzEvent() \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/ChatPeek.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/ChatPeek.kt new file mode 100644 index 000000000..5d5580772 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/ChatPeek.kt @@ -0,0 +1,30 @@ +package at.hannibal2.skyhanni.features.chat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.GuiEditManager +import at.hannibal2.skyhanni.utils.KeyboardManager.isKeyHeld +import at.hannibal2.skyhanni.features.garden.fortuneguide.FFGuideGUI +import at.hannibal2.skyhanni.features.misc.visualwords.VisualWordGui +import at.hannibal2.skyhanni.utils.NEUItems +import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapper +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiEditSign +import org.lwjgl.input.Keyboard + + +object ChatPeek { + @JvmStatic + fun peek(): Boolean { + val key = SkyHanniMod.feature.chat.peekChat + + if (Minecraft.getMinecraft().thePlayer == null) return false + if (key <= Keyboard.KEY_NONE) return false + if (Minecraft.getMinecraft().currentScreen is GuiEditSign) return false + if (Minecraft.getMinecraft().currentScreen is GuiScreenElementWrapper) return false + + if (NEUItems.neuHasFocus()) return false + if (GuiEditManager.isInGui() || FFGuideGUI.isInGui() || VisualWordGui.isInGui()) return false + + return key.isKeyHeld() + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt new file mode 100644 index 000000000..dfc0db3f9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt @@ -0,0 +1,32 @@ +package at.hannibal2.skyhanni.features.chat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import net.minecraft.util.ChatComponentText +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class CompactSplashPotionMessage { + private val potionEffectPattern = + "§a§lBUFF! §fYou have gained §r(?.*)§r§f! Press TAB or type /effects to view your active effects!".toPattern() + private val potionEffectOthersPattern = + "§a§lBUFF! §fYou were splashed by (?.*) §fwith §r(?.*)§r§f! Press TAB or type /effects to view your active effects!".toPattern() + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyBlock || !SkyHanniMod.feature.chat.compactPotionMessage) return + + potionEffectPattern.matchMatcher(event.message) { + val name = group("name") + event.chatComponent = ChatComponentText("§a§lPotion Effect! §r$name") + } + + potionEffectOthersPattern.matchMatcher(event.message) { + val playerName = group("playerName").removeColor() + val effectName = group("effectName") + event.chatComponent = ChatComponentText("§a§lPotion Effect! §r$effectName by §b$playerName") + } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt new file mode 100644 index 000000000..5f0a4125e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt @@ -0,0 +1,489 @@ +package at.hannibal2.skyhanni.features.combat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.addButton +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber +import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNeeded +import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.toRoman +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.renderables.Renderable +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + + +object BestiaryData { + + private val config get() = SkyHanniMod.feature.combat.bestiary + private var display = emptyList>() + private val mobList = mutableListOf() + private val stackList = mutableMapOf() + private val catList = mutableListOf() + private val progressPattern = "(?[0-9kKmMbB,.]+)/(?[0-9kKmMbB,.]+$)".toPattern() + private val titlePattern = "^(?:\\(\\d+/\\d+\\) )?(Bestiary|.+) ➜ (.+)$".toPattern() + private var inInventory = false + private var isCategory = false + private var indexes = listOf( + 10, 11, 12, 13, 14, 15, 16, + 19, 20, 21, 22, 23, 24, 25, + 28, 29, 30, 31, 32, 33, 34, + 37, 38, 39, 40, 41, 42, 43 + ) + + @SubscribeEvent + fun onBackgroundDraw(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { + if (!isEnabled()) return + if (inInventory) { + config.position.renderStringsAndItems( + display, + extraSpace = -1, + itemScale = 1.3, + posLabel = "Bestiary Data" + ) + } + } + + @SubscribeEvent + fun onRender(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!isEnabled()) return + if (inInventory) { + for (slot in InventoryUtils.getItemsInOpenChest()) { + val stack = slot.stack + val lore = stack.getLore() + if (lore.any { it == "§7Overall Progress: §b100% §7(§c§lMAX!§7)" || it == "§7Families Completed: §a100%" }) { + slot highlight LorenzColor.GREEN + } + } + } + } + + @SubscribeEvent + fun onInventoryOpen(event: InventoryFullyOpenedEvent) { + if (!isEnabled()) return + val inventoryName = event.inventoryName + val stack = event.inventoryItems[4] ?: return + if ((inventoryName == "Bestiary ➜ Fishing" || inventoryName == "Bestiary") || isBestiaryGui( + stack, + inventoryName + ) + ) { + isCategory = inventoryName == "Bestiary ➜ Fishing" || inventoryName == "Bestiary" + stackList.putAll(event.inventoryItems) + inInventory = true + update() + } + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + mobList.clear() + stackList.clear() + inInventory = false + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.bestiaryData", "combat.bestiary") + } + + private fun update() { + display = drawDisplay() + } + + private fun init() { + mobList.clear() + catList.clear() + if (isCategory) { + inCategory() + } else { + notInCategory() + } + } + + private fun inCategory() { + for ((index, stack) in stackList) { + if (stack.displayName == " ") continue + if (!indexes.contains(index)) continue + inInventory = true + val name = stack.displayName + var familiesFound: Long = 0 + var totalFamilies: Long = 0 + var familiesCompleted: Long = 0 + for ((lineIndex, loreLine) in stack.getLore().withIndex()) { + val line = loreLine.removeColor() + if (!line.startsWith(" ")) continue + val previousLine = stack.getLore()[lineIndex - 1] + val progress = line.substring(line.lastIndexOf(' ') + 1) + if (previousLine.contains("Families Found")) { + progressPattern.matchMatcher(progress) { + familiesFound = group("current").formatNumber() + totalFamilies = group("needed").formatNumber() + } + } else if (previousLine.contains("Families Completed")) { + progressPattern.matchMatcher(progress) { + familiesCompleted = group("current").formatNumber() + } + } + } + catList.add(Category(name, familiesFound, totalFamilies, familiesCompleted)) + } + } + + private fun notInCategory() { + for ((index, stack) in stackList) { + if (stack.displayName == " ") continue + if (!indexes.contains(index)) continue + inInventory = true + val name = " [IVX0-9]+$".toPattern().matcher(stack.displayName).replaceFirst("") + val level = " ([IVX0-9]+$)".toRegex().find(stack.displayName)?.groupValues?.get(1) ?: "0" + var totalKillToMax: Long = 0 + var currentTotalKill: Long = 0 + var totalKillToTier: Long = 0 + var currentKillToTier: Long = 0 + var actualRealTotalKill: Long = 0 + for ((lineIndex, line) in stack.getLore().withIndex()) { + val loreLine = line.removeColor() + if (loreLine.startsWith("Kills: ")) { + actualRealTotalKill = + "([0-9,.]+)".toRegex().find(loreLine)?.groupValues?.get(1)?.formatNumber() + ?: 0 + } + if (!loreLine.startsWith(" ")) continue + val previousLine = stack.getLore()[lineIndex - 1] + val progress = loreLine.substring(loreLine.lastIndexOf(' ') + 1) + if (previousLine.contains("Progress to Tier")) { + progressPattern.matchMatcher(progress) { + totalKillToTier = group("needed").formatNumber() + currentKillToTier = group("current").formatNumber() + } + } else if (previousLine.contains("Overall Progress")) { + progressPattern.matchMatcher(progress) { + totalKillToMax = group("needed").formatNumber() + currentTotalKill = group("current").formatNumber() + } + } + } + mobList.add( + BestiaryMob( + name, + level, + totalKillToMax, + currentTotalKill, + totalKillToTier, + currentKillToTier, + actualRealTotalKill + ) + ) + } + } + + private fun drawDisplay(): List> { + val newDisplay = mutableListOf>() + init() + + addCategories(newDisplay) + + if (mobList.isEmpty()) return newDisplay + + addList(newDisplay) + + addButtons(newDisplay) + + return newDisplay + } + + private fun sortMobList(): MutableList { + val sortedMobList = when (config.displayType) { + 0 -> mobList.sortedBy { it.percentToMax() } + 1 -> mobList.sortedBy { it.percentToTier() } + 2 -> mobList.sortedBy { it.totalKills } + 3 -> mobList.sortedByDescending { it.totalKills } + 4 -> mobList.sortedBy { it.killNeededToMax() } + 5 -> mobList.sortedByDescending { it.killNeededToMax() } + 6 -> mobList.sortedBy { it.killNeededToNextLevel() } + 7 -> mobList.sortedByDescending { it.killNeededToNextLevel() } + else -> mobList.sortedBy { it.totalKills } + }.toMutableList() + return sortedMobList + } + + private fun addList(newDisplay: MutableList>) { + val sortedMobList = sortMobList() + + newDisplay.addAsSingletonList("§7Bestiary Data") + for (mob in sortedMobList) { + val isUnlocked = mob.totalKills != 0.toLong() + val isMaxed = mob.percentToMax() >= 1 + if (!isUnlocked) { + newDisplay.add(buildList { + add(" §7- ") + add("${mob.name}: §cNot unlocked!") + }) + continue + } + if (isMaxed && config.hideMaxed) continue + val text = getMobLine(mob, isMaxed) + val tips = getMobHover(mob) + newDisplay.addAsSingletonList(Renderable.hoverTips(text, tips) { true }) + } + } + + private fun getMobHover(mob: BestiaryMob) = listOf( + "§6Name: §b${mob.name}", + "§6Level: §b${mob.level} ${if (!config.replaceRoman) "§7(${mob.level.romanToDecimalIfNeeded()})" else ""}", + "§6Total Kills: §b${mob.actualRealTotalKill.formatNumber()}", + "§6Kills needed to max: §b${mob.killNeededToMax().formatNumber()}", + "§6Kills needed to next lvl: §b${mob.killNeededToNextLevel().formatNumber()}", + "§6Current kill to next level: §b${mob.currentKillToNextLevel.formatNumber()}", + "§6Kill needed for next level: §b${mob.killNeededForNextLevel.formatNumber()}", + "§6Current kill to max: §b${mob.killToMax.formatNumber()}", + "§6Percent to max: §b${mob.percentToMaxFormatted()}", + "§6Percent to tier: §b${mob.percentToTierFormatted()}", + "", + "§7More info thing" + ) + + private fun getMobLine( + mob: BestiaryMob, + isMaxed: Boolean + ): String { + val displayType = config.displayType + var text = "" + text += " §7- " + text += "${mob.name} ${mob.level.romanOrInt()} " + text += if (isMaxed) { + "§c§lMAXED! §7(§b${mob.actualRealTotalKill.formatNumber()}§7 kills)" + } else { + when (displayType) { + 0, 1 -> { + val currentKill = when (displayType) { + 0 -> mob.totalKills + 1 -> mob.currentKillToNextLevel + else -> 0 + } + val killNeeded = when (displayType) { + 0 -> mob.killToMax + 1 -> mob.killNeededForNextLevel + else -> 0 + } + "§7(§b${currentKill.formatNumber()}§7/§b${killNeeded.formatNumber()}§7) §a${ + ((currentKill.toDouble() / killNeeded) * 100).roundToPrecision( + 2 + ) + }§6% ${if (displayType == 1) "§ato level ${mob.getNextLevel()}" else ""}" + } + + 2, 3 -> { + + "§6${mob.totalKills.formatNumber()} §7total kills" + } + + 4, 5 -> { + "§6${mob.killNeededToMax().formatNumber()} §7kills needed" + } + + 6, 7 -> { + "§6${mob.killNeededToNextLevel().formatNumber()} §7kills needed" + } + + else -> "§cYou are not supposed to see this, please report it to @HiZe on discord!" + } + } + return text + } + + private fun addButtons(newDisplay: MutableList>) { + newDisplay.addButton( + prefix = "§7Number Format: ", + getName = FormatType.entries[config.numberFormat].type, + onChange = { + config.numberFormat = (config.numberFormat + 1) % 2 + update() + }) + + newDisplay.addButton( + prefix = "§7Display Type: ", + getName = DisplayType.entries[config.displayType].type, + onChange = { + config.displayType = (config.displayType + 1) % 8 + update() + }) + + newDisplay.addButton( + prefix = "§7Number Type: ", + getName = NumberType.entries[if (config.replaceRoman) 0 else 1].type, + onChange = { + config.replaceRoman = !config.replaceRoman + update() + } + ) + newDisplay.addButton( + prefix = "§7Hide Maxed: ", + getName = HideMaxed.entries[if (config.hideMaxed) 1 else 0].type, + onChange = { + config.hideMaxed = !config.hideMaxed + update() + } + ) + } + + private fun addCategories(newDisplay: MutableList>) { + if (catList.isNotEmpty()) { + newDisplay.addAsSingletonList("§7Category") + for (cat in catList) { + newDisplay.add(buildList { + add(" §7- ${cat.name}§7: ") + val element = when { + cat.familiesCompleted == cat.totalFamilies -> "§c§lCompleted!" + cat.familiesFound == cat.totalFamilies -> "§b${cat.familiesCompleted}§7/§b${cat.totalFamilies} §7completed" + cat.familiesFound < cat.totalFamilies -> + "§b${cat.familiesFound}§7/§b${cat.totalFamilies} §7found, §b${cat.familiesCompleted}§7/§b${cat.totalFamilies} §7completed" + + else -> continue + } + add(element) + }) + } + } + } + + private fun isBestiaryGui(stack: ItemStack?, name: String): Boolean { + if (stack == null) return false + val bestiaryGuiTitleMatcher = titlePattern.matcher(name) + if (bestiaryGuiTitleMatcher.matches()) { + if ("Bestiary" != bestiaryGuiTitleMatcher.group(1)) { + var loreContainsFamiliesFound = false + for (line in stack.getLore()) { + if (line.removeColor().startsWith("Families Found")) { + loreContainsFamiliesFound = true + break + } + } + if (!loreContainsFamiliesFound) { + return false + } + } + return true + } else if (name == "Search Results") { + val loreList = stack.getLore() + if (loreList.size >= 2 && loreList[0].startsWith("§7Query: §a") + && loreList[1].startsWith("§7Results: §a") + ) { + return true + } + } + return false + } + + enum class FormatType(val type: String) { + SHORT("Short"), + LONG("Long") + } + + enum class NumberType(val type: String) { + INT("Normal (1, 2, 3)"), + ROMAN("Roman (I, II, III") + } + + enum class DisplayType(val type: String) { + GLOBAL_MAX("Global display (to max)"), + GLOBAL_TIER("Global display (to next tier)"), + LOWEST_TOTAL("Lowest total kills"), + HIGHEST_TOTAL("Highest total kills"), + LOWEST_NEEDED_MAX("Lowest kills needed to max"), + HIGHEST_NEEDED_MAX("Highest kills needed to max"), + LOWEST_NEEDED_TIER("Lowest kills needed to next tier"), + HIGHEST_NEEDED_TIER("Highest kills needed to next tier"), + } + + enum class HideMaxed(val type: String) { + NO("Show"), + YES("Hide") + } + + private fun Long.formatNumber(): String = when (config.numberFormat) { + 0 -> NumberUtil.format(this) + 1 -> this.addSeparators() + else -> "0" + } + + data class Category( + val name: String, + val familiesFound: Long, + val totalFamilies: Long, + val familiesCompleted: Long + ) + + data class BestiaryMob( + var name: String, + var level: String, + var killToMax: Long, + var totalKills: Long, + var killNeededForNextLevel: Long, + var currentKillToNextLevel: Long, + var actualRealTotalKill: Long + ) { + + fun killNeededToMax(): Long { + return 0L.coerceAtLeast(killToMax - totalKills) + } + + fun killNeededToNextLevel(): Long { + return 0L.coerceAtLeast(killNeededForNextLevel - currentKillToNextLevel) + } + + fun percentToMax() = totalKills.toDouble() / killToMax + + fun percentToMaxFormatted() = LorenzUtils.formatPercentage(percentToMax()) + + fun percentToTier() = currentKillToNextLevel.toDouble() / killNeededForNextLevel + + fun percentToTierFormatted() = LorenzUtils.formatPercentage(percentToTier()) + + fun getNextLevel() = level.getNextLevel() + } + + + private fun String.romanOrInt() = romanToDecimalIfNeeded().let { + if (config.replaceRoman || it == 0) it.toString() else it.toRoman() + } + + fun Any.getNextLevel(): String { + return when (this) { + is Int -> { + (this + 1).toString().romanOrInt() + } + + is String -> { + if (this == "0") { + "I".romanOrInt() + } else { + val intValue = romanToDecimalIfNeeded() + (intValue + 1).toRoman().romanOrInt() + } + } + + else -> { + "Unsupported type: ${this::class.simpleName}" + } + } + } + + private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled + +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt new file mode 100644 index 000000000..7b2684c40 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.features.combat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.features.combat.damageindicator.DamageIndicatorManager +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraft.entity.EntityLivingBase +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class HideDamageSplash { + + @SubscribeEvent(priority = EventPriority.HIGH) + fun onRenderDamage(event: RenderLivingEvent.Specials.Pre) { + if (!LorenzUtils.inSkyBlock) return + if (!SkyHanniMod.feature.combat.hideDamageSplash) return + + if (DamageIndicatorManager.isDamageSplash(event.entity)) { + event.isCanceled = true + } + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.hideDamageSplash", "combat.hideDamageSplash") + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt new file mode 100644 index 000000000..664aa63d4 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt @@ -0,0 +1,111 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +enum class BossType( + val fullName: String, + val bossTypeToggle: Int, + val shortName: String = fullName, + val showDeathTime: Boolean = false +) { + GENERIC_DUNGEON_BOSS("Generic Dungeon boss", 0),//TODO split into different bosses + + //Nether Mini Bosses + NETHER_BLADESOUL("§8Bladesoul", 1), + NETHER_MAGMA_BOSS("§4Magma Boss", 1), + NETHER_ASHFANG("§cAshfang", 1), + NETHER_BARBARIAN_DUKE("§eBarbarian Duke", 1), + NETHER_MAGE_OUTLAW("§5Mage Outlaw", 1), + + NETHER_VANQUISHER("§5Vanquisher", 2), + + END_ENDSTONE_PROTECTOR("§cEndstone Protector", 3), + END_ENDER_DRAGON("Ender Dragon", 4),//TODO fix totally + + SLAYER_ZOMBIE_1("§aRevenant Horror 1", 5, "§aRev 1", showDeathTime = true), + SLAYER_ZOMBIE_2("§eRevenant Horror 2", 5, "§eRev 2", showDeathTime = true), + SLAYER_ZOMBIE_3("§cRevenant Horror 3", 5, "§cRev 3", showDeathTime = true), + SLAYER_ZOMBIE_4("§4Revenant Horror 4", 5, "§4Rev 4", showDeathTime = true), + SLAYER_ZOMBIE_5("§5Revenant Horror 5", 5, "§5Rev 5", showDeathTime = true), + + SLAYER_SPIDER_1("§aTarantula Broodfather 1", 6, "§aTara 1", showDeathTime = true), + SLAYER_SPIDER_2("§eTarantula Broodfather 2", 6, "§eTara 2", showDeathTime = true), + SLAYER_SPIDER_3("§cTarantula Broodfather 3", 6, "§cTara 3", showDeathTime = true), + SLAYER_SPIDER_4("§4Tarantula Broodfather 4", 6, "§4Tara 4", showDeathTime = true), + + SLAYER_WOLF_1("§aSven Packmaster 1", 7, "§aSven 1", showDeathTime = true), + SLAYER_WOLF_2("§eSven Packmaster 2", 7, "§eSven 2", showDeathTime = true), + SLAYER_WOLF_3("§cSven Packmaster 3", 7, "§cSven 3", showDeathTime = true), + SLAYER_WOLF_4("§4Sven Packmaster 4", 7, "§4Sven 4", showDeathTime = true), + + SLAYER_ENDERMAN_1("§aVoidgloom Seraph 1", 8, "§aVoid 1", showDeathTime = true), + SLAYER_ENDERMAN_2("§eVoidgloom Seraph 2", 8, "§eVoid 2", showDeathTime = true), + SLAYER_ENDERMAN_3("§cVoidgloom Seraph 3", 8, "§cVoid 3", showDeathTime = true), + SLAYER_ENDERMAN_4("§4Voidgloom Seraph 4", 8, "§4Void 4", showDeathTime = true), + + SLAYER_BLAZE_1("§aInferno Demonlord 1", 9, "§aInferno 1", showDeathTime = true), + SLAYER_BLAZE_2("§aInferno Demonlord 2", 9, "§aInferno 2", showDeathTime = true), + SLAYER_BLAZE_3("§aInferno Demonlord 3", 9, "§aInferno 3", showDeathTime = true), + SLAYER_BLAZE_4("§aInferno Demonlord 4", 9, "§aInferno 4", showDeathTime = true), + + SLAYER_BLAZE_TYPHOEUS_1("§aInferno Typhoeus 1", 9, "§aTyphoeus 1"), + SLAYER_BLAZE_TYPHOEUS_2("§eInferno Typhoeus 2", 9, "§eTyphoeus 2"), + SLAYER_BLAZE_TYPHOEUS_3("§cInferno Typhoeus 3", 9, "§cTyphoeus 3"), + SLAYER_BLAZE_TYPHOEUS_4("§cInferno Typhoeus 4", 9, "§cTyphoeus 4"), + + SLAYER_BLAZE_QUAZII_1("§aInferno Quazii 1", 9, "§aQuazii 1"), + SLAYER_BLAZE_QUAZII_2("§eInferno Quazii 2", 9, "§eQuazii 2"), + SLAYER_BLAZE_QUAZII_3("§cInferno Quazii 3", 9, "§cQuazii 3"), + SLAYER_BLAZE_QUAZII_4("§cInferno Quazii 4", 9, "§cQuazii 4"), + + SLAYER_BLOODFIEND_1("§aRiftstalker Bloodfiend 1", 23, "§aBlood 1", showDeathTime = true), + SLAYER_BLOODFIEND_2("§6Riftstalker Bloodfiend 2", 23, "§6Blood 2", showDeathTime = true), + SLAYER_BLOODFIEND_3("§cRiftstalker Bloodfiend 3", 23, "§cBlood 3", showDeathTime = true), + SLAYER_BLOODFIEND_4("§4Riftstalker Bloodfiend 4", 23, "§4Blood 4", showDeathTime = true), + SLAYER_BLOODFIEND_5("§5Riftstalker Bloodfiend 5", 23, "§5Blood 5", showDeathTime = true), + + HUB_HEADLESS_HORSEMAN("§6Headless Horseman", 10), + + DUNGEON_F1("", 11), + DUNGEON_F2("", 12), + DUNGEON_F3("", 13), + DUNGEON_F4_THORN("§cThorn", 14), + DUNGEON_F5("§dLivid", 15), + DUNGEON_F("", 16), + DUNGEON_75("", 17), + + MINOS_INQUISITOR("§5Minos Inquisitor", 18), + MINOS_CHAMPION("§2Minos Champion", 18), + GAIA_CONSTURUCT("§2Gaia Construct", 18), + MINOTAUR("§2Minotaur", 18), + + THUNDER("§cThunder", 19), + LORD_JAWBUS("§cLord Jawbus", 19), + + DUMMY("Dummy", 20), + ARACHNE_SMALL("§cSmall Arachne", 21), + ARACHNE_BIG("§4Big Arachne", 21), + + // The Rift + LEECH_SUPREME("§cLeech Supreme", 22), + BACTE("§aBacte", 22), + + WINTER_REINDRAKE("Reindrake", 24),//TODO fix totally + + //TODO arachne + + //TODO corelone + //TODO bal + + + /** + * TODO dungeon mini bosses + * shadow assassin + * lost adventurer + * frozen adventurer + * king midas + * in blood room: bonzo, scarf, ?? + * f7 blood room giants + * + */ + + //TODO diana mythological creatures +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt new file mode 100644 index 000000000..1595b728a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt @@ -0,0 +1,12 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +import java.util.LinkedList + +class DamageCounter { + + var currentDamage = 0L + var currentHealing = 0L + var oldDamages = LinkedList() + var firstTick = 0L + +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt new file mode 100644 index 000000000..7735c5835 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt @@ -0,0 +1,862 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.events.BossHealthChangeEvent +import at.hannibal2.skyhanni.events.DamageIndicatorDetectedEvent +import at.hannibal2.skyhanni.events.DamageIndicatorFinalBossEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.features.dungeon.DungeonAPI +import at.hannibal2.skyhanni.features.slayer.blaze.HellionShield +import at.hannibal2.skyhanni.features.slayer.blaze.setHellionShield +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.EntityUtils.getNameTagWith +import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith +import at.hannibal2.skyhanni.utils.LocationUtils +import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth +import at.hannibal2.skyhanni.utils.LorenzUtils.between +import at.hannibal2.skyhanni.utils.LorenzUtils.editCopy +import at.hannibal2.skyhanni.utils.LorenzUtils.put +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.TimeUtils +import at.hannibal2.skyhanni.utils.getLorenzVec +import net.minecraft.client.Minecraft +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.entity.EntityLiving +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.item.EntityArmorStand +import net.minecraft.entity.monster.EntityEnderman +import net.minecraft.entity.monster.EntityMagmaCube +import net.minecraft.entity.monster.EntityZombie +import net.minecraft.entity.passive.EntityWolf +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.event.entity.EntityJoinWorldEvent +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.UUID +import kotlin.math.max + +class DamageIndicatorManager { + + private var mobFinder: MobFinder? = null + private val maxHealth = mutableMapOf() + private val config get() = SkyHanniMod.feature.combat.damageIndicator + + companion object { + private var data = mapOf() + private val damagePattern = "[✧✯]?(\\d+[⚔+✧❤♞☄✷ﬗ✯]*)".toPattern() + + fun isBoss(entity: EntityLivingBase) = data.values.any { it.entity == entity } + + fun isDamageSplash(entity: EntityLivingBase): Boolean { + if (entity.ticksExisted > 300 || entity !is EntityArmorStand) return false + if (!entity.hasCustomName()) return false + if (entity.isDead) return false + val name = entity.customNameTag.removeColor().replace(",", "") + + return damagePattern.matcher(name).matches() + } + + fun isBossSpawned(type: BossType) = data.entries.find { it.value.bossType == type } != null + + fun isBossSpawned(vararg types: BossType) = types.any { isBossSpawned(it) } + + fun getDistanceTo(vararg types: BossType): Double { + val playerLocation = LocationUtils.playerLocation() + return data.values.filter { it.bossType in types } + .map { it.entity.getLorenzVec().distance(playerLocation) } + .let { list -> + if (list.isEmpty()) Double.MAX_VALUE else list.minOf { it } + } + } + + fun getNearestDistanceTo(location: LorenzVec): Double { + return data.values + .map { it.entity.getLorenzVec() } + .minOfOrNull { it.distance(location) } ?: Double.MAX_VALUE + } + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + mobFinder = MobFinder() + data = emptyMap() + } + + @SubscribeEvent(receiveCanceled = true) + fun onChatMessage(event: LorenzChatEvent) { + mobFinder?.handleChat(event.message) + } + + @SubscribeEvent + fun onWorldRender(event: LorenzRenderWorldEvent) { + if (!isEnabled()) return + + GlStateManager.disableDepth() + GlStateManager.disableCull() + + val player = Minecraft.getMinecraft().thePlayer + + //TODO config to define between 100ms and 5 sec + val filter = data.filter { + val waitForRemoval = if (it.value.dead && !noDeathDisplay(it.value.bossType)) 4_000 else 100 + (System.currentTimeMillis() > it.value.timeLastTick + waitForRemoval) || (it.value.dead && noDeathDisplay(it.value.bossType)) + } + if (filter.isNotEmpty()) { + data = data.editCopy { + for (entry in filter) { + remove(entry.key) + } + } + } + + val sizeHealth: Double + val sizeNameAbove: Double + val sizeBossName: Double + val sizeFinalResults: Double + val smallestDistanceVew: Double + val thirdPersonView = Minecraft.getMinecraft().gameSettings.thirdPersonView + // 0 == normal + // 1 == f3 behind + // 2 == selfie + if (thirdPersonView == 1) { + sizeHealth = 2.8 + sizeNameAbove = 2.2 + sizeBossName = 2.4 + sizeFinalResults = 1.8 + + smallestDistanceVew = 10.0 + } else { + sizeHealth = 1.9 + sizeNameAbove = 1.8 + sizeBossName = 2.1 + sizeFinalResults = 1.4 + + smallestDistanceVew = 6.0 + } + + for (data in data.values) { + + //TODO test end stone protector in hole? - maybe change eye pos +// data.ignoreBlocks = +// data.bossType == BossType.END_ENDSTONE_PROTECTOR && Minecraft.getMinecraft().thePlayer.isSneaking + + if (!data.ignoreBlocks && !player.canEntityBeSeen(data.entity)) continue + if (data.bossType.bossTypeToggle !in config.bossesToShow) continue + + val entity = data.entity + + var healthText = data.healthText + val delayedStart = data.delayedStart + if (delayedStart != -1L && delayedStart > System.currentTimeMillis()) { + val delay = delayedStart - System.currentTimeMillis() + healthText = formatDelay(delay) + } + + val location = if (data.dead && data.deathLocation != null) { + data.deathLocation!! + } else { + val loc = entity.getLorenzVec() + if (data.dead) data.deathLocation = loc + loc + }.add(-0.5, 0.0, -0.5) + + + event.drawDynamicText(location, healthText, sizeHealth, smallestDistanceVew = smallestDistanceVew) + + if (data.nameAbove.isNotEmpty()) { + event.drawDynamicText( + location, + data.nameAbove, + sizeNameAbove, + -18f, + smallestDistanceVew = smallestDistanceVew + ) + } + + var bossName = when (config.bossName) { + 0 -> "" + 1 -> data.bossType.fullName + 2 -> data.bossType.shortName + else -> data.bossType.fullName + } + + if (data.namePrefix.isNotEmpty()) { + bossName = data.namePrefix + bossName + } + if (data.nameSuffix.isNotEmpty()) { + bossName += data.nameSuffix + } + event.drawDynamicText(location, bossName, sizeBossName, -9f, smallestDistanceVew = smallestDistanceVew) + + if (config.showDamageOverTime) { + var diff = 13f + val currentDamage = data.damageCounter.currentDamage + val currentHealing = data.damageCounter.currentHealing + if (currentDamage != 0L || currentHealing != 0L) { + val formatDamage = "§c" + NumberUtil.format(currentDamage) + val formatHealing = "§a+" + NumberUtil.format(currentHealing) + val finalResult = if (currentHealing == 0L) { + formatDamage + } else if (currentDamage == 0L) { + formatHealing + } else { + "$formatDamage §7/ $formatHealing" + } + event.drawDynamicText( + location, + finalResult, + sizeFinalResults, + diff, + smallestDistanceVew = smallestDistanceVew + ) + diff += 9f + } + for (damage in data.damageCounter.oldDamages) { + val formatDamage = "§c" + NumberUtil.format(damage.damage) + "/s" + val formatHealing = "§a+" + NumberUtil.format(damage.healing) + "/s" + val finalResult = if (damage.healing == 0L) { + formatDamage + } else if (damage.damage == 0L) { + formatHealing + } else { + "$formatDamage §7/ $formatHealing" + } + event.drawDynamicText( + location, + finalResult, + sizeFinalResults, + diff, + smallestDistanceVew = smallestDistanceVew + ) + diff += 9f + } + } + + } + GlStateManager.enableDepth() + GlStateManager.enableCull() + } + + private fun noDeathDisplay(bossType: BossType): Boolean { + return when (bossType) { + BossType.SLAYER_BLAZE_TYPHOEUS_1, + BossType.SLAYER_BLAZE_TYPHOEUS_2, + BossType.SLAYER_BLAZE_TYPHOEUS_3, + BossType.SLAYER_BLAZE_TYPHOEUS_4, + BossType.SLAYER_BLAZE_QUAZII_1, + BossType.SLAYER_BLAZE_QUAZII_2, + BossType.SLAYER_BLAZE_QUAZII_3, + BossType.SLAYER_BLAZE_QUAZII_4, + + //TODO f3/m3 4 guardians, f2/m2 4 boss room fighters + -> true + + else -> false + } + } + + private fun tickDamage(damageCounter: DamageCounter) { + val now = System.currentTimeMillis() + if (damageCounter.currentDamage != 0L || damageCounter.currentHealing != 0L) { + if (damageCounter.firstTick == 0L) { + damageCounter.firstTick = now + } + + if (now > damageCounter.firstTick + 1_000) { + damageCounter.oldDamages.add( + 0, OldDamage(now, damageCounter.currentDamage, damageCounter.currentHealing) + ) + damageCounter.firstTick = 0L + damageCounter.currentDamage = 0 + damageCounter.currentHealing = 0 + } + } + damageCounter.oldDamages.removeIf { now > it.time + 5_000 } + } + + private fun formatDelay(delay: Long): String { + val color = when { + delay < 1_000 -> LorenzColor.DARK_PURPLE + delay < 3_000 -> LorenzColor.LIGHT_PURPLE + + else -> LorenzColor.WHITE + } + val format = TimeUtils.formatDuration(delay, showMilliSeconds = true) + return color.getChatColor() + format + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + data = data.editCopy { + EntityUtils.getEntities().mapNotNull(::checkEntity).forEach { this put it } + } + } + + private fun checkEntity(entity: EntityLivingBase): Pair? { + try { + val entityData = grabData(entity) ?: return null + if (LorenzUtils.inDungeons) { + checkFinalBoss(entityData.finalDungeonBoss, entity.entityId) + } + + val health = entity.health.toLong() + val maxHealth: Long + val biggestHealth = getMaxHealthFor(entity) + if (biggestHealth == 0L) { + val currentMaxHealth = entity.baseMaxHealth.toLong() + maxHealth = max(currentMaxHealth, health) + setMaxHealth(entity, maxHealth) + } else { + maxHealth = biggestHealth + } + + entityData.namePrefix = "" + entityData.nameSuffix = "" + entityData.nameAbove = "" + val customHealthText = if (health == 0L) { + entityData.dead = true + if (entityData.bossType.showDeathTime && config.timeToKillSlayer) { + entityData.nameAbove = entityData.timeToKill + } + "§cDead" + } else { + getCustomHealth(entityData, health, entity, maxHealth) ?: return null + } + + if (data.containsKey(entity.uniqueID)) { + val lastHealth = data[entity.uniqueID]!!.lastHealth + checkDamage(entityData, health, lastHealth) + tickDamage(entityData.damageCounter) + + BossHealthChangeEvent(entityData, lastHealth, health, maxHealth).postAndCatch() + } + entityData.lastHealth = health + + if (customHealthText.isNotEmpty()) { + entityData.healthText = customHealthText + } else { + val color = NumberUtil.percentageColor(health, maxHealth) + entityData.healthText = color.getChatColor() + NumberUtil.format(health) + } + entityData.timeLastTick = System.currentTimeMillis() + return entity.uniqueID to entityData + } catch (e: Throwable) { + e.printStackTrace() + return null + } + } + + private fun getCustomHealth( + entityData: EntityData, + health: Long, + entity: EntityLivingBase, + maxHealth: Long, + ): String? { + + when (entityData.bossType) { + BossType.DUNGEON_F4_THORN -> { + val thorn = checkThorn(health, maxHealth) + if (thorn == null) { + val floor = DungeonAPI.dungeonFloor + LorenzUtils.error("problems with thorn detection! ($floor, $health/$maxHealth)") + } + return thorn + } + + BossType.SLAYER_ENDERMAN_1, + BossType.SLAYER_ENDERMAN_2, + BossType.SLAYER_ENDERMAN_3, + BossType.SLAYER_ENDERMAN_4, + -> return checkEnderSlayer(entity as EntityEnderman, entityData, health.toInt(), maxHealth.toInt()) + + BossType.SLAYER_BLOODFIEND_1, + BossType.SLAYER_BLOODFIEND_2, + BossType.SLAYER_BLOODFIEND_3, + BossType.SLAYER_BLOODFIEND_4, + -> return checkVampireSlayer(entity as EntityOtherPlayerMP, entityData, health.toInt(), maxHealth.toInt()) + + BossType.SLAYER_BLAZE_1, + BossType.SLAYER_BLAZE_2, + BossType.SLAYER_BLAZE_3, + BossType.SLAYER_BLAZE_4, + BossType.SLAYER_BLAZE_QUAZII_2, + BossType.SLAYER_BLAZE_QUAZII_3, + BossType.SLAYER_BLAZE_QUAZII_4, + BossType.SLAYER_BLAZE_TYPHOEUS_2, + BossType.SLAYER_BLAZE_TYPHOEUS_3, + BossType.SLAYER_BLAZE_TYPHOEUS_4, + -> return checkBlazeSlayer(entity as EntityLiving, entityData, health.toInt(), maxHealth.toInt()) + + BossType.NETHER_MAGMA_BOSS -> return checkMagmaCube( + entity as EntityMagmaCube, + entityData, + health.toInt(), + maxHealth.toInt() + ) + + BossType.SLAYER_ZOMBIE_5 -> { + if ((entity as EntityZombie).hasNameTagWith(3, "§fBoom!")) { + //TODO fix +// val ticksAlive = entity.ticksExisted % (20 * 5) +// val remainingTicks = (5 * 20).toLong() - ticksAlive +// val format = formatDelay(remainingTicks * 50) +// entityData.nameSuffix = " §f§lBOOM - $format" + entityData.nameSuffix = " §f§lBOOM!" + } + } + + BossType.SLAYER_WOLF_3, + BossType.SLAYER_WOLF_4, + -> { + if ((entity as EntityWolf).hasNameTagWith(2, "§bCalling the pups!")) { + return "Pups!" + } + } + + BossType.NETHER_BARBARIAN_DUKE, + -> { + val location = entity.getLorenzVec() + entityData.ignoreBlocks = location.y == 117.0 && location.distanceToPlayer() < 15 + } + + else -> return "" + } + return "" + } + + private fun checkBlazeSlayer(entity: EntityLiving, entityData: EntityData, health: Int, maxHealth: Int): String { + var found = false + for (shield in HellionShield.entries) { + val armorStand = entity.getNameTagWith(3, shield.name) + if (armorStand != null) { + val number = armorStand.name.split(" ♨")[1].substring(0, 1) + entity.setHellionShield(shield) + entityData.nameAbove = shield.formattedName + " $number" + found = true + break + } + } + if (!found) { + entity.setHellionShield(null) + } + + if (!SkyHanniMod.feature.slayer.blazes.phaseDisplay) return "" + + var calcHealth = health + val calcMaxHealth: Int + entityData.namePrefix = when (entityData.bossType) { + BossType.SLAYER_BLAZE_1, + BossType.SLAYER_BLAZE_2, + -> { + val step = maxHealth / 2 + calcMaxHealth = step + if (health > step) { + calcHealth -= step + "§c1/2 " + } else { + calcHealth = health + "§a2/2 " + } + } + + BossType.SLAYER_BLAZE_3, + BossType.SLAYER_BLAZE_4, + -> { + val step = maxHealth / 3 + calcMaxHealth = step + if (health > step * 2) { + calcHealth -= step * 2 + "§c1/3 " + } else if (health > step) { + calcHealth -= step + "§e2/3 " + } else { + calcHealth = health + "§a3/3 " + } + } + + else -> return "" + } + + return NumberUtil.percentageColor( + calcHealth.toLong(), calcMaxHealth.toLong() + ).getChatColor() + NumberUtil.format(calcHealth) + } + + private fun checkMagmaCube( + entity: EntityMagmaCube, + entityData: EntityData, + health: Int, + maxHealth: Int, + ): String? { + val slimeSize = entity.slimeSize + entityData.namePrefix = when (slimeSize) { + 24 -> "§c1/6" + 22 -> "§e2/6" + 20 -> "§e3/6" + 18 -> "§e4/6" + 16 -> "§e5/6" + else -> { + val color = NumberUtil.percentageColor(health.toLong(), 10_000_000) + entityData.namePrefix = "§a6/6" + return color.getChatColor() + NumberUtil.format(health) + } + } + " §f" + + //hide while in the middle +// val position = entity.getLorenzVec() + //TODO other logic or something +// entityData.healthLineHidden = position.x == -368.0 && position.z == -804.0 + + var calcHealth = -1 + for (line in ScoreboardData.sidebarLinesRaw) { + if (line.contains("▎")) { + val color: String + if (line.startsWith("§7")) { + color = "§7" + } else if (line.startsWith("§e")) { + color = "§e" + } else if (line.startsWith("§6") || line.startsWith("§a") || line.startsWith("§c")) { + calcHealth = 0 + break + } else { + LorenzUtils.error("unknown magma boss health sidebar format!") + break + } + + val text = line.replace("\uD83C\uDF81" + color, "") + val max = 25.0 + val length = text.split("§e", "§7")[1].length + val missing = (health.toDouble() / max) * length + calcHealth = (health - missing).toInt() + } + } + if (calcHealth == -1) return null + + val color = NumberUtil.percentageColor(calcHealth.toLong(), maxHealth.toLong()) + return color.getChatColor() + NumberUtil.format(calcHealth) + } + + private fun checkEnderSlayer( + entity: EntityEnderman, + entityData: EntityData, + health: Int, + maxHealth: Int, + ): String? { + var calcHealth = health + val calcMaxHealth: Int + entityData.namePrefix = when (entityData.bossType) { + BossType.SLAYER_ENDERMAN_1, + BossType.SLAYER_ENDERMAN_2, + BossType.SLAYER_ENDERMAN_3, + -> { + val step = maxHealth / 3 + calcMaxHealth = step + if (health > step * 2) { + calcHealth -= step * 2 + "§c1/3 " + } else if (health > step) { + calcHealth -= step + "§e2/3 " + } else { + calcHealth = health + "§a3/3 " + } + } + + BossType.SLAYER_ENDERMAN_4 -> { + val step = maxHealth / 6 + calcMaxHealth = step + if (health > step * 5) { + calcHealth -= step * 5 + "§c1/6 " + } else if (health > step * 4) { + calcHealth -= step * 4 + "§e2/6 " + } else if (health > step * 3) { + calcHealth -= step * 3 + "§e3/6 " + } else if (health > step * 2) { + calcHealth -= step * 2 + "§e4/6 " + } else if (health > step) { + calcHealth -= step + "§e5/6 " + } else { + calcHealth = health + "§a6/6 " + } + } + + else -> return null + } + var result = NumberUtil.percentageColor( + calcHealth.toLong(), calcMaxHealth.toLong() + ).getChatColor() + NumberUtil.format(calcHealth) + + if (!SkyHanniMod.feature.slayer.endermen.phaseDisplay) { + result = "" + entityData.namePrefix = "" + } + + + //Hit phase + val armorStandHits = entity.getNameTagWith(3, " Hit") + if (armorStandHits != null) { + val maxHits = when (entityData.bossType) { + BossType.SLAYER_ENDERMAN_1 -> 15 + BossType.SLAYER_ENDERMAN_2 -> 30 + BossType.SLAYER_ENDERMAN_3 -> 60 + BossType.SLAYER_ENDERMAN_4 -> 100 + else -> 100 + } + val name = armorStandHits.name.removeColor() + + // TODO replace this super ugly workaround with regex + val text = name.between("Seraph ", " Hit") + val hits = try { + text.toInt() + } catch (e: NumberFormatException) { + text.substring(2).toInt() + } + + return NumberUtil.percentageColor(hits.toLong(), maxHits.toLong()).getChatColor() + "$hits Hits" + } + + //Laser phase + if (config.enderSlayer.laserPhaseTimer && entity.ridingEntity != null) { + val ticksAlive = entity.ridingEntity.ticksExisted.toLong() + //TODO more tests, more exact values, better logic? idk make this working perfectly pls + //val remainingTicks = 8 * 20 - ticksAlive + val remainingTicks = (7.4 * 20).toLong() - ticksAlive + + if (config.enderSlayer.showHealthDuringLaser) { + entityData.nameSuffix = " §f" + formatDelay(remainingTicks * 50) + } else { + return formatDelay(remainingTicks * 50) + } + } + + return result + } + + private fun checkVampireSlayer( + entity: EntityOtherPlayerMP, + entityData: EntityData, + health: Int, + maxHealth: Int, + ): String { + val config = config.vampireSlayer + + if (config.percentage) { + val percentage = LorenzUtils.formatPercentage(health.toDouble() / maxHealth) + entityData.nameSuffix = " §e$percentage" + } + + if (config.maniaCircles) { + entity.ridingEntity?.let { + val existed = it.ticksExisted + if (existed > 40) { + val end = (20 * 26) - existed + val time = end.toDouble() / 20 + entityData.nameAbove = "Mania Circles: §b${time.round(1)}s" + return "" + } + } + } + + if (config.hpTillSteak) { + val rest = maxHealth * 0.2 + val showHealth = health - rest + if (showHealth < 300) { + entityData.nameAbove = if (showHealth > 0) { + "§cHP till Steak: ${showHealth.addSeparators()}" + } else "§cSteak!" + } + } + + return "" + } + + private fun checkThorn(realHealth: Long, realMaxHealth: Long): String? { + val maxHealth: Int + val health = if (DungeonAPI.isOneOf("F4")) { + maxHealth = 4 + + if (realMaxHealth == 300_000L) { + // no derpy + when { + realHealth == 1L -> 0 + realHealth <= 66_000 -> 1 + realHealth <= 144_000 -> 2 + realHealth <= 222_000 -> 3 + realHealth <= 300_000 -> 4 + + else -> return null + } + } else { + // derpy + when { + realHealth == 1L -> 0 + realHealth <= 132_000 -> 1 + realHealth <= 288_000 -> 2 + realHealth <= 444_000 -> 3 + realHealth <= 600_000 -> 4 + + else -> return null + } + } + } else if (DungeonAPI.isOneOf("M4")) { + maxHealth = 6 + + if (realMaxHealth == 900_000L) { + // no derpy + when { + realHealth == 1L -> 0 + realHealth <= 135_000 -> 1 + realHealth <= 288_000 -> 2 + realHealth <= 441_000 -> 3 + realHealth <= 594_000 -> 4 + realHealth <= 747_000 -> 5 + realHealth <= 900_000L -> 6 + + else -> return null + } + } else { + // derpy + when { + realHealth == 1L -> 0 + realHealth <= 270_000 -> 1 + realHealth <= 576_000 -> 2 + realHealth <= 882_000 -> 3 + realHealth <= 1_188_000 -> 4 + realHealth <= 1_494_000 -> 5 + realHealth <= 1_800_000 -> 6 + + else -> return null + } + } + } else { + LorenzUtils.error("Invalid/impossible thorn floor!") + return null + } + val color = NumberUtil.percentageColor(health.toLong(), maxHealth.toLong()) + return color.getChatColor() + health + "/" + maxHealth + } + + private fun checkDamage(entityData: EntityData, health: Long, lastHealth: Long) { + val damage = lastHealth - health + val healing = health - lastHealth + if (damage > 0 && entityData.bossType != BossType.DUMMY) { + val damageCounter = entityData.damageCounter + damageCounter.currentDamage += damage + } + if (healing > 0) { + //Hide auto heal every 10 ticks (with rounding errors) + if ((healing == 15_000L || healing == 15_001L) && entityData.bossType == BossType.SLAYER_ZOMBIE_5) return + + val damageCounter = entityData.damageCounter + damageCounter.currentHealing += healing + } + } + + private fun grabData(entity: EntityLivingBase): EntityData? { + if (data.contains(entity.uniqueID)) return data[entity.uniqueID] + + + val entityResult = mobFinder?.tryAdd(entity) ?: return null + + val entityData = EntityData( + entity, + entityResult.ignoreBlocks, + entityResult.delayedStart, + entityResult.finalDungeonBoss, + entityResult.bossType, + foundTime = System.currentTimeMillis() + ) + DamageIndicatorDetectedEvent(entityData).postAndCatch() + return entityData + } + + private fun checkFinalBoss(finalBoss: Boolean, id: Int) { + if (finalBoss) { + DamageIndicatorFinalBossEvent(id).postAndCatch() + } + } + + private fun setMaxHealth(entity: EntityLivingBase, currentMaxHealth: Long) { + maxHealth[entity.uniqueID!!] = currentMaxHealth + } + + private fun getMaxHealthFor(entity: EntityLivingBase): Long { + return maxHealth.getOrDefault(entity.uniqueID!!, 0L) + } + + @SubscribeEvent + fun onEntityJoin(event: EntityJoinWorldEvent) { + mobFinder?.handleNewEntity(event.entity) + } + + private val dummyDamageCache = mutableListOf() + + @SubscribeEvent(priority = EventPriority.HIGH) + fun onRenderLiving(event: RenderLivingEvent.Specials.Pre) { + val entity = event.entity + + val entityData = data.values.find { + val distance = it.entity.getLorenzVec().distance(entity.getLorenzVec()) + distance < 4.5 + } + + if (isDamageSplash(entity)) { + val name = entity.customNameTag.removeColor().replace(",", "") + + if (entityData != null) { + if (config.hideDamageSplash) { + event.isCanceled = true + } + if (entityData.bossType == BossType.DUMMY) { + val uuid = entity.uniqueID + if (dummyDamageCache.contains(uuid)) return + dummyDamageCache.add(uuid) + val dmg = name.toCharArray().filter { Character.isDigit(it) }.joinToString("").toLong() + entityData.damageCounter.currentDamage += dmg + } + } + } else { + if (entityData != null && isEnabled() && config.hideVanillaNametag) { + val name = entity.name + if (name.contains("Plaesmaflux")) return + if (name.contains("Overflux")) return + if (name.contains("Mana Flux")) return + if (name.contains("Radiant")) return + event.isCanceled = true + } + } + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "damageIndicator", "combat.damageIndicator") + event.move(3, "slayer.endermanPhaseDisplay", "slayer.endermen.phaseDisplay") + event.move(3, "slayer.blazePhaseDisplay", "slayer.blazes.phaseDisplay") + } + + fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityData.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityData.kt new file mode 100644 index 000000000..970bdd0dd --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityData.kt @@ -0,0 +1,30 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.TimeUnit +import at.hannibal2.skyhanni.utils.TimeUtils +import net.minecraft.entity.EntityLivingBase + +class EntityData( + val entity: EntityLivingBase, + var ignoreBlocks: Boolean, + var delayedStart: Long, + val finalDungeonBoss: Boolean, + val bossType: BossType, + val damageCounter: DamageCounter = DamageCounter(), + val foundTime: Long, + + var lastHealth: Long = 0L, + var healthText: String = "", + var timeLastTick: Long = 0, + var namePrefix: String = "", + var nameSuffix: String = "", + var nameAbove: String = "", + var dead: Boolean = false, + var deathLocation: LorenzVec? = null, +) { + val timeToKill by lazy { + val duration = System.currentTimeMillis() - foundTime + "§e" + TimeUtils.formatDuration(duration, TimeUnit.SECOND, showMilliSeconds = true) + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityResult.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityResult.kt new file mode 100644 index 000000000..4f6920293 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityResult.kt @@ -0,0 +1,8 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +class EntityResult( + val delayedStart: Long = -1L, + val ignoreBlocks: Boolean = false, + val finalDungeonBoss: Boolean = false, + val bossType: BossType = BossType.GENERIC_DUNGEON_BOSS, +) \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/MobFinder.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/MobFinder.kt new file mode 100644 index 000000000..9476eafb3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/MobFinder.kt @@ -0,0 +1,513 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.features.dungeon.DungeonAPI +import at.hannibal2.skyhanni.features.dungeon.DungeonLividFinder +import at.hannibal2.skyhanni.features.rift.RiftAPI +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.EntityUtils.hasBossHealth +import at.hannibal2.skyhanni.utils.EntityUtils.hasMaxHealth +import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith +import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth +import at.hannibal2.skyhanni.utils.LorenzUtils.derpy +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.StringUtils.matchRegex +import at.hannibal2.skyhanni.utils.getLorenzVec +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityLiving +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.boss.EntityDragon +import net.minecraft.entity.boss.EntityWither +import net.minecraft.entity.monster.EntityBlaze +import net.minecraft.entity.monster.EntityEnderman +import net.minecraft.entity.monster.EntityGhast +import net.minecraft.entity.monster.EntityGiantZombie +import net.minecraft.entity.monster.EntityGuardian +import net.minecraft.entity.monster.EntityIronGolem +import net.minecraft.entity.monster.EntityMagmaCube +import net.minecraft.entity.monster.EntityPigZombie +import net.minecraft.entity.monster.EntitySkeleton +import net.minecraft.entity.monster.EntitySlime +import net.minecraft.entity.monster.EntitySpider +import net.minecraft.entity.monster.EntityZombie +import net.minecraft.entity.passive.EntityHorse +import net.minecraft.entity.passive.EntityWolf +import java.util.UUID + +class MobFinder { + + //F1 + private var floor1bonzo1 = false + private var floor1bonzo1SpawnTime = 0L + private var floor1bonzo2 = false + private var floor1bonzo2SpawnTime = 0L + + //F2 + private var floor2summons1 = false + private var floor2summons1SpawnTime = 0L + private var floor2summonsDiedOnce = mutableListOf() + private var floor2secondPhase = false + private var floor2secondPhaseSpawnTime = 0L + + //F3 + private var floor3GuardianShield = false + private var floor3GuardianShieldSpawnTime = 0L + private var guardians = mutableListOf() + private var floor3Professor = false + private var floor3ProfessorSpawnTime = 0L + private var floor3ProfessorGuardianPrepare = false + private var floor3ProfessorGuardianPrepareSpawnTime = 0L + private var floor3ProfessorGuardian = false + private var floor3ProfessorGuardianEntity: EntityGuardian? = null + + //F5 + private var floor5lividEntity: EntityOtherPlayerMP? = null + private var floor5lividEntitySpawnTime = 0L + + //F6 + private var floor6Giants = false + private var floor6GiantsSpawnTime = 0L + private var floor6GiantsSeparateDelay = mutableMapOf() + private var floor6Sadan = false + private var floor6SadanSpawnTime = 0L + + internal fun tryAdd(entity: EntityLivingBase): EntityResult? { + if (LorenzUtils.inDungeons) { + if (DungeonAPI.isOneOf("F1", "M1")) { + if (floor1bonzo1 && entity is EntityOtherPlayerMP && entity.name == "Bonzo ") { + return EntityResult(floor1bonzo1SpawnTime) + } + if (floor1bonzo2 && entity is EntityOtherPlayerMP && entity.name == "Bonzo ") { + return EntityResult(floor1bonzo2SpawnTime, finalDungeonBoss = true) + } + } + + if (DungeonAPI.isOneOf("F2", "M2")) { + if (entity.name == "Summon " && entity is EntityOtherPlayerMP) { + if (floor2summons1 && !floor2summonsDiedOnce.contains(entity)) { + if (entity.health.toInt() != 0) { + return EntityResult(floor2summons1SpawnTime) + } else { + floor2summonsDiedOnce.add(entity) + } + } + if (floor2secondPhase) { + return EntityResult(floor2secondPhaseSpawnTime) + } + } + + if (floor2secondPhase && entity is EntityOtherPlayerMP) { + //TODO only show scarf after (all/at least x) summons are dead? + val result = entity.name == "Scarf " + if (result) { + return EntityResult(floor2secondPhaseSpawnTime, finalDungeonBoss = true) + } + } + } + + if (DungeonAPI.isOneOf("F3", "M3")) { + if (entity is EntityGuardian && floor3GuardianShield) { + if (guardians.size == 4) { + var totalHealth = 0 + for (guardian in guardians) { + totalHealth += guardian.health.toInt() + } + if (totalHealth == 0) { + floor3GuardianShield = false + guardians.clear() + } + } else { + findGuardians() + } + if (guardians.contains(entity)) { + return EntityResult(floor3GuardianShieldSpawnTime, true) + } + } + + if (floor3Professor && entity is EntityOtherPlayerMP && entity.name == "The Professor") { + return EntityResult( + floor3ProfessorSpawnTime, + floor3ProfessorSpawnTime + 1_000 > System.currentTimeMillis() + ) + } + if (floor3ProfessorGuardianPrepare && entity is EntityOtherPlayerMP && entity.name == "The Professor") { + return EntityResult(floor3ProfessorGuardianPrepareSpawnTime, true) + } + + if (entity is EntityGuardian && floor3ProfessorGuardian && entity == floor3ProfessorGuardianEntity) { + return EntityResult(finalDungeonBoss = true) + } + } + + if (DungeonAPI.isOneOf("F4", "M4") && entity is EntityGhast) { + return EntityResult( + bossType = BossType.DUNGEON_F4_THORN, + ignoreBlocks = true, + finalDungeonBoss = true + ) + } + + if (DungeonAPI.isOneOf("F5", "M5") && entity is EntityOtherPlayerMP && entity == DungeonLividFinder.livid) { + return EntityResult( + bossType = BossType.DUNGEON_F5, + ignoreBlocks = true, + finalDungeonBoss = true + ) + } + + if (DungeonAPI.isOneOf("F6", "M6") && entity is EntityGiantZombie && !entity.isInvisible) { + if (floor6Giants && entity.posY > 68) { + val extraDelay = checkExtraF6GiantsDelay(entity) + return EntityResult( + floor6GiantsSpawnTime + extraDelay, + floor6GiantsSpawnTime + extraDelay + 1_000 > System.currentTimeMillis() + ) + } + + if (floor6Sadan) { + return EntityResult(floor6SadanSpawnTime, finalDungeonBoss = true) + } + } + } else if (RiftAPI.inRift()) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Leech Supreme") { + return EntityResult(bossType = BossType.LEECH_SUPREME) + } + + if (entity.name == "Bloodfiend ") { + when { + entity.hasMaxHealth(625, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_1) + entity.hasMaxHealth(1_100, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_2) + entity.hasMaxHealth(1_800, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_3) + entity.hasMaxHealth(2_400, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_4) + entity.hasMaxHealth(3_000, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_5) + } + } + } + if (entity is EntitySlime && entity.baseMaxHealth == 1_000) { + return EntityResult(bossType = BossType.BACTE) + } + } else { + if (entity is EntityBlaze && entity.name != "Dinnerbone" && entity.hasNameTagWith( + 2, + "§e﴾ §8[§7Lv200§8] §l§8§lAshfang§r " + ) && entity.hasMaxHealth(50_000_000, true) + ) { + return EntityResult(bossType = BossType.NETHER_ASHFANG) + } + if (entity is EntitySkeleton && entity.hasNameTagWith(5, "§e﴾ §8[§7Lv200§8] §l§8§lBladesoul§r ")) { + return EntityResult(bossType = BossType.NETHER_BLADESOUL) + } + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Mage Outlaw") { + return EntityResult(bossType = BossType.NETHER_MAGE_OUTLAW) + } + if (entity.name == "DukeBarb " && entity.getLorenzVec().distanceToPlayer() < 30) { + return EntityResult(bossType = BossType.NETHER_BARBARIAN_DUKE) + } + } + if (entity is EntityWither && entity.hasNameTagWith(4, "§8[§7Lv100§8] §c§5Vanquisher§r ")) { + return EntityResult(bossType = BossType.NETHER_VANQUISHER) + } + if (entity is EntityEnderman && entity.hasNameTagWith(3, "§c☠ §bVoidgloom Seraph ")) { + when { + entity.hasMaxHealth(300_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_1) + entity.hasMaxHealth(12_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_2) + entity.hasMaxHealth(50_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_3) + entity.hasMaxHealth(210_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_4) + } + } + if (entity is EntityDragon) { + //TODO testing and use sidebar data + if (IslandType.THE_END.isInIsland()) { + return EntityResult(bossType = BossType.END_ENDER_DRAGON) + } else if (IslandType.WINTER.isInIsland()) { + return EntityResult(bossType = BossType.WINTER_REINDRAKE) + } + } + if (entity is EntityIronGolem && entity.hasNameTagWith(3, "§e﴾ §8[§7Lv100§8] §lEndstone Protector§r ")) { + return EntityResult(bossType = BossType.END_ENDSTONE_PROTECTOR) + } + if (entity is EntityZombie) { + if (entity.hasNameTagWith(2, "§c☠ §bRevenant Horror")) { + when { + entity.hasMaxHealth(500, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_1) + entity.hasMaxHealth(20_000, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_2) + entity.hasMaxHealth(400_000, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_3) + entity.hasMaxHealth(1_500_000, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_4) + } + } + if (entity.hasNameTagWith(2, "§c☠ §fAtoned Horror ") && entity.hasMaxHealth(10_000_000, true)) { + return EntityResult(bossType = BossType.SLAYER_ZOMBIE_5) + } + } + if (entity is EntityLiving && entity.hasNameTagWith(2, "Dummy §a10M§c❤")) { + return EntityResult(bossType = BossType.DUMMY) + } + if (entity is EntityMagmaCube && entity.hasNameTagWith( + 15, + "§e﴾ §8[§7Lv500§8] §l§4§lMagma Boss§r " + ) && entity.hasMaxHealth(200_000_000, true) + ) { + return EntityResult(bossType = BossType.NETHER_MAGMA_BOSS, ignoreBlocks = true) + } + if (entity is EntityHorse && entity.hasNameTagWith( + 15, + "§8[§7Lv100§8] §c§6Headless Horseman§r " + ) && entity.hasMaxHealth(3_000_000, true) + ) { + return EntityResult(bossType = BossType.HUB_HEADLESS_HORSEMAN) + } + if (entity is EntityBlaze && entity.hasNameTagWith(2, "§c☠ §bInferno Demonlord ")) { + when { + entity.hasBossHealth(2_500_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_1) + entity.hasBossHealth(10_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_2) + entity.hasBossHealth(45_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_3) + entity.hasBossHealth(150_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_4) + } + } + if (entity is EntityPigZombie && entity.hasNameTagWith(2, "§c☠ §6ⓉⓎⓅⒽⓄⒺⓊⓈ ")) { + when { + entity.hasBossHealth(10_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_4) + entity.hasBossHealth(5_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_3) + entity.hasBossHealth(1_750_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_2) + entity.hasBossHealth(500_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_1) + } + } + if (entity is EntitySkeleton && entity.hasNameTagWith(2, "§c☠ §3ⓆⓊⒶⓏⒾⒾ ")) { + when { + entity.hasBossHealth(10_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_4) + entity.hasBossHealth(5_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_3) + entity.hasBossHealth(1_750_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_2) + entity.hasBossHealth(500_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_1) + } + } + + if (entity is EntitySpider) { + if (entity.hasNameTagWith(1, "§5☠ §4Tarantula Broodfather ")) { + when { + entity.hasMaxHealth(740, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_1) + entity.hasMaxHealth(30_000, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_2) + entity.hasMaxHealth(900_000, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_3) + entity.hasMaxHealth(2_400_000, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_4) + } + } + checkArachne(entity)?.let { return it } + } + if (entity is EntityWolf && entity.hasNameTagWith(1, "§c☠ §fSven Packmaster ")) { + when { + entity.hasMaxHealth(2_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_1) + entity.hasMaxHealth(40_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_2) + entity.hasMaxHealth(750_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_3) + entity.hasMaxHealth(2_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_4) + } + } + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Minos Inquisitor") return EntityResult(bossType = BossType.MINOS_INQUISITOR) + if (entity.name == "Minos Champion") return EntityResult(bossType = BossType.MINOS_CHAMPION) + if (entity.name == "Minotaur ") return EntityResult(bossType = BossType.MINOTAUR) + } + if (entity is EntityIronGolem && entity.hasMaxHealth(1_500_000)) { + return EntityResult(bossType = BossType.GAIA_CONSTURUCT) + } + if (entity is EntityGuardian && entity.hasMaxHealth(35_000_000)) { + return EntityResult(bossType = BossType.THUNDER) + } + + if (entity is EntityIronGolem && entity.hasMaxHealth(100_000_000)) { + return EntityResult(bossType = BossType.LORD_JAWBUS) + } + } + + return null + } + + private fun checkArachne(entity: EntitySpider): EntityResult? { + if (entity.hasNameTagWith(1, "[§7Lv300§8] §cArachne") || + entity.hasNameTagWith(1, "[§7Lv300§8] §lArachne") + ) { + val maxHealth = entity.baseMaxHealth + // Ignore the minis + if (maxHealth == 12 || maxHealth.derpy() == 4000) return null + return EntityResult(bossType = BossType.ARACHNE_SMALL) + } + if (entity.hasNameTagWith(1, "[§7Lv500§8] §cArachne") || + entity.hasNameTagWith(1, "[§7Lv500§8] §lArachne") + ) { + val maxHealth = entity.baseMaxHealth + if (maxHealth == 12 || maxHealth.derpy() == 20_000) return null + return EntityResult(bossType = BossType.ARACHNE_BIG) + } + + return null + } + + private fun checkExtraF6GiantsDelay(entity: EntityGiantZombie): Long { + val uuid = entity.uniqueID + + if (floor6GiantsSeparateDelay.contains(uuid)) { + return floor6GiantsSeparateDelay[uuid]!! + } + + val middle = LorenzVec(-8, 0, 56) + + val loc = entity.getLorenzVec() + + var pos = 0 + + //first + if (loc.x > middle.x && loc.z > middle.z) { + pos = 2 + } + + //second + if (loc.x > middle.x && loc.z < middle.z) { + pos = 3 + } + + //third + if (loc.x < middle.x && loc.z < middle.z) { + pos = 0 + } + + //fourth + if (loc.x < middle.x && loc.z > middle.z) { + pos = 1 + } + + val extraDelay = 900L * pos + floor6GiantsSeparateDelay[uuid] = extraDelay + + return extraDelay + } + + fun handleChat(message: String) { + if (LorenzUtils.inDungeons) { + when (message) { + //F1 + "§c[BOSS] Bonzo§r§f: Gratz for making it this far, but I’m basically unbeatable." -> { + floor1bonzo1 = true + floor1bonzo1SpawnTime = System.currentTimeMillis() + 11_250 + } + + "§c[BOSS] Bonzo§r§f: Oh noes, you got me.. what ever will I do?!" -> { + floor1bonzo1 = false + } + + "§c[BOSS] Bonzo§r§f: Oh I'm dead!" -> { + floor1bonzo2 = true + floor1bonzo2SpawnTime = System.currentTimeMillis() + 4_200 + } + + "§c[BOSS] Bonzo§r§f: Alright, maybe I'm just weak after all.." -> { + floor1bonzo2 = false + } + + //F2 + "§c[BOSS] Scarf§r§f: ARISE, MY CREATIONS!" -> { + floor2summons1 = true + floor2summons1SpawnTime = System.currentTimeMillis() + 3_500 + } + + "§c[BOSS] Scarf§r§f: Those toys are not strong enough I see." -> { + floor2summons1 = false + } + + "§c[BOSS] Scarf§r§f: Don't get too excited though." -> { + floor2secondPhase = true + floor2secondPhaseSpawnTime = System.currentTimeMillis() + 6_300 + } + + "§c[BOSS] Scarf§r§f: Whatever..." -> { + floor2secondPhase = false + } + + //F3 + "§c[BOSS] The Professor§r§f: I was burdened with terrible news recently..." -> { + floor3GuardianShield = true + floor3GuardianShieldSpawnTime = System.currentTimeMillis() + 16_400 + } + + "§c[BOSS] The Professor§r§f: Even if you took my barrier down, I can still fight." -> { + floor3GuardianShield = false + } + + "§c[BOSS] The Professor§r§f: Oh? You found my Guardians' one weakness?" -> { + floor3Professor = true + floor3ProfessorSpawnTime = System.currentTimeMillis() + 10_300 + } + + "§c[BOSS] The Professor§r§f: I see. You have forced me to use my ultimate technique." -> { + floor3Professor = false + + floor3ProfessorGuardianPrepare = true + floor3ProfessorGuardianPrepareSpawnTime = System.currentTimeMillis() + 10_500 + } + + "§c[BOSS] The Professor§r§f: The process is irreversible, but I'll be stronger than a Wither now!" -> { + floor3ProfessorGuardian = true + } + + "§c[BOSS] The Professor§r§f: What?! My Guardian power is unbeatable!" -> { + floor3ProfessorGuardian = false + } + + + //F5 + "§c[BOSS] Livid§r§f: This Orb you see, is Thorn, or what is left of him." -> { + floor5lividEntity = DungeonLividFinder.livid + floor5lividEntitySpawnTime = System.currentTimeMillis() + 13_000 + } + + //F6 + "§c[BOSS] Sadan§r§f: ENOUGH!" -> { + floor6Giants = true + floor6GiantsSpawnTime = System.currentTimeMillis() + 7_400 + } + + "§c[BOSS] Sadan§r§f: You did it. I understand now, you have earned my respect." -> { + floor6Giants = false + floor6Sadan = true + floor6SadanSpawnTime = System.currentTimeMillis() + 32_500 + } + + "§c[BOSS] Sadan§r§f: NOOOOOOOOO!!! THIS IS IMPOSSIBLE!!" -> { + floor6Sadan = false + } + } + + if (message.matchRegex("§c\\[BOSS] (.*) Livid§r§f: Impossible! How did you figure out which one I was\\?!")) { + floor5lividEntity = null + } + } + } + + fun handleNewEntity(entity: Entity) { + if (LorenzUtils.inDungeons && floor3ProfessorGuardian && entity is EntityGuardian && floor3ProfessorGuardianEntity == null) { + + floor3ProfessorGuardianEntity = entity + floor3ProfessorGuardianPrepare = false + + } + } + + private fun findGuardians() { + guardians.clear() + + for (entity in EntityUtils.getEntities()) { + //F3 + if (entity.hasMaxHealth(1_000_000, true) || entity.hasMaxHealth(1_200_000, true)) { + guardians.add(entity) + } + + //M3 + if (entity.hasMaxHealth(120_000_000, true) || entity.hasMaxHealth(240_000_000, true)) { + guardians.add(entity) + } + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/OldDamage.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/OldDamage.kt new file mode 100644 index 000000000..77ca2c6c9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/OldDamage.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.features.combat.damageindicator + +class OldDamage(val time: Long, val damage: Long, val healing: Long) \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNode.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNode.kt new file mode 100644 index 000000000..bdfb97833 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNode.kt @@ -0,0 +1,33 @@ +package at.hannibal2.skyhanni.features.combat.endernodetracker + +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName + +enum class EnderNode( + val internalName: NEUInternalName, + val displayName: String, +) { + + ENCHANTED_ENDSTONE("ENCHANTED_ENDSTONE".asInternalName(), "§aEnchanted End Stone"), + ENCHANTED_OBSIDIAN("ENCHANTED_OBSIDIAN".asInternalName(), "§aEnchanted Obsidian"), + ENCHANTED_ENDER_PEARL("ENCHANTED_ENDER_PEARL".asInternalName(), "§aEnchanted Ender Pearl"), + GRAND_EXP_BOTTLE("GRAND_EXP_BOTTLE".asInternalName(), "§aGrand Experience Bottle"), + TITANIC_EXP_BOTTLE("TITANIC_EXP_BOTTLE".asInternalName(), "§9Titanic Experience Bottle"), + END_STONE_SHULKER("END_STONE_SHULKER".asInternalName(), "§9End Stone Shulker"), + ENDSTONE_GEODE("ENDSTONE_GEODE".asInternalName(), "§9End Stone Geode"), + MAGIC_RUNE("MAGIC_RUNE;1".asInternalName(), "§d◆ Magical Rune I"), + ENDER_GAUNTLET("ENDER_GAUNTLET".asInternalName(), "§5Ender Gauntlet"), + MITE_GEL("MITE_GEL".asInternalName(), "§5Mite Gel"), + SHRIMP_THE_FISH("SHRIMP_THE_FISH".asInternalName(), "§cShrimp the Fish"), + + END_HELMET("END_HELMET".asInternalName(), "§5Ender Helmet"), + END_CHESTPLATE("END_CHESTPLATE".asInternalName(), "§5Ender Chestplate"), + END_LEGGINGS("END_LEGGINGS".asInternalName(), "§5Ender Leggings"), + END_BOOTS("END_BOOTS".asInternalName(), "§5Ender Boots"), + ENDER_NECKLACE("ENDER_NECKLACE".asInternalName(), "§5Ender Necklace"), + COMMON_ENDERMAN_PET("ENDERMAN;0".asInternalName(), "§fEnderman"), + UNCOMMON_ENDERMAN_PET("ENDERMAN;1".asInternalName(), "§aEnderman"), + RARE_ENDERMAN_PET("ENDERMAN;2".asInternalName(), "§9Enderman"), + EPIC_ENDERMAN_PET("ENDERMAN;3".asInternalName(), "§5Enderman"), + LEGENDARY_ENDERMAN_PET("ENDERMAN;4".asInternalName(), "§6Enderman") +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNodeTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNodeTracker.kt new file mode 100644 index 000000000..7fb000766 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNodeTracker.kt @@ -0,0 +1,239 @@ +package at.hannibal2.skyhanni.features.combat.endernodetracker + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.config.Storage +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.IslandChangeEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.OwnInventoryItemUpdateEvent +import at.hannibal2.skyhanni.events.SackChangeEvent +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.afterChange +import at.hannibal2.skyhanni.utils.LorenzUtils.editCopy +import at.hannibal2.skyhanni.utils.NEUItems.getNpcPriceOrNull +import at.hannibal2.skyhanni.utils.NEUItems.getPriceOrNull +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.format +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import io.github.moulberry.notenoughupdates.util.MinecraftExecutor +import net.minecraft.client.Minecraft +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class EnderNodeTracker { + private val config get() = SkyHanniMod.feature.combat.enderNodeTracker + private val storage get() = ProfileStorageData.profileSpecific?.enderNodeTracker + + private var totalEnderArmor = 0 + private var miteGelInInventory = 0 + private var display = emptyList>() + private var lootProfit = mapOf() + + private val enderNodeRegex = Regex("""ENDER NODE!.+You found (\d+x )?§r(.+)§r§f!""") + private val endermanRegex = Regex("""(RARE|PET) DROP! §r(.+) §r§b\(""") + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!config.enabled) return + if (!ProfileStorageData.loaded) return + if (!isInTheEnd()) return + + // don't call removeColor because we want to distinguish enderman pet rarity + val message = event.message.trim() + var item: String? = null + var amount = 1 + val storage = storage ?: return + + // check whether the loot is from an ender node or an enderman + enderNodeRegex.find(message)?.let { + storage.totalNodesMined++ + amount = it.groups[1]?.value?.substringBefore("x")?.toIntOrNull() ?: 1 + item = it.groups[2]?.value + } ?: endermanRegex.find(message)?.let { + amount = 1 + item = it.groups[2]?.value + } + + when { + item == null -> return + isEnderArmor(item) -> totalEnderArmor++ + item == "§cEndermite Nest" -> { + storage.totalEndermiteNests++ + } + } + + // increment the count of the specific item found + EnderNode.entries.find { it.displayName == item }?.let { + val old = storage.lootCount[it] ?: 0 + storage.lootCount = storage.lootCount.editCopy { + this[it] = old + amount + } + } + update() + } + + @SubscribeEvent + fun onIslandChange(event: IslandChangeEvent) { + if (!config.enabled) return + if (event.newIsland != IslandType.THE_END) return + miteGelInInventory = Minecraft.getMinecraft().thePlayer.inventory.mainInventory + .filter { it?.getInternalNameOrNull() == EnderNode.MITE_GEL.internalName } + .sumOf { it.stackSize } + } + + @SubscribeEvent + fun onSackChange(event: SackChangeEvent) { + if (!config.enabled) return + if (!ProfileStorageData.loaded) return + if (!isInTheEnd()) return + val storage = storage ?: return + + val change = event.sackChanges + .firstOrNull { it.internalName == EnderNode.MITE_GEL.internalName && it.delta > 0 } + ?: return + val old = storage.lootCount[EnderNode.MITE_GEL] ?: 0 + storage.lootCount = storage.lootCount.editCopy { + this[EnderNode.MITE_GEL] = old + change.delta + } + update() + } + + @SubscribeEvent + fun onInventoryUpdate(event: OwnInventoryItemUpdateEvent) { + if (!config.enabled) return + if (!isInTheEnd()) return + if (!ProfileStorageData.loaded) return + val storage = storage ?: return + + MinecraftExecutor.OnThread.execute { + val newMiteGelInInventory = Minecraft.getMinecraft().thePlayer.inventory.mainInventory + .filter { it?.getInternalNameOrNull() == EnderNode.MITE_GEL.internalName } + .sumOf { it.stackSize } + val change = newMiteGelInInventory - miteGelInInventory + if (change > 0) { + val old = storage.lootCount[EnderNode.MITE_GEL] ?: 0 + storage.lootCount = storage.lootCount.editCopy { + this[EnderNode.MITE_GEL] = old + change + } + update() + } + miteGelInInventory = newMiteGelInInventory + } + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!config.enabled) return + if (!isInTheEnd()) return + config.position.renderStringsAndItems(display, posLabel = "Ender Node Tracker") + } + + @SubscribeEvent + fun onConfigLoad(event: ConfigLoadEvent) { + config.textFormat.afterChange { + update() + } + val storage = storage ?: return + + totalEnderArmor = storage.lootCount.filter { isEnderArmor(it.key.displayName) } + .map { it.value } + .sum() + update() + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.enderNodeTracker", "combat.enderNodeTracker") + } + + private fun calculateProfit(storage: Storage.ProfileSpecific.EnderNodeTracker): Map { + if (!ProfileStorageData.loaded) return emptyMap() + + val newProfit = mutableMapOf() + storage.lootCount.forEach { (item, amount) -> + val price = if (isEnderArmor(item.displayName)) { + 10_000.0 + } else { + (if (!LorenzUtils.noTradeMode) item.internalName.getPriceOrNull() else 0.0) + ?.coerceAtLeast(item.internalName.getNpcPriceOrNull() ?: 0.0) + ?.coerceAtLeast(georgePrice(item) ?: 0.0) + ?: 0.0 + } + newProfit[item] = price * amount + } + return newProfit + } + + private fun update() { + val storage = storage ?: return + lootProfit = calculateProfit(storage) + display = formatDisplay(drawDisplay(storage)) + } + + private fun isInTheEnd() = LorenzUtils.skyBlockArea == "The End" + + private fun isEnderArmor(displayName: String?) = when (displayName) { + EnderNode.END_HELMET.displayName, + EnderNode.END_CHESTPLATE.displayName, + EnderNode.END_LEGGINGS.displayName, + EnderNode.END_BOOTS.displayName, + EnderNode.ENDER_NECKLACE.displayName, + EnderNode.ENDER_GAUNTLET.displayName -> true + + else -> false + } + + private fun georgePrice(petRarity: EnderNode): Double? = when (petRarity) { + EnderNode.COMMON_ENDERMAN_PET -> 100.0 + EnderNode.UNCOMMON_ENDERMAN_PET -> 500.0 + EnderNode.RARE_ENDERMAN_PET -> 2_000.0 + EnderNode.EPIC_ENDERMAN_PET -> 10_000.0 + EnderNode.LEGENDARY_ENDERMAN_PET -> 1_000_000.0 + else -> null + } + + private fun drawDisplay(storage: Storage.ProfileSpecific.EnderNodeTracker) = buildList> { + if (!ProfileStorageData.loaded) return emptyList>() + + addAsSingletonList("§5§lEnder Node Tracker") + addAsSingletonList("§d${storage.totalNodesMined.addSeparators()} Ender Nodes mined") + addAsSingletonList("§6${format(lootProfit.values.sum())} Coins made") + addAsSingletonList(" ") + addAsSingletonList("§b${storage.totalEndermiteNests.addSeparators()} §cEndermite Nest") + + for (item in EnderNode.entries.subList(0, 11)) { + val count = (storage.lootCount[item] ?: 0).addSeparators() + val profit = format(lootProfit[item] ?: 0.0) + addAsSingletonList("§b$count ${item.displayName} §7(§6$profit§7)") + } + addAsSingletonList(" ") + addAsSingletonList( + "§b${totalEnderArmor.addSeparators()} §5Ender Armor " + + "§7(§6${format(totalEnderArmor * 10_000)}§7)" + ) + for (item in EnderNode.entries.subList(11, 16)) { + val count = (storage.lootCount[item] ?: 0).addSeparators() + val profit = format(lootProfit[item] ?: 0.0) + addAsSingletonList("§b$count ${item.displayName} §7(§6$profit§7)") + } + // enderman pet rarities + val (c, u, r, e, l) = EnderNode.entries.subList(16, 21).map { (storage.lootCount[it] ?: 0).addSeparators() } + val profit = format(EnderNode.entries.subList(16, 21).sumOf { lootProfit[it] ?: 0.0 }) + addAsSingletonList("§f$c§7-§a$u§7-§9$r§7-§5$e§7-§6$l §fEnderman Pet §7(§6$profit§7)") + } + + private fun formatDisplay(map: List>): List> { + if (!ProfileStorageData.loaded) return emptyList() + + val newList = mutableListOf>() + for (index in config.textFormat.get()) { + newList.add(map[index]) + } + return newList + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt new file mode 100644 index 000000000..cb80263c8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt @@ -0,0 +1,494 @@ +package at.hannibal2.skyhanni.features.combat.ghostcounter + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.data.SkillExperience +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzActionBarEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.PurseChangeCause +import at.hannibal2.skyhanni.events.PurseChangeEvent +import at.hannibal2.skyhanni.events.TabListUpdateEvent +import at.hannibal2.skyhanni.features.bazaar.BazaarApi.Companion.getBazaarData +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostData.Option +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostData.Option.KILLS +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostData.bestiaryData +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil.formatBestiary +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil.formatText +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil.isUsingCTGhostCounter +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil.preFormat +import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostUtil.prettyTime +import at.hannibal2.skyhanni.utils.CombatUtils._isKilling +import at.hannibal2.skyhanni.utils.CombatUtils.calculateETA +import at.hannibal2.skyhanni.utils.CombatUtils.calculateXP +import at.hannibal2.skyhanni.utils.CombatUtils.interp +import at.hannibal2.skyhanni.utils.CombatUtils.isKilling +import at.hannibal2.skyhanni.utils.CombatUtils.killGainHour +import at.hannibal2.skyhanni.utils.CombatUtils.killGainHourLast +import at.hannibal2.skyhanni.utils.CombatUtils.lastKillUpdate +import at.hannibal2.skyhanni.utils.CombatUtils.lastUpdate +import at.hannibal2.skyhanni.utils.CombatUtils.xpGainHour +import at.hannibal2.skyhanni.utils.CombatUtils.xpGainHourLast +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.chat +import at.hannibal2.skyhanni.utils.LorenzUtils.clickableChat +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber +import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal +import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.OSUtils +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.renderables.Renderable +import io.github.moulberry.notenoughupdates.util.Utils +import io.github.moulberry.notenoughupdates.util.XPInformation +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.io.File +import java.text.NumberFormat +import java.util.Locale +import kotlin.math.roundToInt +import kotlin.math.roundToLong + +object GhostCounter { + + val config get() = SkyHanniMod.feature.combat.ghostCounter + val hidden get() = ProfileStorageData.profileSpecific?.ghostCounter + private var display = emptyList>() + var ghostCounterV3File = + File("." + File.separator + "config" + File.separator + "ChatTriggers" + File.separator + "modules" + File.separator + "GhostCounterV3" + File.separator + ".persistantData.json") + private val skillXPPattern = "[+](?[0-9,.]+) \\((?[0-9,.]+)(?:/(?[0-9,.]+))?\\)".toPattern() + private val combatSectionPattern = + ".*[+](?[0-9,.]+) (?[A-Za-z]+) \\((?(?[0-9.,]+)/(?[0-9.,]+)|(?[0-9.]+)%)\\).*".toPattern() + private val killComboExpiredPattern = + "§cYour Kill Combo has expired! You reached a (?.*) Kill Combo!".toPattern() + private val ghostXPPattern = + "(?\\d+(?:\\.\\d+)?(?:,\\d+)?[kK]?)/(?\\d+(?:\\.\\d+)?(?:,\\d+)?[kKmM]?)".toPattern() + private val bestiaryPattern = + ".*(?:§\\d|§\\w)+BESTIARY (?:§\\d|§\\w)+Ghost (?:§\\d|§\\w)(?\\d+)➜(?:§\\d|§\\w)(?\\d+).*".toPattern() // &3&lBESTIARY &b&lGhost &89➜&b10 + private val skillLevelPattern = ".*§e§lSkills: §r§a(?.*) (?\\d+).*".toPattern() + private val format = NumberFormat.getInstance() + private var percent: Float = 0.0f + private var totalSkillXp = 0 + private var currentSkillXp = 0.0f + private var skillText = "" + private var lastParsedSkillSection = "" + private var lastSkillProgressString: String? = null + private var lastXp: String = "0" + private var gain: Int = 0 + private var num: Double = 0.0 + private var inMist = false + private var notifyCTModule = true + var bestiaryCurrentKill = 0 + private var killETA = "" + private var currentSkill = "" + private var currentSkillLevel = -1 + private const val CONFIG_VALUE_VERSION = 1 + private val SORROW = "SORROW".asInternalName() + private val PLASMA = "PLASMA".asInternalName() + private val VOLTA = "VOLTA".asInternalName() + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled()) return + if (config.onlyOnMist && !inMist) return + config.position.renderStringsAndItems( + display, + extraSpace = config.extraSpace, + posLabel = "Ghost Counter" + ) + } + + private fun formatDisplay(map: List>): List> { + val newList = mutableListOf>() + for (index in config.ghostDisplayText) { + newList.add(map[index]) + } + return newList + } + + fun update() { + display = formatDisplay(drawDisplay()) + } + + private fun drawDisplay() = buildList> { + val textFormatting = config.textFormatting + val ghostKillPerSorrow: Int = when (Option.SORROWCOUNT.get()) { + 0.0 -> 0 + else -> "${((((KILLS.get() / Option.SORROWCOUNT.get()) + Math.ulp(1.0)) * 100) / 100).roundToInt()}".toInt() + } + val avgMagicFind = when (Option.TOTALDROPS.get()) { + 0.0 -> "0" + else -> "${((((hidden?.totalMF!! / Option.TOTALDROPS.get()) + Math.ulp(1.0)) * 100) / 100).roundToPrecision(2)}" + } + + val xpHourFormatting = textFormatting.xpHourFormatting + val xpInterp: Float + val xp = if (xpGainHourLast == xpGainHour && xpGainHour <= 0) { + xpHourFormatting.noData + } else { + xpInterp = interp(xpGainHour, xpGainHourLast, lastUpdate) + val part = "([0-9]{3,}[^,]+)".toRegex().find(format.format(xpInterp))?.groupValues?.get(1) ?: "N/A" + "$part ${if (isKilling) "" else xpHourFormatting.paused}" + } + + val killHourFormatting = textFormatting.killHourFormatting + val killHour: String + var killInterp: Long = 0 + if (killGainHourLast == killGainHour && killGainHour <= 0) { + killHour = killHourFormatting.noData + } else { + killInterp = interp(killGainHour.toFloat(), killGainHourLast.toFloat(), lastKillUpdate).toLong() + killHour = "${format.format(killInterp)} ${if (_isKilling) "" else killHourFormatting.paused}" + } + + val bestiaryFormatting = textFormatting.bestiaryFormatting + val currentKill = hidden?.bestiaryCurrentKill?.toInt() ?: 0 + val killNeeded = hidden?.bestiaryKillNeeded?.toInt() ?: 0 + val nextLevel = hidden?.bestiaryNextLevel?.toInt() ?: -1 + val bestiary = if (config.showMax) { + when (nextLevel) { + 26 -> bestiaryFormatting.maxed.replace("%currentKill%", currentKill.addSeparators()) + in 1..25 -> { + val sum = bestiaryData.filterKeys { it <= nextLevel - 1 }.values.sum() + + val cKill = sum + currentKill + bestiaryCurrentKill = cKill + bestiaryFormatting.showMax_progress + } + + else -> bestiaryFormatting.openMenu + } + } else { + when (nextLevel) { + 26 -> bestiaryFormatting.maxed + in 1..25 -> bestiaryFormatting.progress + else -> bestiaryFormatting.openMenu + } + } + + val etaFormatting = textFormatting.etaFormatting + val remaining: Int = when (config.showMax) { + true -> 250_000 - bestiaryCurrentKill + false -> killNeeded - currentKill + } + + val eta = if (remaining < 0) { + etaFormatting.maxed + } else { + if (killGainHour < 1) { + etaFormatting.noData + } else { + val timeMap = prettyTime(remaining.toLong() * 1000 * 60 * 60 / killInterp) + val time = buildString { + if (timeMap.isNotEmpty()) { + val formatMap = mapOf( + "%days%" to "days", + "%hours%" to "hours", + "%minutes%" to "minutes", + "%seconds%" to "seconds" + ) + for ((format, key) in formatMap) { + if (etaFormatting.time.contains(format)) { + timeMap[key]?.let { value -> + append("$value${format[1]}") + } + } + } + } else { + append("§cEnded!") + } + } + killETA = time + etaFormatting.progress + if (_isKilling) "" else etaFormatting.paused + } + } + + addAsSingletonList(Utils.chromaStringByColourCode(textFormatting.titleFormat.replace("&", "§"))) + addAsSingletonList(textFormatting.ghostKilledFormat.formatText(KILLS)) + addAsSingletonList(textFormatting.sorrowsFormat.formatText(Option.SORROWCOUNT)) + addAsSingletonList(textFormatting.ghostSinceSorrowFormat.formatText(Option.GHOSTSINCESORROW.getInt())) + addAsSingletonList(textFormatting.ghostKillPerSorrowFormat.formatText(ghostKillPerSorrow)) + addAsSingletonList(textFormatting.voltasFormat.formatText(Option.VOLTACOUNT)) + addAsSingletonList(textFormatting.plasmasFormat.formatText(Option.PLASMACOUNT)) + addAsSingletonList(textFormatting.ghostlyBootsFormat.formatText(Option.GHOSTLYBOOTS)) + addAsSingletonList(textFormatting.bagOfCashFormat.formatText(Option.BAGOFCASH)) + addAsSingletonList(textFormatting.avgMagicFindFormat.formatText(avgMagicFind)) + addAsSingletonList(textFormatting.scavengerCoinsFormat.formatText(Option.SCAVENGERCOINS)) + addAsSingletonList(textFormatting.killComboFormat.formatText(Option.MAXKILLCOMBO)) + addAsSingletonList(textFormatting.highestKillComboFormat.formatText(Option.MAXKILLCOMBO)) + addAsSingletonList(textFormatting.skillXPGainFormat.formatText(Option.SKILLXPGAINED)) + addAsSingletonList( + bestiaryFormatting.base.preFormat(bestiary, nextLevel - 1, nextLevel) + .formatBestiary(currentKill, killNeeded) + ) + + addAsSingletonList(xpHourFormatting.base.formatText(xp)) + addAsSingletonList(killHourFormatting.base.formatText(killHour)) + addAsSingletonList(etaFormatting.base.formatText(eta).formatText(killETA)) + + val rate = 0.12 * (1 + (avgMagicFind.toDouble() / 100)) + val sorrowValue = SORROW.getBazaarData()?.buyPrice?.toLong() ?: 0L + val final: String = (killInterp * sorrowValue * (rate / 100)).toLong().addSeparators() + val plasmaValue = PLASMA.getBazaarData()?.buyPrice?.toLong() ?: 0L + val voltaValue = VOLTA.getBazaarData()?.buyPrice?.toLong() ?: 0L + var moneyMade: Long = 0 + val priceMap = listOf( + Triple("Sorrow", Option.SORROWCOUNT.getInt(), sorrowValue), + Triple("Plasma", Option.PLASMACOUNT.getInt(), plasmaValue), + Triple("Volta", Option.VOLTACOUNT.getInt(), voltaValue), + Triple("Bag Of Cash", Option.BAGOFCASH.getInt(), 1_000_000), + Triple("Scavenger Coins", Option.SCAVENGERCOINS.getInt(), 1), + Triple("Ghostly Boots", Option.GHOSTLYBOOTS.getInt(), 77_777) + ) + val moneyMadeTips = buildList { + for ((name, count, value) in priceMap) { + moneyMade += (count.toLong() * value.toLong()) + add("$name: §b${value.addSeparators()} §fx §b${count.addSeparators()} §f= §6${(value.toLong() * count.toLong()).addSeparators()}") + } + add("§bTotal: §6${moneyMade.addSeparators()}") + add("§eClick to copy to clipboard!") + } + val moneyMadeWithClickableTips = Renderable.clickAndHover( + textFormatting.moneyMadeFormat.formatText(moneyMade.addSeparators()), + moneyMadeTips + ) { OSUtils.copyToClipboard(moneyMadeTips.joinToString("\n").removeColor()) } + addAsSingletonList(textFormatting.moneyHourFormat.formatText(final)) + addAsSingletonList(moneyMadeWithClickableTips) + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + if (event.repeatSeconds(1)) { + skillXPPattern.matchMatcher(skillText) { + val gained = group("gained").formatNumber().toDouble() + val current = group("current") + if (current != lastXp) { + val res = current.formatNumber().toString() + gain = (res.toLong() - lastXp.toLong()).toDouble().roundToInt() + num = (gain.toDouble() / gained) + if (gained in 150.0..450.0 && lastXp != "0" && num >= 0) { + KILLS.add(num) + KILLS.add(num, true) + Option.GHOSTSINCESORROW.add(num) + Option.KILLCOMBO.add(num) + Option.SKILLXPGAINED.add(gained * num.roundToLong()) + Option.SKILLXPGAINED.add(gained * num.roundToLong(), true) + hidden?.bestiaryCurrentKill = hidden?.bestiaryCurrentKill?.plus(num) ?: num + } + lastXp = res + } + } + if (notifyCTModule && ProfileStorageData.profileSpecific?.ghostCounter?.ctDataImported != true) { + notifyCTModule = false + if (isUsingCTGhostCounter()) { + clickableChat( + "§6[SkyHanni] GhostCounterV3 ChatTriggers module has been detected, do you want to import saved data ? Click here to import data", + "shimportghostcounterdata" + ) + } + } + inMist = LorenzUtils.skyBlockArea == "The Mist" + update() + } + if (event.repeatSeconds(2)) { + calculateXP() + calculateETA() + } + } + + @SubscribeEvent + fun onActionBar(event: LorenzActionBarEvent) { + if (!isEnabled()) return + if (!inMist) return + combatSectionPattern.matchMatcher(event.message) { + if (group("skillName").lowercase() != "combat") return + parseCombatSection(event.message) + } + } + + private fun parseCombatSection(section: String) { + val sb = StringBuilder() + val nf = NumberFormat.getInstance(Locale.US) + nf.maximumFractionDigits = 2 + if (lastParsedSkillSection == section) { + sb.append(lastSkillProgressString) + } else if (combatSectionPattern.matcher(section).find()) { + combatSectionPattern.matchMatcher(section) { + sb.append("+").append(group("gained")) + val skillName = group("skillName") + val skillPercent = group("percent") != null + var parse = true + if (skillPercent) { + percent = nf.parse(group("percent")).toFloat() + val level = + if (currentSkill == "Combat" && currentSkillLevel != -1) currentSkillLevel else XPInformation.getInstance() + .getSkillInfo(skillName)?.level ?: 0 + if (level > 0) { + totalSkillXp = SkillExperience.getExpForNextLevel(level) + currentSkillXp = totalSkillXp * percent / 100 + } else { + parse = false + } + } else { + currentSkillXp = nf.parse(group("current")).toFloat() + totalSkillXp = nf.parse(group("total")).toInt() + } + percent = 100f.coerceAtMost(percent) + if (!parse) { + sb.append(" (").append(String.format("%.2f", percent)).append("%)") + } else { + sb.append(" (").append(nf.format(currentSkillXp)) + if (totalSkillXp != 0) { + sb.append("/") + sb.append(nf.format(totalSkillXp)) + } + sb.append(")") + } + lastParsedSkillSection = section + lastSkillProgressString = sb.toString() + } + if (sb.toString().isNotEmpty()) { + skillText = sb.toString() + } + } + } + + @SubscribeEvent + fun onTabUpdate(event: TabListUpdateEvent) { + if (!isEnabled()) return + for (line in event.tabList) { + skillLevelPattern.matchMatcher(line) { + currentSkill = group("skillName") + currentSkillLevel = group("skillLevel").toInt() + } + } + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + if (LorenzUtils.skyBlockIsland != IslandType.DWARVEN_MINES) return + for (opt in Option.entries) { + val pattern = opt.pattern ?: continue + pattern.matchMatcher(event.message) { + when (opt) { + Option.SORROWCOUNT, Option.VOLTACOUNT, Option.PLASMACOUNT, Option.GHOSTLYBOOTS -> { + opt.add(1.0) + opt.add(1.0, true) + hidden?.totalMF = hidden?.totalMF?.plus(group("mf").substring(4).toDouble()) + ?: group("mf").substring(4).toDouble() + Option.TOTALDROPS.add(1.0) + if (opt == Option.SORROWCOUNT) + Option.GHOSTSINCESORROW.set(0.0) + update() + } + + Option.BAGOFCASH -> { + Option.BAGOFCASH.add(1.0) + Option.BAGOFCASH.add(1.0, true) + update() + } + + Option.KILLCOMBOCOINS -> { + Option.KILLCOMBOCOINS.set(Option.KILLCOMBOCOINS.get() + group("coin").toDouble()) + update() + } + + else -> {} + } + } + } + killComboExpiredPattern.matchMatcher(event.message) { + if (Option.KILLCOMBO.getInt() > Option.MAXKILLCOMBO.getInt()) { + Option.MAXKILLCOMBO.set(group("combo").formatNumber().toDouble()) + } + if (Option.KILLCOMBO.getInt() > Option.MAXKILLCOMBO.getInt(true)) { + Option.MAXKILLCOMBO.set(group("combo").formatNumber().toDouble(), true) + } + Option.KILLCOMBOCOINS.set(0.0) + Option.KILLCOMBO.set(0.0) + update() + } + //replace with BestiaryLevelUpEvent ? + bestiaryPattern.matchMatcher(event.message) { + val currentLevel = group("nextLevel").toInt() + when (val nextLevel = if (currentLevel >= 25) 26 else currentLevel + 1) { + 26 -> { + hidden?.bestiaryNextLevel = 26.0 + hidden?.bestiaryCurrentKill = 250_000.0 + hidden?.bestiaryKillNeeded = 0.0 + } + + else -> { + val killNeeded: Int = bestiaryData[nextLevel] ?: -1 + hidden?.bestiaryNextLevel = nextLevel.toDouble() + hidden?.bestiaryCurrentKill = 0.0 + hidden?.bestiaryKillNeeded = killNeeded.toDouble() + } + } + update() + } + } + + @SubscribeEvent + fun onPurseChange(event: PurseChangeEvent) { + if (!isEnabled()) return + if (LorenzUtils.skyBlockArea != "The Mist") return + if (event.reason != PurseChangeCause.GAIN_MOB_KILL) return + Option.SCAVENGERCOINS.add(event.coins, true) + Option.SCAVENGERCOINS.add(event.coins) + } + + @SubscribeEvent + fun onInventoryOpen(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + val inventoryName = event.inventoryName + if (inventoryName != "Bestiary ➜ Dwarven Mines") return + val stacks = event.inventoryItems + val ghostStack = stacks[10] ?: return + val bestiaryNextLevel = + if ("§\\wGhost".toRegex().matches(ghostStack.displayName)) 1 else ghostStack.displayName.substring(8) + .romanToDecimal() + 1 + hidden?.bestiaryNextLevel = bestiaryNextLevel.toDouble() + var kills = 0.0 + for (line in ghostStack.getLore()) { + val l = line.removeColor().trim() + if (l.startsWith("Kills: ")) { + kills = "Kills: (.*)".toRegex().find(l)?.groupValues?.get(1)?.formatNumber()?.toDouble() ?: 0.0 + } + ghostXPPattern.matchMatcher(line.removeColor().trim()) { + hidden?.bestiaryCurrentKill = if (kills > 0) kills else group("current").formatNumber().toDouble() + hidden?.bestiaryKillNeeded = group("total").formatNumber().toDouble() + } + } + update() + } + + @SubscribeEvent + fun onConfigLoad(event: ConfigLoadEvent) { + if (hidden?.configUpdateVersion == 0) { + config.textFormatting.bestiaryFormatting.base = " &6Bestiary %display%: &b%value%" + chat("§e[SkyHanni] Your GhostCounter config has been automatically adjusted.") + hidden?.configUpdateVersion = CONFIG_VALUE_VERSION + } + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "ghostCounter", "combat.ghostCounter") + } + + fun isEnabled(): Boolean { + return LorenzUtils.inSkyBlock && config.enabled && LorenzUtils.skyBlockIsland == IslandType.DWARVEN_MINES + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostData.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostData.kt new file mode 100644 index 000000000..70832980c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostData.kt @@ -0,0 +1,90 @@ +package at.hannibal2.skyhanni.features.combat.ghostcounter + +import java.util.regex.Pattern +import kotlin.math.roundToInt + +object GhostData { + + private var session = mutableMapOf( + Option.KILLS to 0.0, + Option.SORROWCOUNT to 0.0, + Option.VOLTACOUNT to 0.0, + Option.PLASMACOUNT to 0.0, + Option.GHOSTLYBOOTS to 0.0, + Option.BAGOFCASH to 0.0, + Option.TOTALDROPS to 0.0, + Option.SCAVENGERCOINS to 0.0, + Option.MAXKILLCOMBO to 0.0, + Option.SKILLXPGAINED to 0.0 + ) + + val bestiaryData = mutableMapOf().apply { + for (i in 1..25) { + this[i] = when (i) { + 1 -> 5 + 2 -> 5 + 3 -> 5 + 4 -> 10 + 5 -> 25 + 6 -> 50 + 7 -> 100 + 8 -> 150 + 9 -> 150 + 10 -> 250 + 11 -> 750 + 12 -> 1_500 + 13 -> 2_000 + 14,15,16,17 -> 2_500 + 18 -> 3_000 + 19,20 -> 3_500 + 21 -> 25_000 + 22,23,24,25 -> 50_000 + else -> 0 + } + } + } + + enum class Option(val pattern: Pattern? = null) { + KILLS, + SORROWCOUNT("§6§lRARE DROP! §r§9Sorrow §r§b\\([+](?.*)% §r§b✯ Magic Find§r§b\\)".toPattern()), + VOLTACOUNT("§6§lRARE DROP! §r§9Volta §r§b\\([+](?.*)% §r§b✯ Magic Find§r§b\\)".toPattern()), + PLASMACOUNT("§6§lRARE DROP! §r§9Plasma §r§b\\([+](?.*)% §r§b✯ Magic Find§r§b\\)".toPattern()), + GHOSTLYBOOTS("§6§lRARE DROP! §r§9Ghostly Boots §r§b\\([+](?.*)% §r§b✯ Magic Find§r§b\\)".toPattern()), + BAGOFCASH("§eThe ghost's death materialized §r§61,000,000 coins §r§efrom the mists!".toPattern()), + KILLCOMBOCOINS("[+]\\d+ Kill Combo [+](?.*) coins per kill".toPattern()), + TOTALDROPS, + GHOSTSINCESORROW, + SCAVENGERCOINS, + MAXKILLCOMBO, + KILLCOMBO("[+]\\d+ Kill Combo [+](?.*) coins per kill".toPattern()), + SKILLXPGAINED; + + fun add(i: Double, s: Boolean = false) { + if (s) + session[this] = session[this]?.plus(i) ?: i + else + GhostCounter.hidden?.data?.set(this, GhostCounter.hidden?.data?.get(this)?.plus(i) ?: i) + } + + fun set(i: Double, s: Boolean = false) { + if (s) + session[this] = i + else + GhostCounter.hidden?.data?.set(this, i) + } + + fun getInt(s: Boolean = false): Int { + return if (s) + session[this]?.roundToInt() ?: 0 + else + GhostCounter.hidden?.data?.get(this)?.roundToInt() ?: 0 + } + + fun get(s: Boolean = false): Double { + return if (s) + session[this] ?: 0.0 + else + GhostCounter.hidden?.data?.get(this) ?: 0.0 + } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostFormatting.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostFormatting.kt new file mode 100644 index 000000000..70f64de29 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostFormatting.kt @@ -0,0 +1,182 @@ +package at.hannibal2.skyhanni.features.combat.ghostcounter + +import com.google.gson.JsonArray +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.awt.Toolkit +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.StringSelection +import java.nio.charset.StandardCharsets +import java.util.Base64 + +object GhostFormatting { + + private const val exportPrefix = "gc/" + + fun importFormat() { + val base64: String = try { + Toolkit.getDefaultToolkit().systemClipboard.getData(DataFlavor.stringFlavor) as String + } catch (e: Exception) { + return + } + + if (base64.length <= exportPrefix.length) return + val jsonString = try { + val t = String(Base64.getDecoder().decode(base64.trim())) + if (!t.startsWith(exportPrefix)) return + t.substring(exportPrefix.length) + } catch (e: IllegalArgumentException) { + return + } + + val list = try { + JsonParser().parse(jsonString).asJsonArray + .filter { it.isJsonPrimitive } + .map { it.asString } + } catch (e: Exception) { + return + } + + if (list.isNotEmpty()) { + with(GhostCounter.config.textFormatting) { + titleFormat = list[0] + ghostKilledFormat = list[1] + sorrowsFormat = list[2] + ghostSinceSorrowFormat = list[3] + ghostKillPerSorrowFormat = list[4] + voltasFormat = list[5] + plasmasFormat = list[6] + ghostlyBootsFormat = list[7] + bagOfCashFormat = list[8] + avgMagicFindFormat = list[9] + scavengerCoinsFormat = list[10] + killComboFormat = list[11] + highestKillComboFormat = list[12] + skillXPGainFormat = list[13] + with(xpHourFormatting) { + base = list[14] + noData = list[15] + paused = list[16] + } + with(bestiaryFormatting) { + base = list[17] + openMenu = list[18] + maxed = list[19] + showMax_progress = list[20] + progress = list[21] + } + with(killHourFormatting) { + base = list[22] + noData = list[23] + paused = list[24] + } + with(etaFormatting) { + base = list[25] + maxed = list[26] + noData = list[27] + progress = list[28] + time = list[29] + } + moneyHourFormat = list[30] + moneyMadeFormat = list[31] + } + } + } + + fun export() { + val list = mutableListOf() + with(GhostCounter.config.textFormatting) { + list.add(titleFormat) + list.add(ghostKilledFormat) + list.add(sorrowsFormat) + list.add(ghostSinceSorrowFormat) + list.add(ghostKillPerSorrowFormat) + list.add(voltasFormat) + list.add(plasmasFormat) + list.add(ghostlyBootsFormat) + list.add(bagOfCashFormat) + list.add(avgMagicFindFormat) + list.add(scavengerCoinsFormat) + list.add(killComboFormat) + list.add(highestKillComboFormat) + list.add(skillXPGainFormat) + with(xpHourFormatting) { + list.add(base) + list.add(noData) + list.add(paused) + } + with(bestiaryFormatting) { + list.add(base) + list.add(openMenu) + list.add(maxed) + list.add(showMax_progress) + list.add(progress) + } + with(killHourFormatting) { + list.add(base) + list.add(noData) + list.add(paused) + } + with(etaFormatting) { + list.add(base) + list.add(maxed) + list.add(noData) + list.add(progress) + list.add(time) + } + list.add(moneyHourFormat) + list.add(moneyMadeFormat) + } + val jsonArray = JsonArray() + for (l in list) { + jsonArray.add(JsonPrimitive(l)) + } + val base64 = Base64.getEncoder().encodeToString((exportPrefix + jsonArray).toByteArray(StandardCharsets.UTF_8)) + Toolkit.getDefaultToolkit().systemClipboard.setContents(StringSelection(base64), null) + } + + fun reset() { + with(GhostCounter.config.textFormatting) { + titleFormat = "&6Ghost Counter" + ghostKilledFormat = " &6Ghost Killed: &b%value% &7(%session%)" + sorrowsFormat = " &6Sorrow: &b%value% &7(%session%)" + ghostSinceSorrowFormat = " &6Ghost since Sorrow: &b%value%" + ghostKillPerSorrowFormat = " &6Ghosts/Sorrow: &b%value%" + voltasFormat = " &6Volta: &b%value% &7(%session%)" + plasmasFormat = " &6Plasmas: &b%value% &7(%session%)" + ghostlyBootsFormat = " &6Ghostly Boots: &b%value% &7(%session%)" + bagOfCashFormat = " &6Bag Of Cash: &b%value% &7(%session%)" + avgMagicFindFormat = " &6Avg Magic Find: &b%value%" + scavengerCoinsFormat = " &6Scavenger Coins: &b%value% &7(%session%)" + killComboFormat = " &6Kill Combo: &b%value%" + highestKillComboFormat = " &6Highest Kill Combo: &b%value% &7(%session%)" + skillXPGainFormat = " &6Skill XP Gained: &b%value% &7(%session%)" + with(xpHourFormatting) { + base = " &6XP/h: &b%value%" + noData = "&bN/A" + paused = "&c(PAUSED)" + } + with(bestiaryFormatting) { + base = " &6Bestiary %currentLevel%->%nextLevel%: &b%value%" + openMenu = "§cOpen Bestiary Menu !" + maxed = "%currentKill% (&c&lMaxed!)" + showMax_progress = "%currentKill%/250k (%percentNumber%%)" + progress = "%currentKill%/%killNeeded%" + } + with(killHourFormatting) { + base = " &6Kill/h: &b%value%" + noData = "§bN/A" + paused = "&c(PAUSED)" + } + with(etaFormatting) { + base = " &6ETA: &b%value%" + maxed = "§c§lMAXED!" + noData = "§bN/A" + progress = "§b%value%" + time = "&6%days%%hours%%minutes%%seconds%" + } + moneyHourFormat = " &6$/h: &b%value%" + moneyMadeFormat = " &6Money made: &b%value%" + } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt new file mode 100644 index 000000000..6dffc62fc --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt @@ -0,0 +1,144 @@ +package at.hannibal2.skyhanni.features.combat.ghostcounter + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigManager +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import io.github.moulberry.notenoughupdates.util.Utils +import java.io.FileReader + +object GhostUtil { + + fun reset() { + for (opt in GhostData.Option.entries) { + opt.set(0.0) + opt.set(0.0, true) + } + GhostCounter.hidden?.totalMF = 0.0 + GhostCounter.update() + } + + fun isUsingCTGhostCounter(): Boolean { + return GhostCounter.ghostCounterV3File.exists() && GhostCounter.ghostCounterV3File.isFile + } + + fun prettyTime(millis: Long): Map { + val seconds = millis / 1000 % 60 + val minutes = millis / 1000 / 60 % 60 + val hours = millis / 1000 / 60 / 60 % 24 + val days = millis / 1000 / 60 / 60 / 24 + return buildMap { + when { + millis < 0 -> { + clear() + } + + minutes == 0L && hours == 0L && days == 0L -> { + put("seconds", seconds.toString()) + } + + hours == 0L && days == 0L -> { + put("seconds", seconds.toString()) + put("minutes", minutes.toString()) + } + + days == 0L -> { + put("seconds", seconds.toString()) + put("minutes", minutes.toString()) + put("hours", hours.toString()) + } + + else -> { + put("seconds", seconds.toString()) + put("minutes", minutes.toString()) + put("hours", hours.toString()) + put("days", days.toString()) + } + } + } + } + + fun importCTGhostCounterData() { + val c = ProfileStorageData.profileSpecific?.ghostCounter ?: return + if (isUsingCTGhostCounter()) { + if (c.ctDataImported) { + LorenzUtils.chat("§e[SkyHanni] §cYou already imported GhostCounterV3 data!") + return + } + val json = ConfigManager.gson.fromJson( + FileReader(GhostCounter.ghostCounterV3File), + com.google.gson.JsonObject::class.java + ) + GhostData.Option.GHOSTSINCESORROW.add(json["ghostsSinceSorrow"].asDouble) + GhostData.Option.SORROWCOUNT.add(json["sorrowCount"].asDouble) + GhostData.Option.BAGOFCASH.add(json["BagOfCashCount"].asDouble) + GhostData.Option.PLASMACOUNT.add(json["PlasmaCount"].asDouble) + GhostData.Option.VOLTACOUNT.add(json["VoltaCount"].asDouble) + GhostData.Option.GHOSTLYBOOTS.add(json["GhostlyBootsCount"].asDouble) + GhostData.Option.KILLS.add(json["ghostsKilled"].asDouble) + GhostCounter.hidden?.totalMF = GhostCounter.hidden?.totalMF?.plus(json["TotalMF"].asDouble) + ?: json["TotalMF"].asDouble + GhostData.Option.TOTALDROPS.add(json["TotalDrops"].asDouble) + c.ctDataImported = true + LorenzUtils.chat("§e[SkyHanni] §aImported data successfully!") + } else + LorenzUtils.chat("§e[SkyHanni] §cGhostCounterV3 ChatTriggers module not found!") + } + + fun String.formatText(option: GhostData.Option) = formatText(option.getInt(), option.getInt(true)) + + fun String.formatText(value: Int, session: Int = -1) = Utils.chromaStringByColourCode( + this.replace("%value%", value.addSeparators()) + .replace("%session%", session.addSeparators()) + .replace("&", "§") + ) + + fun String.formatText(t: String) = Utils.chromaStringByColourCode(this.replace("%value%", t).replace("&", "§")) + + fun String.preFormat(t: String, level: Int, nextLevel: Int) = if (nextLevel == 26) { + Utils.chromaStringByColourCode( + replace("%value%", t) + .replace("%display%", "25") + ) + } else { + Utils.chromaStringByColourCode( + this.replace("%value%", t) + .replace( + "%display%", + "$level->${if (SkyHanniMod.feature.combat.ghostCounter.showMax) "25" else nextLevel}" + ) + ) + } + + fun String.formatText(value: Double, session: Double) = Utils.chromaStringByColourCode( + this.replace("%value%", value.roundToPrecision(2).addSeparators()) + .replace("%session%", session.roundToPrecision(2).addSeparators()) + .replace("&", "§") + ) + + fun String.formatBestiary(currentKill: Int, killNeeded: Int): String { + val bestiaryNextLevel = GhostCounter.hidden?.bestiaryNextLevel + val currentLevel = + bestiaryNextLevel?.let { if (it.toInt() < 0) "25" else "${it.toInt() - 1}" } ?: "§cNo Bestiary Level Data!" + val nextLevel = bestiaryNextLevel?.let { if (GhostCounter.config.showMax) "25" else "${it.toInt()}" } + ?: "§cNo Bestiary Level data!" + + return Utils.chromaStringByColourCode( + this.replace( + "%currentKill%", + if (GhostCounter.config.showMax) GhostCounter.bestiaryCurrentKill.addSeparators() else currentKill.addSeparators() + ) + .replace("%percentNumber%", percent(GhostCounter.bestiaryCurrentKill.toDouble())) + .replace("%killNeeded%", NumberUtil.format(killNeeded)) + .replace("%currentLevel%", currentLevel) + .replace("%nextLevel%", nextLevel) + .replace("&", "§") + ) + } + + private fun percent(number: Double) = + 100.0.coerceAtMost(((number / 250_000) * 100).roundToPrecision(4)).toString() +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AreaMiniBossFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AreaMiniBossFeatures.kt new file mode 100644 index 000000000..e63119a11 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AreaMiniBossFeatures.kt @@ -0,0 +1,121 @@ +package at.hannibal2.skyhanni.features.combat.mobs + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.EntityMaxHealthUpdateEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.withAlpha +import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper +import at.hannibal2.skyhanni.utils.EntityUtils.hasMaxHealth +import at.hannibal2.skyhanni.utils.LocationUtils +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText +import at.hannibal2.skyhanni.utils.TimeUtils +import net.minecraft.entity.EntityLiving +import net.minecraft.entity.monster.EntityBlaze +import net.minecraft.entity.monster.EntityEnderman +import net.minecraft.entity.monster.EntityZombie +import net.minecraft.entity.passive.EntityWolf +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class AreaMiniBossFeatures { + private val config get() = SkyHanniMod.feature.combat.mobs + private var lastTime = 0L + private var miniBossType: AreaMiniBossType? = null + private var respawnCooldown = 11_000L + + @SubscribeEvent + fun onEntityHealthUpdate(event: EntityMaxHealthUpdateEvent) { + if (!LorenzUtils.inSkyBlock) return + + val entity = event.entity + val maxHealth = event.maxHealth + for (bossType in AreaMiniBossType.entries) { + if (!bossType.clazz.isInstance(entity)) continue + if (!entity.hasMaxHealth(bossType.health, false, maxHealth)) continue + + miniBossType = bossType + val time = System.currentTimeMillis() + val diff = time - lastTime + if (diff in 5_000..20_000) { + respawnCooldown = diff + } + lastTime = time + + if (config.areaBossHighlight) { + val color = bossType.color.toColor().withAlpha(bossType.colorOpacity) + RenderLivingEntityHelper.setEntityColor(entity, color) + { config.areaBossHighlight } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.areaBossHighlight } + } + + // TODO add sound + } + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.areaBossRespawnTimer) return + + miniBossType?.apply { + val time = getTime() + val playerLocation = LocationUtils.playerLocation() + spawnLocations.filter { it.distance(playerLocation) < 15 } + .forEach { event.drawDynamicText(it, time, 1.2, ignoreBlocks = false) } + } + } + + private fun AreaMiniBossType.getTime(): String { + val duration = System.currentTimeMillis() - lastTime + val estimatedTime = respawnCooldown - duration % respawnCooldown + val format = TimeUtils.formatDuration(estimatedTime, showMilliSeconds = true) + return color.getChatColor() + format + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + miniBossType = null + } + + enum class AreaMiniBossType( + val clazz: Class, + val health: Int, + val color: LorenzColor, + val colorOpacity: Int, + vararg val spawnLocations: LorenzVec + ) { + GOLDEN_GHOUL( + EntityZombie::class.java, 45_000, LorenzColor.YELLOW, 127, + LorenzVec(-99.7, 39.0, -86.4), + LorenzVec(-128.5, 42.0, -138.5), + ), + OLD_WOLF( + EntityWolf::class.java, 15_000, LorenzColor.GOLD, 60, + LorenzVec(-248.0, 123.0, 54.0), + LorenzVec(-256.7, 105.0, 75.7), + LorenzVec(-268.5, 90.0, 97.7), + LorenzVec(-258.1, 94.0, 75.5), + LorenzVec(-225.7, 92.0, 127.5), + ), + VOIDLING_EXTREMIST( + EntityEnderman::class.java, 8_000_000, LorenzColor.LIGHT_PURPLE, 127, + LorenzVec(-591.1, 10.0, -304.0), + LorenzVec(-544.8, 21.0, -301.1), + LorenzVec(-593.5, 26.0, -328.7), + LorenzVec(-565.0, 41.0, -307.1), + LorenzVec(-573.2, 51.0, -353.4), + ), + MILLENIA_AGED_BLAZE( + EntityBlaze::class.java, 30_000_000, LorenzColor.DARK_RED, 60, + LorenzVec(-292.5, 97.0, -999.7), + LorenzVec(-232.3, 77.0, -951.1), + LorenzVec(-304.1, 73.0, -952.9), + LorenzVec(-281.6, 82.0, -1010.7), + LorenzVec(-342.8, 86.0, -1035.2), + ), + ; + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AshfangMinisNametagHider.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AshfangMinisNametagHider.kt new file mode 100644 index 000000000..657e13d73 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AshfangMinisNametagHider.kt @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.features.combat.mobs + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.utils.LorenzUtils +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.item.EntityArmorStand +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class AshfangMinisNametagHider { + private val config get() = SkyHanniMod.feature.combat.mobs + + @SubscribeEvent(priority = EventPriority.HIGH) + fun onRenderLiving(event: RenderLivingEvent.Specials.Pre) { + if (!LorenzUtils.inSkyBlock) return + if (!config.hideNameTagArachneMinis) return + + val entity = event.entity + if (entity !is EntityArmorStand) return + if (!entity.hasCustomName()) return + + val name = entity.name + if (name.contains("§cArachne's Brood§r")) { + event.isCanceled = true + } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/MobHighlight.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/MobHighlight.kt new file mode 100644 index 000000000..c3df8091d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/MobHighlight.kt @@ -0,0 +1,99 @@ +package at.hannibal2.skyhanni.features.combat.mobs + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.EntityHealthUpdateEvent +import at.hannibal2.skyhanni.events.EntityMaxHealthUpdateEvent +import at.hannibal2.skyhanni.events.withAlpha +import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper +import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.monster.EntityCaveSpider +import net.minecraft.entity.monster.EntityEnderman +import net.minecraft.entity.monster.EntitySpider +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class MobHighlight { + private val config get() = SkyHanniMod.feature.combat.mobs + + @SubscribeEvent + fun onEntityHealthUpdate(event: EntityHealthUpdateEvent) { + if (!LorenzUtils.inSkyBlock) return + + val entity = event.entity + val baseMaxHealth = entity.baseMaxHealth + if (config.corruptedMobHighlight && event.health == baseMaxHealth * 3) { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.DARK_PURPLE.toColor().withAlpha(127)) + { config.corruptedMobHighlight } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.corruptedMobHighlight } + } + } + + @SubscribeEvent + fun onEntityHealthUpdate(event: EntityMaxHealthUpdateEvent) { + if (!LorenzUtils.inSkyBlock) return + + val entity = event.entity + val maxHealth = event.maxHealth + if (config.arachneKeeperHighlight && (maxHealth == 3_000 || maxHealth == 12_000) && entity is EntityCaveSpider) { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.DARK_BLUE.toColor().withAlpha(127)) + { config.arachneKeeperHighlight } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.arachneKeeperHighlight } + } + + if (config.corleoneHighlighter && maxHealth == 1_000_000 && entity is EntityOtherPlayerMP && entity.name == "Team Treasurite") { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.DARK_PURPLE.toColor().withAlpha(127)) + { config.corleoneHighlighter } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.corleoneHighlighter } + } + + if (config.zealotBruiserHighlighter) { + val isZealot = maxHealth == 13_000 || maxHealth == 13_000 * 4 // runic + val isBruiser = maxHealth == 65_000 || maxHealth == 65_000 * 4 // runic + if ((isZealot || isBruiser) && entity is EntityEnderman) { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.DARK_AQUA.toColor().withAlpha(127)) + { config.zealotBruiserHighlighter } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.zealotBruiserHighlighter } + } + } + + if (config.specialZealotHighlighter && maxHealth == 2_000 && entity is EntityEnderman) { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.DARK_RED.toColor().withAlpha(50)) + { config.specialZealotHighlighter } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.specialZealotHighlighter } + } + + if (config.arachneBossHighlighter && entity is EntitySpider) { + checkArachne(entity) + } + } + + private fun checkArachne(entity: EntitySpider) { + if (!entity.hasNameTagWith(1, "[§7Lv300§8] §cArachne") && + !entity.hasNameTagWith(1, "[§7Lv300§8] §lArachne") && + !entity.hasNameTagWith(1, "[§7Lv500§8] §cArachne") && + !entity.hasNameTagWith(1, "[§7Lv500§8] §lArachne") + ) return + + if (entity is EntityCaveSpider) { + markArachneMinis(entity) + } else if (entity.baseMaxHealth == 20_000 || entity.baseMaxHealth == 100_000) { + markArachne(entity) + } + } + + private fun markArachneMinis(entity: EntityLivingBase) { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.GOLD.toColor().withAlpha(50)) + { config.arachneBossHighlighter } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.arachneBossHighlighter } + } + + private fun markArachne(entity: EntityLivingBase) { + RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.RED.toColor().withAlpha(50)) + { config.arachneBossHighlighter } + RenderLivingEntityHelper.setNoHurtTime(entity) { config.arachneBossHighlighter } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/SpawnTimers.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/SpawnTimers.kt new file mode 100644 index 000000000..966f8ad06 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/SpawnTimers.kt @@ -0,0 +1,98 @@ +package at.hannibal2.skyhanni.features.combat.mobs + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.PacketEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.TimeUtils.format +import at.hannibal2.skyhanni.utils.toLorenzVec +import net.minecraft.network.play.server.S2APacketParticles +import net.minecraft.util.EnumParticleTypes +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration.Companion.seconds + +class SpawnTimers { + private val config get() = SkyHanniMod.feature.combat.mobs + + private val arachneAltarLocation = LorenzVec(-283f, 51f, -179f) + private var arachneSpawnTime = SimpleTimeMark.farPast() + private val arachneFragmentMessage = "^☄ [a-z0-9_]{2,22} placed an arachne's calling! something is awakening! \\(4/4\\)\$".toRegex() + private val arachneCrystalMessage = "^☄ [a-z0-9_]{2,22} placed an arachne crystal! something is awakening!$".toRegex() + private var saveNextTickParticles = false + private var particleCounter = 0 + private var tickTime: Long = 0 + private var searchTime: Long = 0 + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + searchTime = 0 + tickTime = 0 + particleCounter = 0 + saveNextTickParticles = false + arachneSpawnTime = SimpleTimeMark.farPast() + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!isEnabled()) return + if (arachneSpawnTime.isInPast()) return + val countDown = arachneSpawnTime.timeUntil() + + val format = countDown.format(showMilliSeconds = true) + event.drawDynamicText(arachneAltarLocation, "§b$format", 1.5) + } + + @SubscribeEvent + fun onChatReceived(event: LorenzChatEvent) { + if (!isEnabled()) return + val message = event.message.removeColor().lowercase() + + if (arachneFragmentMessage.matches(message) || arachneCrystalMessage.matches(message)) { + if (arachneCrystalMessage.matches(message)) { + saveNextTickParticles = true + searchTime = System.currentTimeMillis() + particleCounter = 0 + tickTime = 0L + } else arachneSpawnTime = SimpleTimeMark.now() + 19.seconds + } + } + + // All this to detect "quickspawn" vs regular arachne spawn + @SubscribeEvent(priority = EventPriority.LOW, receiveCanceled = true) + fun onChatPacket(event: PacketEvent.ReceiveEvent) { + if (!saveNextTickParticles) return + if (System.currentTimeMillis() <= searchTime + 3000) return + + if (particleCounter == 0 && tickTime == 0L) tickTime = System.currentTimeMillis() + + if (System.currentTimeMillis() > tickTime + 60) { + arachneSpawnTime = if (particleCounter <= 20) { + SimpleTimeMark.now() + 21.seconds + } else { + SimpleTimeMark.now() + 37.seconds + } + saveNextTickParticles = false + return + } + + val packet = event.packet + if (packet is S2APacketParticles) { + val location = packet.toLorenzVec().round(2) + if (arachneAltarLocation.distance(location) > 30) return + if (packet.particleType == EnumParticleTypes.REDSTONE && packet.particleSpeed == 1.0f) { + particleCounter += 1 + } + } + } + + fun isEnabled() = IslandType.SPIDER_DEN.isInIsland() && LorenzUtils.skyBlockArea == "Arachne's Sanctuary" && config.showArachneSpawnTimer +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/GetFromSacksTabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/GetFromSacksTabComplete.kt new file mode 100644 index 000000000..62e6f9e9c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/GetFromSacksTabComplete.kt @@ -0,0 +1,45 @@ +package at.hannibal2.skyhanni.features.commands.tabcomplete + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.PacketEvent +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.jsonobjects.SackListJson +import net.minecraft.network.play.client.C01PacketChatMessage +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object GetFromSacksTabComplete { + private val config get() = SkyHanniMod.feature.commands.tabComplete + private var sackList = emptyList() + private val commands = arrayOf("gfs", "getfromsacks") + + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + sackList = event.getConstant("Sacks")?.sackList ?: return + } + + fun handleTabComplete(command: String): List? { + if (!isEnabled()) return null + if (command !in commands) return null + + return sackList.map { it.replace(" ", "_") } + } + + @SubscribeEvent + fun onSendPacket(event: PacketEvent.SendEvent) { + if (!isEnabled()) return + + val packet = event.packet as? C01PacketChatMessage ?: return + val message = packet.message + if (commands.any { message.startsWith("/$it ") }) { + val rawName = message.split(" ")[1] + val realName = rawName.replace("_", " ") + if (realName == rawName) return + if (realName !in sackList) return + event.isCanceled = true + LorenzUtils.sendMessageToServer(message.replace(rawName, realName)) + } + } + + fun isEnabled() = LorenzUtils.inSkyBlock && config.gfsSack +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt new file mode 100644 index 000000000..96bfeb83a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt @@ -0,0 +1,86 @@ +package at.hannibal2.skyhanni.features.commands.tabcomplete + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.data.FriendAPI +import at.hannibal2.skyhanni.data.PartyAPI +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.utils.EntityUtils.isNPC +import at.hannibal2.skyhanni.utils.jsonobjects.VipVisitsJson +import net.minecraft.client.Minecraft +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object PlayerTabComplete { + private val config get() = SkyHanniMod.feature.commands.tabComplete + private var vipVisitsJson: VipVisitsJson? = null + + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + vipVisitsJson = event.getConstant("VipVisits") + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.tabCompleteCommands", "commands.tabComplete") + } + + enum class PlayerCategory { + PARTY, + FRIENDS, + ISLAND_PLAYERS, + } + + fun handleTabComplete(command: String): List? { + val commands = mapOf( + "p" to listOf(PlayerCategory.PARTY), + "party" to listOf(PlayerCategory.PARTY), + "pt" to listOf(PlayerCategory.FRIENDS, PlayerCategory.ISLAND_PLAYERS), // /party transfer + "f" to listOf(PlayerCategory.FRIENDS), + "friend" to listOf(PlayerCategory.FRIENDS), + + "msg" to listOf(), + "w" to listOf(), + "tell" to listOf(), + "boop" to listOf(), + + "visit" to listOf(), + "invite" to listOf(), + "ah" to listOf(), + + "pv" to listOf(), // NEU's Profile Viewer + "shmarkplayer" to listOf(), // SkyHanni's Mark Player + ) + val ignored = commands[command] ?: return null + + return buildList { + + if (config.friends && PlayerCategory.FRIENDS !in ignored) { + FriendAPI.getAllFriends().filter { it.bestFriend || !config.onlyBestFriends } + .forEach { add(it.name) } + } + + if (config.islandPlayers && PlayerCategory.ISLAND_PLAYERS !in ignored) { + for (entity in Minecraft.getMinecraft().theWorld.playerEntities) { + if (!entity.isNPC() && entity is EntityOtherPlayerMP) { + add(entity.name) + } + } + } + + if (config.party && PlayerCategory.PARTY !in ignored) { + for (member in PartyAPI.partyMembers) { + add(member) + } + } + + if (config.vipVisits && command == "visit") { + vipVisitsJson?.let { + for (visit in it.vipVisits) { + add(visit) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt new file mode 100644 index 000000000..352748c0b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt @@ -0,0 +1,38 @@ +package at.hannibal2.skyhanni.features.commands.tabcomplete + +import at.hannibal2.skyhanni.features.misc.CollectionTracker + +object TabComplete { + + @JvmStatic + fun handleTabComplete(leftOfCursor: String, originalArray: Array): Array? { + val splits = leftOfCursor.split(" ") + if (splits.size > 1) { + var command = splits.first().lowercase() + if (command.startsWith("/")) { + command = command.substring(1) + customTabComplete(command)?.let { + return buildResponse(splits, it).toSet().toTypedArray() + } + } + } + return null + } + + private fun customTabComplete(command: String): List? { + GetFromSacksTabComplete.handleTabComplete(command)?.let { return it } + WarpTabComplete.handleTabComplete(command)?.let { return it } + PlayerTabComplete.handleTabComplete(command)?.let { return it } + CollectionTracker.handleTabComplete(command)?.let { return it } + + return null + } + + private fun buildResponse(arguments: List, fullResponse: List): List { + if (arguments.size == 2) { + val start = arguments[1].lowercase() + return fullResponse.filter { it.lowercase().startsWith(start) } + } + return emptyList() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt new file mode 100644 index 000000000..4120ebd9d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt @@ -0,0 +1,26 @@ +package at.hannibal2.skyhanni.features.commands.tabcomplete + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.jsonobjects.WarpsJson +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object WarpTabComplete { + private val config get() = SkyHanniMod.feature.commands.tabComplete + private var warpsJson: WarpsJson? = null + + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + warpsJson = event.getConstant("Warps") + } + + fun handleTabComplete(command: String): List? { + if (!isEnabled()) return null + if (command != "warp") return null + + return warpsJson?.warpCommands + } + + fun isEnabled() = LorenzUtils.inSkyBlock && config.warps +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/BossType.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/BossType.kt deleted file mode 100644 index 264c8700a..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/BossType.kt +++ /dev/null @@ -1,111 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -enum class BossType( - val fullName: String, - val bossTypeToggle: Int, - val shortName: String = fullName, - val showDeathTime: Boolean = false -) { - GENERIC_DUNGEON_BOSS("Generic Dungeon boss", 0),//TODO split into different bosses - - //Nether Mini Bosses - NETHER_BLADESOUL("§8Bladesoul", 1), - NETHER_MAGMA_BOSS("§4Magma Boss", 1), - NETHER_ASHFANG("§cAshfang", 1), - NETHER_BARBARIAN_DUKE("§eBarbarian Duke", 1), - NETHER_MAGE_OUTLAW("§5Mage Outlaw", 1), - - NETHER_VANQUISHER("§5Vanquisher", 2), - - END_ENDSTONE_PROTECTOR("§cEndstone Protector", 3), - END_ENDER_DRAGON("Ender Dragon", 4),//TODO fix totally - - SLAYER_ZOMBIE_1("§aRevenant Horror 1", 5, "§aRev 1", showDeathTime = true), - SLAYER_ZOMBIE_2("§eRevenant Horror 2", 5, "§eRev 2", showDeathTime = true), - SLAYER_ZOMBIE_3("§cRevenant Horror 3", 5, "§cRev 3", showDeathTime = true), - SLAYER_ZOMBIE_4("§4Revenant Horror 4", 5, "§4Rev 4", showDeathTime = true), - SLAYER_ZOMBIE_5("§5Revenant Horror 5", 5, "§5Rev 5", showDeathTime = true), - - SLAYER_SPIDER_1("§aTarantula Broodfather 1", 6, "§aTara 1", showDeathTime = true), - SLAYER_SPIDER_2("§eTarantula Broodfather 2", 6, "§eTara 2", showDeathTime = true), - SLAYER_SPIDER_3("§cTarantula Broodfather 3", 6, "§cTara 3", showDeathTime = true), - SLAYER_SPIDER_4("§4Tarantula Broodfather 4", 6, "§4Tara 4", showDeathTime = true), - - SLAYER_WOLF_1("§aSven Packmaster 1", 7, "§aSven 1", showDeathTime = true), - SLAYER_WOLF_2("§eSven Packmaster 2", 7, "§eSven 2", showDeathTime = true), - SLAYER_WOLF_3("§cSven Packmaster 3", 7, "§cSven 3", showDeathTime = true), - SLAYER_WOLF_4("§4Sven Packmaster 4", 7, "§4Sven 4", showDeathTime = true), - - SLAYER_ENDERMAN_1("§aVoidgloom Seraph 1", 8, "§aVoid 1", showDeathTime = true), - SLAYER_ENDERMAN_2("§eVoidgloom Seraph 2", 8, "§eVoid 2", showDeathTime = true), - SLAYER_ENDERMAN_3("§cVoidgloom Seraph 3", 8, "§cVoid 3", showDeathTime = true), - SLAYER_ENDERMAN_4("§4Voidgloom Seraph 4", 8, "§4Void 4", showDeathTime = true), - - SLAYER_BLAZE_1("§aInferno Demonlord 1", 9, "§aInferno 1", showDeathTime = true), - SLAYER_BLAZE_2("§aInferno Demonlord 2", 9, "§aInferno 2", showDeathTime = true), - SLAYER_BLAZE_3("§aInferno Demonlord 3", 9, "§aInferno 3", showDeathTime = true), - SLAYER_BLAZE_4("§aInferno Demonlord 4", 9, "§aInferno 4", showDeathTime = true), - - SLAYER_BLAZE_TYPHOEUS_1("§aInferno Typhoeus 1", 9, "§aTyphoeus 1"), - SLAYER_BLAZE_TYPHOEUS_2("§eInferno Typhoeus 2", 9, "§eTyphoeus 2"), - SLAYER_BLAZE_TYPHOEUS_3("§cInferno Typhoeus 3", 9, "§cTyphoeus 3"), - SLAYER_BLAZE_TYPHOEUS_4("§cInferno Typhoeus 4", 9, "§cTyphoeus 4"), - - SLAYER_BLAZE_QUAZII_1("§aInferno Quazii 1", 9, "§aQuazii 1"), - SLAYER_BLAZE_QUAZII_2("§eInferno Quazii 2", 9, "§eQuazii 2"), - SLAYER_BLAZE_QUAZII_3("§cInferno Quazii 3", 9, "§cQuazii 3"), - SLAYER_BLAZE_QUAZII_4("§cInferno Quazii 4", 9, "§cQuazii 4"), - - SLAYER_BLOODFIEND_1("§aRiftstalker Bloodfiend 1", 23, "§aBlood 1", showDeathTime = true), - SLAYER_BLOODFIEND_2("§6Riftstalker Bloodfiend 2", 23, "§6Blood 2", showDeathTime = true), - SLAYER_BLOODFIEND_3("§cRiftstalker Bloodfiend 3", 23, "§cBlood 3", showDeathTime = true), - SLAYER_BLOODFIEND_4("§4Riftstalker Bloodfiend 4", 23, "§4Blood 4", showDeathTime = true), - SLAYER_BLOODFIEND_5("§5Riftstalker Bloodfiend 5", 23, "§5Blood 5", showDeathTime = true), - - HUB_HEADLESS_HORSEMAN("§6Headless Horseman", 10), - - DUNGEON_F1("", 11), - DUNGEON_F2("", 12), - DUNGEON_F3("", 13), - DUNGEON_F4_THORN("§cThorn", 14), - DUNGEON_F5("§dLivid", 15), - DUNGEON_F("", 16), - DUNGEON_75("", 17), - - MINOS_INQUISITOR("§5Minos Inquisitor", 18), - MINOS_CHAMPION("§2Minos Champion", 18), - GAIA_CONSTURUCT("§2Gaia Construct", 18), - MINOTAUR("§2Minotaur", 18), - - THUNDER("§cThunder", 19), - LORD_JAWBUS("§cLord Jawbus", 19), - - DUMMY("Dummy", 20), - ARACHNE_SMALL("§cSmall Arachne", 21), - ARACHNE_BIG("§4Big Arachne", 21), - - // The Rift - LEECH_SUPREME("§cLeech Supreme", 22), - BACTE("§aBacte", 22), - - WINTER_REINDRAKE("Reindrake", 24),//TODO fix totally - - //TODO arachne - - //TODO corelone - //TODO bal - - - /** - * TODO dungeon mini bosses - * shadow assassin - * lost adventurer - * frozen adventurer - * king midas - * in blood room: bonzo, scarf, ?? - * f7 blood room giants - * - */ - - //TODO diana mythological creatures -} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageCounter.kt deleted file mode 100644 index bf6bb581b..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageCounter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -import java.util.LinkedList - -class DamageCounter { - - var currentDamage = 0L - var currentHealing = 0L - var oldDamages = LinkedList() - var firstTick = 0L - -} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageIndicatorManager.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageIndicatorManager.kt deleted file mode 100644 index 689c3d8c5..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/DamageIndicatorManager.kt +++ /dev/null @@ -1,862 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator -import at.hannibal2.skyhanni.data.ScoreboardData -import at.hannibal2.skyhanni.events.BossHealthChangeEvent -import at.hannibal2.skyhanni.events.DamageIndicatorDetectedEvent -import at.hannibal2.skyhanni.events.DamageIndicatorFinalBossEvent -import at.hannibal2.skyhanni.events.LorenzChatEvent -import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent -import at.hannibal2.skyhanni.events.LorenzTickEvent -import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent -import at.hannibal2.skyhanni.features.dungeon.DungeonAPI -import at.hannibal2.skyhanni.features.slayer.blaze.HellionShield -import at.hannibal2.skyhanni.features.slayer.blaze.setHellionShield -import at.hannibal2.skyhanni.utils.EntityUtils -import at.hannibal2.skyhanni.utils.EntityUtils.getNameTagWith -import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith -import at.hannibal2.skyhanni.utils.LocationUtils -import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer -import at.hannibal2.skyhanni.utils.LorenzColor -import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth -import at.hannibal2.skyhanni.utils.LorenzUtils.between -import at.hannibal2.skyhanni.utils.LorenzUtils.editCopy -import at.hannibal2.skyhanni.utils.LorenzUtils.put -import at.hannibal2.skyhanni.utils.LorenzUtils.round -import at.hannibal2.skyhanni.utils.LorenzVec -import at.hannibal2.skyhanni.utils.NumberUtil -import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators -import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText -import at.hannibal2.skyhanni.utils.StringUtils.removeColor -import at.hannibal2.skyhanni.utils.TimeUtils -import at.hannibal2.skyhanni.utils.getLorenzVec -import net.minecraft.client.Minecraft -import net.minecraft.client.entity.EntityOtherPlayerMP -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.entity.EntityLiving -import net.minecraft.entity.EntityLivingBase -import net.minecraft.entity.item.EntityArmorStand -import net.minecraft.entity.monster.EntityEnderman -import net.minecraft.entity.monster.EntityMagmaCube -import net.minecraft.entity.monster.EntityZombie -import net.minecraft.entity.passive.EntityWolf -import net.minecraftforge.client.event.RenderLivingEvent -import net.minecraftforge.event.entity.EntityJoinWorldEvent -import net.minecraftforge.fml.common.eventhandler.EventPriority -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import java.util.UUID -import kotlin.math.max - -class DamageIndicatorManager { - - private var mobFinder: MobFinder? = null - private val maxHealth = mutableMapOf() - private val config get() = SkyHanniMod.feature.combat.damageIndicator - - companion object { - private var data = mapOf() - private val damagePattern = "[✧✯]?(\\d+[⚔+✧❤♞☄✷ﬗ✯]*)".toPattern() - - fun isBoss(entity: EntityLivingBase) = data.values.any { it.entity == entity } - - fun isDamageSplash(entity: EntityLivingBase): Boolean { - if (entity.ticksExisted > 300 || entity !is EntityArmorStand) return false - if (!entity.hasCustomName()) return false - if (entity.isDead) return false - val name = entity.customNameTag.removeColor().replace(",", "") - - return damagePattern.matcher(name).matches() - } - - fun isBossSpawned(type: BossType) = data.entries.find { it.value.bossType == type } != null - - fun isBossSpawned(vararg types: BossType) = types.any { isBossSpawned(it) } - - fun getDistanceTo(vararg types: BossType): Double { - val playerLocation = LocationUtils.playerLocation() - return data.values.filter { it.bossType in types } - .map { it.entity.getLorenzVec().distance(playerLocation) } - .let { list -> - if (list.isEmpty()) Double.MAX_VALUE else list.minOf { it } - } - } - - fun getNearestDistanceTo(location: LorenzVec): Double { - return data.values - .map { it.entity.getLorenzVec() } - .minOfOrNull { it.distance(location) } ?: Double.MAX_VALUE - } - } - - @SubscribeEvent - fun onWorldChange(event: LorenzWorldChangeEvent) { - mobFinder = MobFinder() - data = emptyMap() - } - - @SubscribeEvent(receiveCanceled = true) - fun onChatMessage(event: LorenzChatEvent) { - mobFinder?.handleChat(event.message) - } - - @SubscribeEvent - fun onWorldRender(event: LorenzRenderWorldEvent) { - if (!isEnabled()) return - - GlStateManager.disableDepth() - GlStateManager.disableCull() - - val player = Minecraft.getMinecraft().thePlayer - - //TODO config to define between 100ms and 5 sec - val filter = data.filter { - val waitForRemoval = if (it.value.dead && !noDeathDisplay(it.value.bossType)) 4_000 else 100 - (System.currentTimeMillis() > it.value.timeLastTick + waitForRemoval) || (it.value.dead && noDeathDisplay(it.value.bossType)) - } - if (filter.isNotEmpty()) { - data = data.editCopy { - for (entry in filter) { - remove(entry.key) - } - } - } - - val sizeHealth: Double - val sizeNameAbove: Double - val sizeBossName: Double - val sizeFinalResults: Double - val smallestDistanceVew: Double - val thirdPersonView = Minecraft.getMinecraft().gameSettings.thirdPersonView - // 0 == normal - // 1 == f3 behind - // 2 == selfie - if (thirdPersonView == 1) { - sizeHealth = 2.8 - sizeNameAbove = 2.2 - sizeBossName = 2.4 - sizeFinalResults = 1.8 - - smallestDistanceVew = 10.0 - } else { - sizeHealth = 1.9 - sizeNameAbove = 1.8 - sizeBossName = 2.1 - sizeFinalResults = 1.4 - - smallestDistanceVew = 6.0 - } - - for (data in data.values) { - - //TODO test end stone protector in hole? - maybe change eye pos -// data.ignoreBlocks = -// data.bossType == BossType.END_ENDSTONE_PROTECTOR && Minecraft.getMinecraft().thePlayer.isSneaking - - if (!data.ignoreBlocks && !player.canEntityBeSeen(data.entity)) continue - if (data.bossType.bossTypeToggle !in config.bossesToShow) continue - - val entity = data.entity - - var healthText = data.healthText - val delayedStart = data.delayedStart - if (delayedStart != -1L && delayedStart > System.currentTimeMillis()) { - val delay = delayedStart - System.currentTimeMillis() - healthText = formatDelay(delay) - } - - val location = if (data.dead && data.deathLocation != null) { - data.deathLocation!! - } else { - val loc = entity.getLorenzVec() - if (data.dead) data.deathLocation = loc - loc - }.add(-0.5, 0.0, -0.5) - - - event.drawDynamicText(location, healthText, sizeHealth, smallestDistanceVew = smallestDistanceVew) - - if (data.nameAbove.isNotEmpty()) { - event.drawDynamicText( - location, - data.nameAbove, - sizeNameAbove, - -18f, - smallestDistanceVew = smallestDistanceVew - ) - } - - var bossName = when (config.bossName) { - 0 -> "" - 1 -> data.bossType.fullName - 2 -> data.bossType.shortName - else -> data.bossType.fullName - } - - if (data.namePrefix.isNotEmpty()) { - bossName = data.namePrefix + bossName - } - if (data.nameSuffix.isNotEmpty()) { - bossName += data.nameSuffix - } - event.drawDynamicText(location, bossName, sizeBossName, -9f, smallestDistanceVew = smallestDistanceVew) - - if (config.showDamageOverTime) { - var diff = 13f - val currentDamage = data.damageCounter.currentDamage - val currentHealing = data.damageCounter.currentHealing - if (currentDamage != 0L || currentHealing != 0L) { - val formatDamage = "§c" + NumberUtil.format(currentDamage) - val formatHealing = "§a+" + NumberUtil.format(currentHealing) - val finalResult = if (currentHealing == 0L) { - formatDamage - } else if (currentDamage == 0L) { - formatHealing - } else { - "$formatDamage §7/ $formatHealing" - } - event.drawDynamicText( - location, - finalResult, - sizeFinalResults, - diff, - smallestDistanceVew = smallestDistanceVew - ) - diff += 9f - } - for (damage in data.damageCounter.oldDamages) { - val formatDamage = "§c" + NumberUtil.format(damage.damage) + "/s" - val formatHealing = "§a+" + NumberUtil.format(damage.healing) + "/s" - val finalResult = if (damage.healing == 0L) { - formatDamage - } else if (damage.damage == 0L) { - formatHealing - } else { - "$formatDamage §7/ $formatHealing" - } - event.drawDynamicText( - location, - finalResult, - sizeFinalResults, - diff, - smallestDistanceVew = smallestDistanceVew - ) - diff += 9f - } - } - - } - GlStateManager.enableDepth() - GlStateManager.enableCull() - } - - private fun noDeathDisplay(bossType: BossType): Boolean { - return when (bossType) { - BossType.SLAYER_BLAZE_TYPHOEUS_1, - BossType.SLAYER_BLAZE_TYPHOEUS_2, - BossType.SLAYER_BLAZE_TYPHOEUS_3, - BossType.SLAYER_BLAZE_TYPHOEUS_4, - BossType.SLAYER_BLAZE_QUAZII_1, - BossType.SLAYER_BLAZE_QUAZII_2, - BossType.SLAYER_BLAZE_QUAZII_3, - BossType.SLAYER_BLAZE_QUAZII_4, - - //TODO f3/m3 4 guardians, f2/m2 4 boss room fighters - -> true - - else -> false - } - } - - private fun tickDamage(damageCounter: DamageCounter) { - val now = System.currentTimeMillis() - if (damageCounter.currentDamage != 0L || damageCounter.currentHealing != 0L) { - if (damageCounter.firstTick == 0L) { - damageCounter.firstTick = now - } - - if (now > damageCounter.firstTick + 1_000) { - damageCounter.oldDamages.add( - 0, OldDamage(now, damageCounter.currentDamage, damageCounter.currentHealing) - ) - damageCounter.firstTick = 0L - damageCounter.currentDamage = 0 - damageCounter.currentHealing = 0 - } - } - damageCounter.oldDamages.removeIf { now > it.time + 5_000 } - } - - private fun formatDelay(delay: Long): String { - val color = when { - delay < 1_000 -> LorenzColor.DARK_PURPLE - delay < 3_000 -> LorenzColor.LIGHT_PURPLE - - else -> LorenzColor.WHITE - } - val format = TimeUtils.formatDuration(delay, showMilliSeconds = true) - return color.getChatColor() + format - } - - @SubscribeEvent - fun onTick(event: LorenzTickEvent) { - if (!isEnabled()) return - data = data.editCopy { - EntityUtils.getEntities().mapNotNull(::checkEntity).forEach { this put it } - } - } - - private fun checkEntity(entity: EntityLivingBase): Pair? { - try { - val entityData = grabData(entity) ?: return null - if (LorenzUtils.inDungeons) { - checkFinalBoss(entityData.finalDungeonBoss, entity.entityId) - } - - val health = entity.health.toLong() - val maxHealth: Long - val biggestHealth = getMaxHealthFor(entity) - if (biggestHealth == 0L) { - val currentMaxHealth = entity.baseMaxHealth.toLong() - maxHealth = max(currentMaxHealth, health) - setMaxHealth(entity, maxHealth) - } else { - maxHealth = biggestHealth - } - - entityData.namePrefix = "" - entityData.nameSuffix = "" - entityData.nameAbove = "" - val customHealthText = if (health == 0L) { - entityData.dead = true - if (entityData.bossType.showDeathTime && config.timeToKillSlayer) { - entityData.nameAbove = entityData.timeToKill - } - "§cDead" - } else { - getCustomHealth(entityData, health, entity, maxHealth) ?: return null - } - - if (data.containsKey(entity.uniqueID)) { - val lastHealth = data[entity.uniqueID]!!.lastHealth - checkDamage(entityData, health, lastHealth) - tickDamage(entityData.damageCounter) - - BossHealthChangeEvent(entityData, lastHealth, health, maxHealth).postAndCatch() - } - entityData.lastHealth = health - - if (customHealthText.isNotEmpty()) { - entityData.healthText = customHealthText - } else { - val color = NumberUtil.percentageColor(health, maxHealth) - entityData.healthText = color.getChatColor() + NumberUtil.format(health) - } - entityData.timeLastTick = System.currentTimeMillis() - return entity.uniqueID to entityData - } catch (e: Throwable) { - e.printStackTrace() - return null - } - } - - private fun getCustomHealth( - entityData: EntityData, - health: Long, - entity: EntityLivingBase, - maxHealth: Long, - ): String? { - - when (entityData.bossType) { - BossType.DUNGEON_F4_THORN -> { - val thorn = checkThorn(health, maxHealth) - if (thorn == null) { - val floor = DungeonAPI.dungeonFloor - LorenzUtils.error("problems with thorn detection! ($floor, $health/$maxHealth)") - } - return thorn - } - - BossType.SLAYER_ENDERMAN_1, - BossType.SLAYER_ENDERMAN_2, - BossType.SLAYER_ENDERMAN_3, - BossType.SLAYER_ENDERMAN_4, - -> return checkEnderSlayer(entity as EntityEnderman, entityData, health.toInt(), maxHealth.toInt()) - - BossType.SLAYER_BLOODFIEND_1, - BossType.SLAYER_BLOODFIEND_2, - BossType.SLAYER_BLOODFIEND_3, - BossType.SLAYER_BLOODFIEND_4, - -> return checkVampireSlayer(entity as EntityOtherPlayerMP, entityData, health.toInt(), maxHealth.toInt()) - - BossType.SLAYER_BLAZE_1, - BossType.SLAYER_BLAZE_2, - BossType.SLAYER_BLAZE_3, - BossType.SLAYER_BLAZE_4, - BossType.SLAYER_BLAZE_QUAZII_2, - BossType.SLAYER_BLAZE_QUAZII_3, - BossType.SLAYER_BLAZE_QUAZII_4, - BossType.SLAYER_BLAZE_TYPHOEUS_2, - BossType.SLAYER_BLAZE_TYPHOEUS_3, - BossType.SLAYER_BLAZE_TYPHOEUS_4, - -> return checkBlazeSlayer(entity as EntityLiving, entityData, health.toInt(), maxHealth.toInt()) - - BossType.NETHER_MAGMA_BOSS -> return checkMagmaCube( - entity as EntityMagmaCube, - entityData, - health.toInt(), - maxHealth.toInt() - ) - - BossType.SLAYER_ZOMBIE_5 -> { - if ((entity as EntityZombie).hasNameTagWith(3, "§fBoom!")) { - //TODO fix -// val ticksAlive = entity.ticksExisted % (20 * 5) -// val remainingTicks = (5 * 20).toLong() - ticksAlive -// val format = formatDelay(remainingTicks * 50) -// entityData.nameSuffix = " §f§lBOOM - $format" - entityData.nameSuffix = " §f§lBOOM!" - } - } - - BossType.SLAYER_WOLF_3, - BossType.SLAYER_WOLF_4, - -> { - if ((entity as EntityWolf).hasNameTagWith(2, "§bCalling the pups!")) { - return "Pups!" - } - } - - BossType.NETHER_BARBARIAN_DUKE, - -> { - val location = entity.getLorenzVec() - entityData.ignoreBlocks = location.y == 117.0 && location.distanceToPlayer() < 15 - } - - else -> return "" - } - return "" - } - - private fun checkBlazeSlayer(entity: EntityLiving, entityData: EntityData, health: Int, maxHealth: Int): String { - var found = false - for (shield in HellionShield.entries) { - val armorStand = entity.getNameTagWith(3, shield.name) - if (armorStand != null) { - val number = armorStand.name.split(" ♨")[1].substring(0, 1) - entity.setHellionShield(shield) - entityData.nameAbove = shield.formattedName + " $number" - found = true - break - } - } - if (!found) { - entity.setHellionShield(null) - } - - if (!SkyHanniMod.feature.slayer.blazes.phaseDisplay) return "" - - var calcHealth = health - val calcMaxHealth: Int - entityData.namePrefix = when (entityData.bossType) { - BossType.SLAYER_BLAZE_1, - BossType.SLAYER_BLAZE_2, - -> { - val step = maxHealth / 2 - calcMaxHealth = step - if (health > step) { - calcHealth -= step - "§c1/2 " - } else { - calcHealth = health - "§a2/2 " - } - } - - BossType.SLAYER_BLAZE_3, - BossType.SLAYER_BLAZE_4, - -> { - val step = maxHealth / 3 - calcMaxHealth = step - if (health > step * 2) { - calcHealth -= step * 2 - "§c1/3 " - } else if (health > step) { - calcHealth -= step - "§e2/3 " - } else { - calcHealth = health - "§a3/3 " - } - } - - else -> return "" - } - - return NumberUtil.percentageColor( - calcHealth.toLong(), calcMaxHealth.toLong() - ).getChatColor() + NumberUtil.format(calcHealth) - } - - private fun checkMagmaCube( - entity: EntityMagmaCube, - entityData: EntityData, - health: Int, - maxHealth: Int, - ): String? { - val slimeSize = entity.slimeSize - entityData.namePrefix = when (slimeSize) { - 24 -> "§c1/6" - 22 -> "§e2/6" - 20 -> "§e3/6" - 18 -> "§e4/6" - 16 -> "§e5/6" - else -> { - val color = NumberUtil.percentageColor(health.toLong(), 10_000_000) - entityData.namePrefix = "§a6/6" - return color.getChatColor() + NumberUtil.format(health) - } - } + " §f" - - //hide while in the middle -// val position = entity.getLorenzVec() - //TODO other logic or something -// entityData.healthLineHidden = position.x == -368.0 && position.z == -804.0 - - var calcHealth = -1 - for (line in ScoreboardData.sidebarLinesRaw) { - if (line.contains("▎")) { - val color: String - if (line.startsWith("§7")) { - color = "§7" - } else if (line.startsWith("§e")) { - color = "§e" - } else if (line.startsWith("§6") || line.startsWith("§a") || line.startsWith("§c")) { - calcHealth = 0 - break - } else { - LorenzUtils.error("unknown magma boss health sidebar format!") - break - } - - val text = line.replace("\uD83C\uDF81" + color, "") - val max = 25.0 - val length = text.split("§e", "§7")[1].length - val missing = (health.toDouble() / max) * length - calcHealth = (health - missing).toInt() - } - } - if (calcHealth == -1) return null - - val color = NumberUtil.percentageColor(calcHealth.toLong(), maxHealth.toLong()) - return color.getChatColor() + NumberUtil.format(calcHealth) - } - - private fun checkEnderSlayer( - entity: EntityEnderman, - entityData: EntityData, - health: Int, - maxHealth: Int, - ): String? { - var calcHealth = health - val calcMaxHealth: Int - entityData.namePrefix = when (entityData.bossType) { - BossType.SLAYER_ENDERMAN_1, - BossType.SLAYER_ENDERMAN_2, - BossType.SLAYER_ENDERMAN_3, - -> { - val step = maxHealth / 3 - calcMaxHealth = step - if (health > step * 2) { - calcHealth -= step * 2 - "§c1/3 " - } else if (health > step) { - calcHealth -= step - "§e2/3 " - } else { - calcHealth = health - "§a3/3 " - } - } - - BossType.SLAYER_ENDERMAN_4 -> { - val step = maxHealth / 6 - calcMaxHealth = step - if (health > step * 5) { - calcHealth -= step * 5 - "§c1/6 " - } else if (health > step * 4) { - calcHealth -= step * 4 - "§e2/6 " - } else if (health > step * 3) { - calcHealth -= step * 3 - "§e3/6 " - } else if (health > step * 2) { - calcHealth -= step * 2 - "§e4/6 " - } else if (health > step) { - calcHealth -= step - "§e5/6 " - } else { - calcHealth = health - "§a6/6 " - } - } - - else -> return null - } - var result = NumberUtil.percentageColor( - calcHealth.toLong(), calcMaxHealth.toLong() - ).getChatColor() + NumberUtil.format(calcHealth) - - if (!SkyHanniMod.feature.slayer.endermen.phaseDisplay) { - result = "" - entityData.namePrefix = "" - } - - - //Hit phase - val armorStandHits = entity.getNameTagWith(3, " Hit") - if (armorStandHits != null) { - val maxHits = when (entityData.bossType) { - BossType.SLAYER_ENDERMAN_1 -> 15 - BossType.SLAYER_ENDERMAN_2 -> 30 - BossType.SLAYER_ENDERMAN_3 -> 60 - BossType.SLAYER_ENDERMAN_4 -> 100 - else -> 100 - } - val name = armorStandHits.name.removeColor() - - // TODO replace this super ugly workaround with regex - val text = name.between("Seraph ", " Hit") - val hits = try { - text.toInt() - } catch (e: NumberFormatException) { - text.substring(2).toInt() - } - - return NumberUtil.percentageColor(hits.toLong(), maxHits.toLong()).getChatColor() + "$hits Hits" - } - - //Laser phase - if (config.enderSlayer.laserPhaseTimer && entity.ridingEntity != null) { - val ticksAlive = entity.ridingEntity.ticksExisted.toLong() - //TODO more tests, more exact values, better logic? idk make this working perfectly pls - //val remainingTicks = 8 * 20 - ticksAlive - val remainingTicks = (7.4 * 20).toLong() - ticksAlive - - if (config.enderSlayer.showHealthDuringLaser) { - entityData.nameSuffix = " §f" + formatDelay(remainingTicks * 50) - } else { - return formatDelay(remainingTicks * 50) - } - } - - return result - } - - private fun checkVampireSlayer( - entity: EntityOtherPlayerMP, - entityData: EntityData, - health: Int, - maxHealth: Int, - ): String { - val config = config.vampireSlayer - - if (config.percentage) { - val percentage = LorenzUtils.formatPercentage(health.toDouble() / maxHealth) - entityData.nameSuffix = " §e$percentage" - } - - if (config.maniaCircles) { - entity.ridingEntity?.let { - val existed = it.ticksExisted - if (existed > 40) { - val end = (20 * 26) - existed - val time = end.toDouble() / 20 - entityData.nameAbove = "Mania Circles: §b${time.round(1)}s" - return "" - } - } - } - - if (config.hpTillSteak) { - val rest = maxHealth * 0.2 - val showHealth = health - rest - if (showHealth < 300) { - entityData.nameAbove = if (showHealth > 0) { - "§cHP till Steak: ${showHealth.addSeparators()}" - } else "§cSteak!" - } - } - - return "" - } - - private fun checkThorn(realHealth: Long, realMaxHealth: Long): String? { - val maxHealth: Int - val health = if (DungeonAPI.isOneOf("F4")) { - maxHealth = 4 - - if (realMaxHealth == 300_000L) { - // no derpy - when { - realHealth == 1L -> 0 - realHealth <= 66_000 -> 1 - realHealth <= 144_000 -> 2 - realHealth <= 222_000 -> 3 - realHealth <= 300_000 -> 4 - - else -> return null - } - } else { - // derpy - when { - realHealth == 1L -> 0 - realHealth <= 132_000 -> 1 - realHealth <= 288_000 -> 2 - realHealth <= 444_000 -> 3 - realHealth <= 600_000 -> 4 - - else -> return null - } - } - } else if (DungeonAPI.isOneOf("M4")) { - maxHealth = 6 - - if (realMaxHealth == 900_000L) { - // no derpy - when { - realHealth == 1L -> 0 - realHealth <= 135_000 -> 1 - realHealth <= 288_000 -> 2 - realHealth <= 441_000 -> 3 - realHealth <= 594_000 -> 4 - realHealth <= 747_000 -> 5 - realHealth <= 900_000L -> 6 - - else -> return null - } - } else { - // derpy - when { - realHealth == 1L -> 0 - realHealth <= 270_000 -> 1 - realHealth <= 576_000 -> 2 - realHealth <= 882_000 -> 3 - realHealth <= 1_188_000 -> 4 - realHealth <= 1_494_000 -> 5 - realHealth <= 1_800_000 -> 6 - - else -> return null - } - } - } else { - LorenzUtils.error("Invalid/impossible thorn floor!") - return null - } - val color = NumberUtil.percentageColor(health.toLong(), maxHealth.toLong()) - return color.getChatColor() + health + "/" + maxHealth - } - - private fun checkDamage(entityData: EntityData, health: Long, lastHealth: Long) { - val damage = lastHealth - health - val healing = health - lastHealth - if (damage > 0 && entityData.bossType != BossType.DUMMY) { - val damageCounter = entityData.damageCounter - damageCounter.currentDamage += damage - } - if (healing > 0) { - //Hide auto heal every 10 ticks (with rounding errors) - if ((healing == 15_000L || healing == 15_001L) && entityData.bossType == BossType.SLAYER_ZOMBIE_5) return - - val damageCounter = entityData.damageCounter - damageCounter.currentHealing += healing - } - } - - private fun grabData(entity: EntityLivingBase): EntityData? { - if (data.contains(entity.uniqueID)) return data[entity.uniqueID] - - - val entityResult = mobFinder?.tryAdd(entity) ?: return null - - val entityData = EntityData( - entity, - entityResult.ignoreBlocks, - entityResult.delayedStart, - entityResult.finalDungeonBoss, - entityResult.bossType, - foundTime = System.currentTimeMillis() - ) - DamageIndicatorDetectedEvent(entityData).postAndCatch() - return entityData - } - - private fun checkFinalBoss(finalBoss: Boolean, id: Int) { - if (finalBoss) { - DamageIndicatorFinalBossEvent(id).postAndCatch() - } - } - - private fun setMaxHealth(entity: EntityLivingBase, currentMaxHealth: Long) { - maxHealth[entity.uniqueID!!] = currentMaxHealth - } - - private fun getMaxHealthFor(entity: EntityLivingBase): Long { - return maxHealth.getOrDefault(entity.uniqueID!!, 0L) - } - - @SubscribeEvent - fun onEntityJoin(event: EntityJoinWorldEvent) { - mobFinder?.handleNewEntity(event.entity) - } - - private val dummyDamageCache = mutableListOf() - - @SubscribeEvent(priority = EventPriority.HIGH) - fun onRenderLiving(event: RenderLivingEvent.Specials.Pre) { - val entity = event.entity - - val entityData = data.values.find { - val distance = it.entity.getLorenzVec().distance(entity.getLorenzVec()) - distance < 4.5 - } - - if (isDamageSplash(entity)) { - val name = entity.customNameTag.removeColor().replace(",", "") - - if (entityData != null) { - if (config.hideDamageSplash) { - event.isCanceled = true - } - if (entityData.bossType == BossType.DUMMY) { - val uuid = entity.uniqueID - if (dummyDamageCache.contains(uuid)) return - dummyDamageCache.add(uuid) - val dmg = name.toCharArray().filter { Character.isDigit(it) }.joinToString("").toLong() - entityData.damageCounter.currentDamage += dmg - } - } - } else { - if (entityData != null && isEnabled() && config.hideVanillaNametag) { - val name = entity.name - if (name.contains("Plaesmaflux")) return - if (name.contains("Overflux")) return - if (name.contains("Mana Flux")) return - if (name.contains("Radiant")) return - event.isCanceled = true - } - } - } - - @SubscribeEvent - fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { - event.move(2, "damageIndicator", "combat.damageIndicator") - event.move(3, "slayer.endermanPhaseDisplay", "slayer.endermen.phaseDisplay") - event.move(3, "slayer.blazePhaseDisplay", "slayer.blazes.phaseDisplay") - } - - fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled -} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityData.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityData.kt deleted file mode 100644 index 7055b7df4..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityData.kt +++ /dev/null @@ -1,30 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -import at.hannibal2.skyhanni.utils.LorenzVec -import at.hannibal2.skyhanni.utils.TimeUnit -import at.hannibal2.skyhanni.utils.TimeUtils -import net.minecraft.entity.EntityLivingBase - -class EntityData( - val entity: EntityLivingBase, - var ignoreBlocks: Boolean, - var delayedStart: Long, - val finalDungeonBoss: Boolean, - val bossType: BossType, - val damageCounter: DamageCounter = DamageCounter(), - val foundTime: Long, - - var lastHealth: Long = 0L, - var healthText: String = "", - var timeLastTick: Long = 0, - var namePrefix: String = "", - var nameSuffix: String = "", - var nameAbove: String = "", - var dead: Boolean = false, - var deathLocation: LorenzVec? = null, -) { - val timeToKill by lazy { - val duration = System.currentTimeMillis() - foundTime - "§e" + TimeUtils.formatDuration(duration, TimeUnit.SECOND, showMilliSeconds = true) - } -} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityResult.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityResult.kt deleted file mode 100644 index a17adf309..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/EntityResult.kt +++ /dev/null @@ -1,8 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -class EntityResult( - val delayedStart: Long = -1L, - val ignoreBlocks: Boolean = false, - val finalDungeonBoss: Boolean = false, - val bossType: BossType = BossType.GENERIC_DUNGEON_BOSS, -) \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/MobFinder.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/MobFinder.kt deleted file mode 100644 index 2bb65b942..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/MobFinder.kt +++ /dev/null @@ -1,513 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -import at.hannibal2.skyhanni.data.IslandType -import at.hannibal2.skyhanni.features.dungeon.DungeonAPI -import at.hannibal2.skyhanni.features.dungeon.DungeonLividFinder -import at.hannibal2.skyhanni.features.rift.RiftAPI -import at.hannibal2.skyhanni.utils.EntityUtils -import at.hannibal2.skyhanni.utils.EntityUtils.hasBossHealth -import at.hannibal2.skyhanni.utils.EntityUtils.hasMaxHealth -import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith -import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer -import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth -import at.hannibal2.skyhanni.utils.LorenzUtils.derpy -import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland -import at.hannibal2.skyhanni.utils.LorenzVec -import at.hannibal2.skyhanni.utils.StringUtils.matchRegex -import at.hannibal2.skyhanni.utils.getLorenzVec -import net.minecraft.client.entity.EntityOtherPlayerMP -import net.minecraft.entity.Entity -import net.minecraft.entity.EntityLiving -import net.minecraft.entity.EntityLivingBase -import net.minecraft.entity.boss.EntityDragon -import net.minecraft.entity.boss.EntityWither -import net.minecraft.entity.monster.EntityBlaze -import net.minecraft.entity.monster.EntityEnderman -import net.minecraft.entity.monster.EntityGhast -import net.minecraft.entity.monster.EntityGiantZombie -import net.minecraft.entity.monster.EntityGuardian -import net.minecraft.entity.monster.EntityIronGolem -import net.minecraft.entity.monster.EntityMagmaCube -import net.minecraft.entity.monster.EntityPigZombie -import net.minecraft.entity.monster.EntitySkeleton -import net.minecraft.entity.monster.EntitySlime -import net.minecraft.entity.monster.EntitySpider -import net.minecraft.entity.monster.EntityZombie -import net.minecraft.entity.passive.EntityHorse -import net.minecraft.entity.passive.EntityWolf -import java.util.UUID - -class MobFinder { - - //F1 - private var floor1bonzo1 = false - private var floor1bonzo1SpawnTime = 0L - private var floor1bonzo2 = false - private var floor1bonzo2SpawnTime = 0L - - //F2 - private var floor2summons1 = false - private var floor2summons1SpawnTime = 0L - private var floor2summonsDiedOnce = mutableListOf() - private var floor2secondPhase = false - private var floor2secondPhaseSpawnTime = 0L - - //F3 - private var floor3GuardianShield = false - private var floor3GuardianShieldSpawnTime = 0L - private var guardians = mutableListOf() - private var floor3Professor = false - private var floor3ProfessorSpawnTime = 0L - private var floor3ProfessorGuardianPrepare = false - private var floor3ProfessorGuardianPrepareSpawnTime = 0L - private var floor3ProfessorGuardian = false - private var floor3ProfessorGuardianEntity: EntityGuardian? = null - - //F5 - private var floor5lividEntity: EntityOtherPlayerMP? = null - private var floor5lividEntitySpawnTime = 0L - - //F6 - private var floor6Giants = false - private var floor6GiantsSpawnTime = 0L - private var floor6GiantsSeparateDelay = mutableMapOf() - private var floor6Sadan = false - private var floor6SadanSpawnTime = 0L - - internal fun tryAdd(entity: EntityLivingBase): EntityResult? { - if (LorenzUtils.inDungeons) { - if (DungeonAPI.isOneOf("F1", "M1")) { - if (floor1bonzo1 && entity is EntityOtherPlayerMP && entity.name == "Bonzo ") { - return EntityResult(floor1bonzo1SpawnTime) - } - if (floor1bonzo2 && entity is EntityOtherPlayerMP && entity.name == "Bonzo ") { - return EntityResult(floor1bonzo2SpawnTime, finalDungeonBoss = true) - } - } - - if (DungeonAPI.isOneOf("F2", "M2")) { - if (entity.name == "Summon " && entity is EntityOtherPlayerMP) { - if (floor2summons1 && !floor2summonsDiedOnce.contains(entity)) { - if (entity.health.toInt() != 0) { - return EntityResult(floor2summons1SpawnTime) - } else { - floor2summonsDiedOnce.add(entity) - } - } - if (floor2secondPhase) { - return EntityResult(floor2secondPhaseSpawnTime) - } - } - - if (floor2secondPhase && entity is EntityOtherPlayerMP) { - //TODO only show scarf after (all/at least x) summons are dead? - val result = entity.name == "Scarf " - if (result) { - return EntityResult(floor2secondPhaseSpawnTime, finalDungeonBoss = true) - } - } - } - - if (DungeonAPI.isOneOf("F3", "M3")) { - if (entity is EntityGuardian && floor3GuardianShield) { - if (guardians.size == 4) { - var totalHealth = 0 - for (guardian in guardians) { - totalHealth += guardian.health.toInt() - } - if (totalHealth == 0) { - floor3GuardianShield = false - guardians.clear() - } - } else { - findGuardians() - } - if (guardians.contains(entity)) { - return EntityResult(floor3GuardianShieldSpawnTime, true) - } - } - - if (floor3Professor && entity is EntityOtherPlayerMP && entity.name == "The Professor") { - return EntityResult( - floor3ProfessorSpawnTime, - floor3ProfessorSpawnTime + 1_000 > System.currentTimeMillis() - ) - } - if (floor3ProfessorGuardianPrepare && entity is EntityOtherPlayerMP && entity.name == "The Professor") { - return EntityResult(floor3ProfessorGuardianPrepareSpawnTime, true) - } - - if (entity is EntityGuardian && floor3ProfessorGuardian && entity == floor3ProfessorGuardianEntity) { - return EntityResult(finalDungeonBoss = true) - } - } - - if (DungeonAPI.isOneOf("F4", "M4") && entity is EntityGhast) { - return EntityResult( - bossType = BossType.DUNGEON_F4_THORN, - ignoreBlocks = true, - finalDungeonBoss = true - ) - } - - if (DungeonAPI.isOneOf("F5", "M5") && entity is EntityOtherPlayerMP && entity == DungeonLividFinder.livid) { - return EntityResult( - bossType = BossType.DUNGEON_F5, - ignoreBlocks = true, - finalDungeonBoss = true - ) - } - - if (DungeonAPI.isOneOf("F6", "M6") && entity is EntityGiantZombie && !entity.isInvisible) { - if (floor6Giants && entity.posY > 68) { - val extraDelay = checkExtraF6GiantsDelay(entity) - return EntityResult( - floor6GiantsSpawnTime + extraDelay, - floor6GiantsSpawnTime + extraDelay + 1_000 > System.currentTimeMillis() - ) - } - - if (floor6Sadan) { - return EntityResult(floor6SadanSpawnTime, finalDungeonBoss = true) - } - } - } else if (RiftAPI.inRift()) { - if (entity is EntityOtherPlayerMP) { - if (entity.name == "Leech Supreme") { - return EntityResult(bossType = BossType.LEECH_SUPREME) - } - - if (entity.name == "Bloodfiend ") { - when { - entity.hasMaxHealth(625, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_1) - entity.hasMaxHealth(1_100, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_2) - entity.hasMaxHealth(1_800, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_3) - entity.hasMaxHealth(2_400, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_4) - entity.hasMaxHealth(3_000, true) -> return EntityResult(bossType = BossType.SLAYER_BLOODFIEND_5) - } - } - } - if (entity is EntitySlime && entity.baseMaxHealth == 1_000) { - return EntityResult(bossType = BossType.BACTE) - } - } else { - if (entity is EntityBlaze && entity.name != "Dinnerbone" && entity.hasNameTagWith( - 2, - "§e﴾ §8[§7Lv200§8] §l§8§lAshfang§r " - ) && entity.hasMaxHealth(50_000_000, true) - ) { - return EntityResult(bossType = BossType.NETHER_ASHFANG) - } - if (entity is EntitySkeleton && entity.hasNameTagWith(5, "§e﴾ §8[§7Lv200§8] §l§8§lBladesoul§r ")) { - return EntityResult(bossType = BossType.NETHER_BLADESOUL) - } - if (entity is EntityOtherPlayerMP) { - if (entity.name == "Mage Outlaw") { - return EntityResult(bossType = BossType.NETHER_MAGE_OUTLAW) - } - if (entity.name == "DukeBarb " && entity.getLorenzVec().distanceToPlayer() < 30) { - return EntityResult(bossType = BossType.NETHER_BARBARIAN_DUKE) - } - } - if (entity is EntityWither && entity.hasNameTagWith(4, "§8[§7Lv100§8] §c§5Vanquisher§r ")) { - return EntityResult(bossType = BossType.NETHER_VANQUISHER) - } - if (entity is EntityEnderman && entity.hasNameTagWith(3, "§c☠ §bVoidgloom Seraph ")) { - when { - entity.hasMaxHealth(300_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_1) - entity.hasMaxHealth(12_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_2) - entity.hasMaxHealth(50_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_3) - entity.hasMaxHealth(210_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_ENDERMAN_4) - } - } - if (entity is EntityDragon) { - //TODO testing and use sidebar data - if (IslandType.THE_END.isInIsland()) { - return EntityResult(bossType = BossType.END_ENDER_DRAGON) - } else if (IslandType.WINTER.isInIsland()) { - return EntityResult(bossType = BossType.WINTER_REINDRAKE) - } - } - if (entity is EntityIronGolem && entity.hasNameTagWith(3, "§e﴾ §8[§7Lv100§8] §lEndstone Protector§r ")) { - return EntityResult(bossType = BossType.END_ENDSTONE_PROTECTOR) - } - if (entity is EntityZombie) { - if (entity.hasNameTagWith(2, "§c☠ §bRevenant Horror")) { - when { - entity.hasMaxHealth(500, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_1) - entity.hasMaxHealth(20_000, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_2) - entity.hasMaxHealth(400_000, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_3) - entity.hasMaxHealth(1_500_000, true) -> return EntityResult(bossType = BossType.SLAYER_ZOMBIE_4) - } - } - if (entity.hasNameTagWith(2, "§c☠ §fAtoned Horror ") && entity.hasMaxHealth(10_000_000, true)) { - return EntityResult(bossType = BossType.SLAYER_ZOMBIE_5) - } - } - if (entity is EntityLiving && entity.hasNameTagWith(2, "Dummy §a10M§c❤")) { - return EntityResult(bossType = BossType.DUMMY) - } - if (entity is EntityMagmaCube && entity.hasNameTagWith( - 15, - "§e﴾ §8[§7Lv500§8] §l§4§lMagma Boss§r " - ) && entity.hasMaxHealth(200_000_000, true) - ) { - return EntityResult(bossType = BossType.NETHER_MAGMA_BOSS, ignoreBlocks = true) - } - if (entity is EntityHorse && entity.hasNameTagWith( - 15, - "§8[§7Lv100§8] §c§6Headless Horseman§r " - ) && entity.hasMaxHealth(3_000_000, true) - ) { - return EntityResult(bossType = BossType.HUB_HEADLESS_HORSEMAN) - } - if (entity is EntityBlaze && entity.hasNameTagWith(2, "§c☠ §bInferno Demonlord ")) { - when { - entity.hasBossHealth(2_500_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_1) - entity.hasBossHealth(10_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_2) - entity.hasBossHealth(45_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_3) - entity.hasBossHealth(150_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_4) - } - } - if (entity is EntityPigZombie && entity.hasNameTagWith(2, "§c☠ §6ⓉⓎⓅⒽⓄⒺⓊⓈ ")) { - when { - entity.hasBossHealth(10_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_4) - entity.hasBossHealth(5_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_3) - entity.hasBossHealth(1_750_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_2) - entity.hasBossHealth(500_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_TYPHOEUS_1) - } - } - if (entity is EntitySkeleton && entity.hasNameTagWith(2, "§c☠ §3ⓆⓊⒶⓏⒾⒾ ")) { - when { - entity.hasBossHealth(10_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_4) - entity.hasBossHealth(5_000_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_3) - entity.hasBossHealth(1_750_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_2) - entity.hasBossHealth(500_000) -> return EntityResult(bossType = BossType.SLAYER_BLAZE_QUAZII_1) - } - } - - if (entity is EntitySpider) { - if (entity.hasNameTagWith(1, "§5☠ §4Tarantula Broodfather ")) { - when { - entity.hasMaxHealth(740, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_1) - entity.hasMaxHealth(30_000, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_2) - entity.hasMaxHealth(900_000, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_3) - entity.hasMaxHealth(2_400_000, true) -> return EntityResult(bossType = BossType.SLAYER_SPIDER_4) - } - } - checkArachne(entity)?.let { return it } - } - if (entity is EntityWolf && entity.hasNameTagWith(1, "§c☠ §fSven Packmaster ")) { - when { - entity.hasMaxHealth(2_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_1) - entity.hasMaxHealth(40_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_2) - entity.hasMaxHealth(750_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_3) - entity.hasMaxHealth(2_000_000, true) -> return EntityResult(bossType = BossType.SLAYER_WOLF_4) - } - } - if (entity is EntityOtherPlayerMP) { - if (entity.name == "Minos Inquisitor") return EntityResult(bossType = BossType.MINOS_INQUISITOR) - if (entity.name == "Minos Champion") return EntityResult(bossType = BossType.MINOS_CHAMPION) - if (entity.name == "Minotaur ") return EntityResult(bossType = BossType.MINOTAUR) - } - if (entity is EntityIronGolem && entity.hasMaxHealth(1_500_000)) { - return EntityResult(bossType = BossType.GAIA_CONSTURUCT) - } - if (entity is EntityGuardian && entity.hasMaxHealth(35_000_000)) { - return EntityResult(bossType = BossType.THUNDER) - } - - if (entity is EntityIronGolem && entity.hasMaxHealth(100_000_000)) { - return EntityResult(bossType = BossType.LORD_JAWBUS) - } - } - - return null - } - - private fun checkArachne(entity: EntitySpider): EntityResult? { - if (entity.hasNameTagWith(1, "[§7Lv300§8] §cArachne") || - entity.hasNameTagWith(1, "[§7Lv300§8] §lArachne") - ) { - val maxHealth = entity.baseMaxHealth - // Ignore the minis - if (maxHealth == 12 || maxHealth.derpy() == 4000) return null - return EntityResult(bossType = BossType.ARACHNE_SMALL) - } - if (entity.hasNameTagWith(1, "[§7Lv500§8] §cArachne") || - entity.hasNameTagWith(1, "[§7Lv500§8] §lArachne") - ) { - val maxHealth = entity.baseMaxHealth - if (maxHealth == 12 || maxHealth.derpy() == 20_000) return null - return EntityResult(bossType = BossType.ARACHNE_BIG) - } - - return null - } - - private fun checkExtraF6GiantsDelay(entity: EntityGiantZombie): Long { - val uuid = entity.uniqueID - - if (floor6GiantsSeparateDelay.contains(uuid)) { - return floor6GiantsSeparateDelay[uuid]!! - } - - val middle = LorenzVec(-8, 0, 56) - - val loc = entity.getLorenzVec() - - var pos = 0 - - //first - if (loc.x > middle.x && loc.z > middle.z) { - pos = 2 - } - - //second - if (loc.x > middle.x && loc.z < middle.z) { - pos = 3 - } - - //third - if (loc.x < middle.x && loc.z < middle.z) { - pos = 0 - } - - //fourth - if (loc.x < middle.x && loc.z > middle.z) { - pos = 1 - } - - val extraDelay = 900L * pos - floor6GiantsSeparateDelay[uuid] = extraDelay - - return extraDelay - } - - fun handleChat(message: String) { - if (LorenzUtils.inDungeons) { - when (message) { - //F1 - "§c[BOSS] Bonzo§r§f: Gratz for making it this far, but I’m basically unbeatable." -> { - floor1bonzo1 = true - floor1bonzo1SpawnTime = System.currentTimeMillis() + 11_250 - } - - "§c[BOSS] Bonzo§r§f: Oh noes, you got me.. what ever will I do?!" -> { - floor1bonzo1 = false - } - - "§c[BOSS] Bonzo§r§f: Oh I'm dead!" -> { - floor1bonzo2 = true - floor1bonzo2SpawnTime = System.currentTimeMillis() + 4_200 - } - - "§c[BOSS] Bonzo§r§f: Alright, maybe I'm just weak after all.." -> { - floor1bonzo2 = false - } - - //F2 - "§c[BOSS] Scarf§r§f: ARISE, MY CREATIONS!" -> { - floor2summons1 = true - floor2summons1SpawnTime = System.currentTimeMillis() + 3_500 - } - - "§c[BOSS] Scarf§r§f: Those toys are not strong enough I see." -> { - floor2summons1 = false - } - - "§c[BOSS] Scarf§r§f: Don't get too excited though." -> { - floor2secondPhase = true - floor2secondPhaseSpawnTime = System.currentTimeMillis() + 6_300 - } - - "§c[BOSS] Scarf§r§f: Whatever..." -> { - floor2secondPhase = false - } - - //F3 - "§c[BOSS] The Professor§r§f: I was burdened with terrible news recently..." -> { - floor3GuardianShield = true - floor3GuardianShieldSpawnTime = System.currentTimeMillis() + 16_400 - } - - "§c[BOSS] The Professor§r§f: Even if you took my barrier down, I can still fight." -> { - floor3GuardianShield = false - } - - "§c[BOSS] The Professor§r§f: Oh? You found my Guardians' one weakness?" -> { - floor3Professor = true - floor3ProfessorSpawnTime = System.currentTimeMillis() + 10_300 - } - - "§c[BOSS] The Professor§r§f: I see. You have forced me to use my ultimate technique." -> { - floor3Professor = false - - floor3ProfessorGuardianPrepare = true - floor3ProfessorGuardianPrepareSpawnTime = System.currentTimeMillis() + 10_500 - } - - "§c[BOSS] The Professor§r§f: The process is irreversible, but I'll be stronger than a Wither now!" -> { - floor3ProfessorGuardian = true - } - - "§c[BOSS] The Professor§r§f: What?! My Guardian power is unbeatable!" -> { - floor3ProfessorGuardian = false - } - - - //F5 - "§c[BOSS] Livid§r§f: This Orb you see, is Thorn, or what is left of him." -> { - floor5lividEntity = DungeonLividFinder.livid - floor5lividEntitySpawnTime = System.currentTimeMillis() + 13_000 - } - - //F6 - "§c[BOSS] Sadan§r§f: ENOUGH!" -> { - floor6Giants = true - floor6GiantsSpawnTime = System.currentTimeMillis() + 7_400 - } - - "§c[BOSS] Sadan§r§f: You did it. I understand now, you have earned my respect." -> { - floor6Giants = false - floor6Sadan = true - floor6SadanSpawnTime = System.currentTimeMillis() + 32_500 - } - - "§c[BOSS] Sadan§r§f: NOOOOOOOOO!!! THIS IS IMPOSSIBLE!!" -> { - floor6Sadan = false - } - } - - if (message.matchRegex("§c\\[BOSS] (.*) Livid§r§f: Impossible! How did you figure out which one I was\\?!")) { - floor5lividEntity = null - } - } - } - - fun handleNewEntity(entity: Entity) { - if (LorenzUtils.inDungeons && floor3ProfessorGuardian && entity is EntityGuardian && floor3ProfessorGuardianEntity == null) { - - floor3ProfessorGuardianEntity = entity - floor3ProfessorGuardianPrepare = false - - } - } - - private fun findGuardians() { - guardians.clear() - - for (entity in EntityUtils.getEntities()) { - //F3 - if (entity.hasMaxHealth(1_000_000, true) || entity.hasMaxHealth(1_200_000, true)) { - guardians.add(entity) - } - - //M3 - if (entity.hasMaxHealth(120_000_000, true) || entity.hasMaxHealth(240_000_000, true)) { - guardians.add(entity) - } - } - } -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/OldDamage.kt b/src/main/java/at/hannibal2/skyhanni/features/damageindicator/OldDamage.kt deleted file mode 100644 index 26c0a751a..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/damageindicator/OldDamage.kt +++ /dev/null @@ -1,3 +0,0 @@ -package at.hannibal2.skyhanni.features.damageindicator - -class OldDamage(val time: Long, val damage: Long, val healing: Long) \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonBossHideDamageSplash.kt b/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonBossHideDamageSplash.kt index c14e717a5..a298aff40 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonBossHideDamageSplash.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonBossHideDamageSplash.kt @@ -1,7 +1,7 @@ package at.hannibal2.skyhanni.features.dungeon import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.features.damageindicator.DamageIndicatorManager +import at.hannibal2.skyhanni.features.combat.damageindicator.DamageIndicatorManager import at.hannibal2.skyhanni.utils.LorenzUtils import net.minecraft.entity.EntityLivingBase import net.minecraftforge.client.event.RenderLivingEvent diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasure.kt b/src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasure.kt new file mode 100644 index 000000000..e4670ffcc --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasure.kt @@ -0,0 +1,19 @@ +package at.hannibal2.skyhanni.features.event.jerry.frozentreasure + +enum class FrozenTreasure( + val internalName: String, + val displayName: String, + val defaultAmount: Int, + val iceMultiplier: Int = 0, +) { + WHITE_GIFT("WHITE_GIFT", "§fWhite Gift", 1), + GREEN_GIFT("GREEN_GIFT", "§aGreen Gift", 1), + RED_GIFT("RED_GIFT", "§9§cRed Gift", 1), + PACKED_ICE("PACKED_ICE", "§fPacked Ice", 32, 9), + ENCHANTED_ICE("ENCHANTED_ICE", "§aEnchanted Ice", 9, 160), // wiki says 1-16 so assuming 9 + ENCHANTED_PACKED_ICE("ENCHANTED_PACKED_ICE", "§9Enchanted Packed Ice", 1, 25600), + ICE_BAIT("ICE_BAIT", "§aIce Bait", 16), + GLOWY_CHUM_BAIT("GLOWY_CHUM_BAIT", "§aGlowy Chum Bait", 16), + GLACIAL_FRAGMENT("GLACIAL_FRAGMENT", "§5Glacial Fragment", 1), + GLACIAL_TALISMAN("GLACIAL_TALISMAN", "§fGlacial Talisman", 1) +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasureTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasureTracker.kt new file mode 100644 index 000000000..034761654 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/event/jerry/frozentreasure/FrozenTreasureTracker.kt @@ -0,0 +1,167 @@ +package at.hannibal2.skyhanni.features.event.jerry.frozentreasure + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.config.Storage +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.PreProfileSwitchEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.editCopy +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.concurrent.fixedRateTimer + +class FrozenTreasureTracker { + private val config get() = SkyHanniMod.feature.event.winter.frozenTreasureTracker + private var display = emptyList>() + private var estimatedIce = 0L + private var lastEstimatedIce = 0L + private var icePerSecond = mutableListOf() + private var icePerHour = 0 + private var stoppedChecks = 0 + private var compactPattern = "COMPACT! You found an Enchanted Ice!".toPattern() + + init { + fixedRateTimer(name = "skyhanni-frozen-treasure-tracker", period = 1000) { + if (!onJerryWorkshop()) return@fixedRateTimer + calculateIcePerHour() + } + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + icePerHour = 0 + stoppedChecks = 0 + icePerSecond = mutableListOf() + saveAndUpdate() + } + + private fun calculateIcePerHour() { + val difference = estimatedIce - lastEstimatedIce + lastEstimatedIce = estimatedIce + + if (difference == estimatedIce) return + + + if (difference == 0L) { + if (icePerSecond.isEmpty()) return + stoppedChecks += 1 + } else { + if (stoppedChecks > 60) { + stoppedChecks = 0 + icePerSecond.clear() + icePerHour = 0 + } + while (stoppedChecks > 0) { + stoppedChecks -= 1 + icePerSecond.add(0) + } + icePerSecond.add(difference) + val listCopy = icePerSecond + while (listCopy.size > 1200) listCopy.removeAt(0) + icePerSecond = listCopy + } + icePerHour = (icePerSecond.average() * 3600).toInt() + } + + private fun formatDisplay(map: List>): List> { + val newList = mutableListOf>() + for (index in config.textFormat) { + newList.add(map[index]) + } + return newList + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!ProfileStorageData.loaded) return + if (!onJerryWorkshop()) return + + val message = event.message.removeColor().trim() + val storage = ProfileStorageData.profileSpecific?.frozenTreasureTracker ?: return + + compactPattern.matchMatcher(message) { + storage.compactProcs += 1 + saveAndUpdate() + if (config.hideMessages) event.blockedReason = "frozen treasure tracker" + } + + for (treasure in FrozenTreasure.entries) { + if ("FROZEN TREASURE! You found ${treasure.displayName.removeColor()}!".toRegex().matches(message)) { + storage.treasuresMined += 1 + val old = storage.treasureCount[treasure] ?: 0 + storage.treasureCount = storage.treasureCount.editCopy { this[treasure] = old + 1 } + saveAndUpdate() + if (config.hideMessages) event.blockedReason = "frozen treasure tracker" + } + } + } + + @SubscribeEvent + fun onPreProfileSwitch(event: PreProfileSwitchEvent) { + display = emptyList() + } + + private fun drawTreasureDisplay(storage: Storage.ProfileSpecific.FrozenTreasureTracker) = buildList> { + addAsSingletonList("§1§lFrozen Treasure Tracker") + addAsSingletonList("§6${formatNumber(storage.treasuresMined)} Treasures Mined") + addAsSingletonList("§3${formatNumber(estimatedIce)} Total Ice") + addAsSingletonList("§3${formatNumber(icePerHour)} Ice/hr") + addAsSingletonList("§8${formatNumber(storage.treasuresMined)} Compact Procs") + addAsSingletonList("") + + for (treasure in FrozenTreasure.entries) { + val count = (storage.treasureCount[treasure] ?: 0) * if (config.showAsDrops) treasure.defaultAmount else 1 + addAsSingletonList("§b${formatNumber(count)} ${treasure.displayName}") + } + addAsSingletonList("") + } + + fun formatNumber(amount: Number): String { + if (amount is Int) return amount.addSeparators() + if (amount is Long) return NumberUtil.format(amount) + return "$amount" + } + + private fun saveAndUpdate() { + val storage = ProfileStorageData.profileSpecific?.frozenTreasureTracker ?: return + calculateIce(storage) + display = formatDisplay(drawTreasureDisplay(storage)) + } + + private fun calculateIce(storage: Storage.ProfileSpecific.FrozenTreasureTracker) { + estimatedIce = 0 + estimatedIce += storage.compactProcs * 160 + for (treasure in FrozenTreasure.entries) { + val amount = storage.treasureCount[treasure] ?: 0 + estimatedIce += amount * treasure.defaultAmount * treasure.iceMultiplier + } + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!config.enabled) return + if (!onJerryWorkshop()) return + if (config.onlyInCave && !inGlacialCave()) return + config.position.renderStringsAndItems(display, posLabel = "Frozen Treasure Tracker") + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.frozenTreasureTracker", "event.winter.frozenTreasureTracker") + } + + private fun onJerryWorkshop() = LorenzUtils.inIsland(IslandType.WINTER) + + private fun inGlacialCave() = onJerryWorkshop() && ScoreboardData.sidebarLinesFormatted.contains(" §7⏣ §3Glacial Cave") +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/fishing/ChumBucketHider.kt b/src/main/java/at/hannibal2/skyhanni/features/fishing/ChumBucketHider.kt new file mode 100644 index 000000000..24506a22b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/fishing/ChumBucketHider.kt @@ -0,0 +1,83 @@ +package at.hannibal2.skyhanni.features.fishing + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.CheckRenderEntityEvent +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.utils.ItemUtils.name +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.onToggle +import at.hannibal2.skyhanni.utils.getLorenzVec +import net.minecraft.entity.Entity +import net.minecraft.entity.item.EntityArmorStand +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ChumBucketHider { + private val config get() = SkyHanniMod.feature.fishing.chumBucketHider + private val titleEntity = mutableListOf() + private val hiddenEntities = mutableListOf() + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + reset() + } + + @SubscribeEvent + fun onCheckRender(event: CheckRenderEntityEvent<*>) { + if (!LorenzUtils.inSkyBlock) return + if (!config.enabled.get()) return + + val entity = event.entity + if (entity !is EntityArmorStand) return + + if (entity in hiddenEntities) { + event.isCanceled = true + return + } + + val name = entity.name + + // First text line + if (name.endsWith("'s Chum Bucket") || name.endsWith("'s Chumcap Bucket")) { + if (name.contains(LorenzUtils.getPlayerName()) && !config.hideOwn.get()) return + titleEntity.add(entity) + hiddenEntities.add(entity) + event.isCanceled = true + return + } + + // Second text line + if (name.contains("/10 §aChums")) { + val entityLocation = entity.getLorenzVec() + for (title in titleEntity) { + if (entityLocation.equalsIgnoreY(title.getLorenzVec())) { + hiddenEntities.add(entity) + event.isCanceled = true + return + } + } + } + + // Chum Bucket + if (config.hideBucket.get() && entity.inventory.any { it != null && (it.name == "§fEmpty Chum Bucket" || it.name == "§aEmpty Chumcap Bucket") }) { + val entityLocation = entity.getLorenzVec() + for (title in titleEntity) { + if (entityLocation.equalsIgnoreY(title.getLorenzVec())) { + hiddenEntities.add(entity) + event.isCanceled = true + return + } + } + } + } + + @SubscribeEvent + fun onConfigLoad(event: ConfigLoadEvent) { + onToggle(config.enabled, config.hideBucket, config.hideOwn) { reset() } + } + + private fun reset() { + titleEntity.clear() + hiddenEntities.clear() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/fishing/SeaCreatureFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/fishing/SeaCreatureFeatures.kt index 06e8facbf..da0d9a950 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/fishing/SeaCreatureFeatures.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/fishing/SeaCreatureFeatures.kt @@ -8,7 +8,7 @@ import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.events.RenderEntityOutlineEvent import at.hannibal2.skyhanni.events.SeaCreatureFishEvent import at.hannibal2.skyhanni.events.withAlpha -import at.hannibal2.skyhanni.features.damageindicator.DamageIndicatorManager +import at.hannibal2.skyhanni.features.combat.damageindicator.DamageIndicatorManager import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper import at.hannibal2.skyhanni.utils.EntityUtils.hasMaxHealth import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith diff --git a/src/main/java/at/hannibal2/skyhanni/features/fishing/ThunderSparksHighlight.kt b/src/main/java/at/hannibal2/skyhanni/features/fishing/ThunderSparksHighlight.kt new file mode 100644 index 000000000..bab0d5ae1 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/fishing/ThunderSparksHighlight.kt @@ -0,0 +1,77 @@ +package at.hannibal2.skyhanni.features.fishing + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.test.GriffinUtils.drawWaypointFilled +import at.hannibal2.skyhanni.utils.BlockUtils.getBlockAt +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.EntityUtils.hasSkullTexture +import at.hannibal2.skyhanni.utils.LocationUtils +import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.drawString +import at.hannibal2.skyhanni.utils.SpecialColour +import at.hannibal2.skyhanni.utils.getLorenzVec +import net.minecraft.entity.item.EntityArmorStand +import net.minecraft.init.Blocks +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.awt.Color + +class ThunderSparksHighlight { + + private val config get() = SkyHanniMod.feature.fishing.thunderSpark + private val texture = + "ewogICJ0aW1lc3RhbXAiIDogMTY0MzUwNDM3MjI1NiwKICAicHJvZmlsZUlkIiA6ICI2MzMyMDgwZTY3YTI0Y2MxYjE3ZGJhNzZmM2MwMGYxZCIsCiAgInByb2ZpbGVOYW1lIiA6ICJUZWFtSHlkcmEiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2IzMzI4ZDNlOWQ3MTA0MjAzMjI1NTViMTcyMzkzMDdmMTIyNzBhZGY4MWJmNjNhZmM1MGZhYTA0YjVjMDZlMSIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9" + private val sparks = mutableListOf() + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + + + EntityUtils.getEntities().filter { + it !in sparks && it.hasSkullTexture(texture) + }.forEach { sparks.add(it) } + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!isEnabled()) return + + val special = config.color + val color = Color(SpecialColour.specialToChromaRGB(special), true) + + val playerLocation = LocationUtils.playerLocation() + for (spark in sparks) { + if (spark.isDead) continue + val sparkLocation = spark.getLorenzVec() + val block = sparkLocation.getBlockAt() + val seeThroughBlocks = + sparkLocation.distanceToPlayer() < 6 && (block == Blocks.flowing_lava || block == Blocks.lava) + event.drawWaypointFilled( + sparkLocation.add(-0.5, 0.0, -0.5), color, extraSize = -0.25, seeThroughBlocks = seeThroughBlocks + ) + if (sparkLocation.distance(playerLocation) < 10) { + event.drawString(sparkLocation.add(0.0, 1.5, 0.0), "Thunder Spark", seeThroughBlocks = seeThroughBlocks) + } + } + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + sparks.clear() + } + + private fun isEnabled(): Boolean { + return LorenzUtils.inSkyBlock && config.highlight + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(3, "fishing.thunderSparkHighlight", "fishing.thunderSpark.highlight") + event.move(3, "fishing.thunderSparkColor", "fishing.thunderSpark.color") + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/ChestValue.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/ChestValue.kt new file mode 100644 index 000000000..52d1627d9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/ChestValue.kt @@ -0,0 +1,277 @@ +package at.hannibal2.skyhanni.features.inventory + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryOpenEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.features.misc.items.EstimatedItemValue +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.addButton +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import at.hannibal2.skyhanni.utils.SpecialColour +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.renderables.Renderable +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.init.Items +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.awt.Color + +class ChestValue { + + private val config get() = SkyHanniMod.feature.inventory.chestValueConfig + private var display = emptyList>() + private val chestItems = mutableMapOf() + private val inInventory get() = isValidStorage() + + @SubscribeEvent + fun onBackgroundDraw(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { + if (!isEnabled()) return + if (InventoryUtils.openInventoryName() == "") return + if (inInventory) { + config.position.renderStringsAndItems( + display, + extraSpace = -1, + itemScale = 1.3, + posLabel = "Estimated Chest Value" + ) + } + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + if (event.isMod(5)) { + update() + } + } + + @SubscribeEvent + fun onInventoryOpen(event: InventoryOpenEvent) { + if (!isEnabled()) return + if (inInventory) { + update() + } + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + chestItems.clear() + Renderable.list.clear() + } + + @SubscribeEvent(priority = EventPriority.LOW) + fun onDrawBackground(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!isEnabled()) return + if (!config.enableHighlight) return + if (inInventory) { + for ((_, indexes) in Renderable.list) { + for (slot in InventoryUtils.getItemsInOpenChest()) { + if (indexes.contains(slot.slotIndex)) { + slot highlight Color(SpecialColour.specialToChromaRGB(config.highlightColor), true) + } + } + } + } + } + + private fun update() { + display = drawDisplay() + } + + private fun drawDisplay(): List> { + val newDisplay = mutableListOf>() + + init() + + if (chestItems.isEmpty()) return newDisplay + + addList(newDisplay) + addButton(newDisplay) + + return newDisplay + } + + private fun addList(newDisplay: MutableList>) { + val sortedList = sortedList() + var totalPrice = 0.0 + var rendered = 0 + val amountShowing = if (config.itemToShow > sortedList.size) sortedList.size else config.itemToShow + newDisplay.addAsSingletonList("§7Estimated Chest Value: §o(Showing $amountShowing of ${sortedList.size} items)") + for ((index, amount, stack, total, tips) in sortedList) { + totalPrice += total + if (rendered >= config.itemToShow) continue + if (total < config.hideBelow) continue + val textAmount = " §7x$amount:" + val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(textAmount) + val name = "${stack.displayName.reduceStringLength((config.nameLength - width), ' ')} $textAmount" + val price = "§b${(total).formatPrice()}" + val text = if (config.alignedDisplay) + "$name $price" + else + "${stack.displayName} §7x$amount: §b${total.formatPrice()}" + newDisplay.add(buildList { + val renderable = Renderable.hoverTips( + text, + tips, + stack = stack, + indexes = index + ) + add(" §7- ") + if (config.showStacks) add(stack) + add(renderable) + }) + rendered++ + } + newDisplay.addAsSingletonList("§6Total value : §b${totalPrice.formatPrice()}") + } + + private fun sortedList(): MutableList { + return when (config.sortingType) { + 0 -> chestItems.values.sortedByDescending { it.total } + 1 -> chestItems.values.sortedBy { it.total } + else -> chestItems.values.sortedByDescending { it.total } + }.toMutableList() + } + + private fun addButton(newDisplay: MutableList>) { + newDisplay.addButton("§7Sorted By: ", + getName = SortType.entries[config.sortingType].longName, + onChange = { + config.sortingType = (config.sortingType + 1) % 2 + update() + }) + + newDisplay.addButton("§7Value format: ", + getName = FormatType.entries[config.formatType].type, + onChange = { + config.formatType = (config.formatType + 1) % 2 + update() + }) + + newDisplay.addButton("§7Display Type: ", + getName = DisplayType.entries[if (config.alignedDisplay) 1 else 0].type, + onChange = { + config.alignedDisplay = !config.alignedDisplay + update() + }) + } + + private fun init() { + if (inInventory) { + val isMinion = InventoryUtils.openInventoryName().contains(" Minion ") + val slots = InventoryUtils.getItemsInOpenChest().filter { + it.hasStack && it.inventory != Minecraft.getMinecraft().thePlayer.inventory && (!isMinion || it.slotNumber % 9 != 1) + } + val stacks = buildMap { + slots.forEach { + put(it.slotIndex, it.stack) + } + } + chestItems.clear() + for ((i, stack) in stacks) { + val internalName = stack.getInternalNameOrNull() ?: continue + if (internalName.getItemStackOrNull() == null) continue + val list = mutableListOf() + val pair = EstimatedItemValue.getEstimatedItemPrice(stack, list) + var (total, _) = pair + if (stack.item == Items.enchanted_book) + total /= 2 + list.add("§aTotal: §6§l${total.formatPrice()}") + if (total == 0.0) continue + val item = chestItems.getOrPut(internalName) { + Item(mutableListOf(), 0, stack, 0.0, list) + } + item.index.add(i) + item.amount += stack.stackSize + item.total += total * stack.stackSize + } + } + } + + private fun Double.formatPrice(): String { + return when (config.formatType) { + 0 -> if (this > 1_000_000_000) NumberUtil.format(this, true) else NumberUtil.format(this) + 1 -> this.addSeparators() + else -> "0" + } + } + + enum class SortType(val shortName: String, val longName: String) { + PRICE_DESC("Price D", "Price Descending"), + PRICE_ASC("Price A", "Price Ascending") + ; + } + + enum class FormatType(val type: String) { + SHORT("Formatted"), + LONG("Unformatted") + ; + } + + enum class DisplayType(val type: String) { + NORMAL("Normal"), + COMPACT("Aligned") + } + + private fun isValidStorage(): Boolean { + val name = InventoryUtils.openInventoryName().removeColor() + if (Minecraft.getMinecraft().currentScreen !is GuiChest) return false + + if ((name.contains("Backpack") && name.contains("Slot #") || name.startsWith("Ender Chest (")) && + !InventoryUtils.isNeuStorageEnabled.getValue() + ) { + return true + } + + val inMinion = name.contains("Minion") && !name.contains("Recipe") && + LorenzUtils.skyBlockIsland == IslandType.PRIVATE_ISLAND + return name == "Chest" || name == "Large Chest" || inMinion || name == "Personal Vault" + } + + private fun String.reduceStringLength(targetLength: Int, char: Char): String { + val mc = Minecraft.getMinecraft() + val spaceWidth = mc.fontRendererObj.getCharWidth(char) + + var currentString = this + var currentLength = mc.fontRendererObj.getStringWidth(currentString) + + while (currentLength > targetLength) { + currentString = currentString.dropLast(1) + currentLength = mc.fontRendererObj.getStringWidth(currentString) + } + + val difference = targetLength - currentLength + + if (difference > 0) { + val numSpacesToAdd = difference / spaceWidth + val spaces = " ".repeat(numSpacesToAdd) + return currentString + spaces + } + + return currentString + } + + data class Item( + val index: MutableList, + var amount: Int, + val stack: ItemStack, + var total: Double, + val tips: MutableList + ) + + private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/HarpFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/HarpFeatures.kt new file mode 100644 index 000000000..e793cd615 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/HarpFeatures.kt @@ -0,0 +1,78 @@ +package at.hannibal2.skyhanni.features.inventory + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.events.RenderItemTipEvent +import at.hannibal2.skyhanni.utils.InventoryUtils.openInventoryName +import at.hannibal2.skyhanni.utils.KeyboardManager.isKeyHeld +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.item.Item +import net.minecraftforge.client.event.GuiScreenEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.lwjgl.input.Keyboard +import kotlin.time.Duration.Companion.milliseconds + +// Delaying key presses by 300ms comes from NotEnoughUpdates +class HarpFeatures { + private val config get() = SkyHanniMod.feature.inventory.helper.harp + private var lastClick = SimpleTimeMark.farPast() + + private val keys = listOf( + Keyboard.KEY_1, + Keyboard.KEY_2, + Keyboard.KEY_3, + Keyboard.KEY_4, + Keyboard.KEY_5, + Keyboard.KEY_6, + Keyboard.KEY_7 + ) + + private val buttonColors = listOf('d', 'e', 'a', '2', '5', '9', 'b') + + @SubscribeEvent + fun onGui(event: GuiScreenEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.keybinds) return + if (!openInventoryName().startsWith("Harp")) return + val chest = event.gui as? GuiChest ?: return + + for (key in keys) { + if (key.isKeyHeld()) { + if (lastClick.passedSince() > 200.milliseconds) { + Minecraft.getMinecraft().playerController.windowClick( + chest.inventorySlots.windowId, + 35 + key, + 2, + 3, + Minecraft.getMinecraft().thePlayer + ) // middle clicks > left clicks + lastClick = SimpleTimeMark.now() + } + break + } + } + } + + @SubscribeEvent + fun onRenderItemTip(event: RenderItemTipEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.showNumbers) return + if (!openInventoryName().startsWith("Harp")) return + if (Item.getIdFromItem(event.stack.item) != 159) return // Stained hardened clay item id = 159 + + // Example: §9| §7Click! will select the 9 + val index = buttonColors.indexOfFirst { it == event.stack.displayName[1] } + if (index == -1) return // this should never happen unless there's an update + + event.stackTip = (index + 1).toString() + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.harpKeybinds", "inventory.helper.harp.keybinds") + event.move(2, "misc.harpNumbers", "inventory.helper.harp.showNumbers") + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/Relay.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/Relay.kt new file mode 100644 index 000000000..f634fffa9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/Relay.kt @@ -0,0 +1,48 @@ +package at.hannibal2.skyhanni.features.inventory.tiarelay + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.utils.LorenzVec + +enum class Relay( + val relayName: String, + val waypoint: LorenzVec, + val island: IslandType, + val chatMessage: String +) { + RELAY_1( + "1st Relay", LorenzVec(143.5, 108.0, 93.0), IslandType.HUB, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe first relay is on a branch of the large tree on the north-east of the fairy pond." + ), + RELAY_2( + "2nd Relay", LorenzVec(-246.5, 123.0, 55.5), IslandType.HUB, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay is in the castle ruins!" + ), + RELAY_3( + "3rd Relay", LorenzVec(128.5, 232.0, 200.5), IslandType.DWARVEN_MINES, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay is in the §bRoyal Palace §rwithin the Dwarven Mines." + ), + RELAY_4( + "4th Relay", LorenzVec(-560, 164, -287), IslandType.THE_END, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay is on the highest spike of §dThe End§r." + ), + RELAY_5( + "5th Relay", LorenzVec(-375, 207, -799), IslandType.CRIMSON_ISLE, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay was placed by our consultant, Odger." + ), + RELAY_6( + "6th Relay", LorenzVec(-69, 157, -879), IslandType.CRIMSON_ISLE, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rScarleton itself has one of the most robust connection to the 9f™ Network." + ), + RELAY_7( + "7th Relay", LorenzVec(93, 86, 187), IslandType.HUB, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay is on top of the shack next to the shady inn right here close to the pond." + ), + RELAY_8( + "8th Relay", LorenzVec(0, 146, -75), IslandType.DUNGEON_HUB, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay is on top of a statue in the dungeon hub." + ), + RELAY_9( + "9th Relay", LorenzVec(-19.0, 88.5, -91.0), IslandType.HUB, + "§e[NPC] §dTia the Fairy§f: §b✆ §f§rThe next relay is on top of the Auction House." + ), +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayHelper.kt new file mode 100644 index 000000000..7c50ec1a0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayHelper.kt @@ -0,0 +1,146 @@ +package at.hannibal2.skyhanni.features.inventory.tiarelay + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.PlaySoundEvent +import at.hannibal2.skyhanni.events.RenderInventoryItemTipEvent +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.sorted +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class TiaRelayHelper { + private val config get() = SkyHanniMod.feature.inventory.helper.tiaRelay + private var inInventory = false + + private var lastClickSlot = 0 + private var lastClickTime = 0L + private var sounds = mutableMapOf() + + private var resultDisplay = mutableMapOf() + + @SubscribeEvent + fun onSoundPlay(event: PlaySoundEvent) { + if (!LorenzUtils.inSkyBlock) return + val soundName = event.soundName + + if (config.tiaRelayMute && soundName == "mob.wolf.whine") { + event.isCanceled = true + } + + if (!config.soundHelper) return + if (!inInventory) return + + val distance = event.distanceToPlayer + if (distance >= 2) return + + if (lastClickSlot == 0) return + val duration = System.currentTimeMillis() - lastClickTime + if (duration > 1_000) return + if (sounds.contains(lastClickSlot)) return + + sounds[lastClickSlot] = Sound(soundName, event.pitch) + + lastClickSlot = 0 + + tryResult() + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.soundHelper) return + + if (event.repeatSeconds(1)) { + if (InventoryUtils.openInventoryName().contains("Network Relay")) { + inInventory = true + } else { + inInventory = false + sounds.clear() + resultDisplay.clear() + } + } + } + + private fun tryResult() { + if (sounds.size < 4) return + + val name = sounds.values.first().name + for (sound in sounds.toMutableMap()) { + if (sound.value.name != name) { + LorenzUtils.chat("§c[SkyHanni] Tia Relay Helper error: Too much background noise! Please try again.") + sounds.clear() + return + } + } + + val pitchMap = mutableMapOf() + for (sound in sounds) { + pitchMap[sound.key] = sound.value.pitch + } + sounds.clear() + resultDisplay.clear() + + var i = 1 + for (entry in pitchMap.sorted()) { + resultDisplay[entry.key] = i + i++ + } + } + + @SubscribeEvent + fun onRenderItemTip(event: RenderInventoryItemTipEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.soundHelper) return + if (!inInventory) return + + val slot = event.slot + val stack = slot.stack + + val slotNumber = slot.slotNumber + + val position = resultDisplay.getOrDefault(slotNumber, null) + if (position != null) { + if (stack.getLore().any { it.contains("Done!") }) { + resultDisplay.clear() + return + } + event.stackTip = "#$position" + return + } + + if (!sounds.contains(slotNumber) && stack.getLore().any { it.contains("Hear!") }) { + event.stackTip = "Hear!" + event.offsetX = 5 + event.offsetY = -5 + return + } + } + + @SubscribeEvent + fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.soundHelper) return + if (!inInventory) return + + // only listen to right clicks + if (event.clickedButton != 1) return + + lastClickSlot = event.slotId + lastClickTime = System.currentTimeMillis() + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.tiaRelayMute", "inventory.helper.tiaRelay.tiaRelayMute") + event.move(2, "misc.tiaRelayHelper", "inventory.helper.tiaRelay.soundHelper") + + event.move(2, "misc.tiaRelayNextWaypoint", "inventory.helper.tiaRelay.nextWaypoint") + event.move(2, "misc.tiaRelayAllWaypoints", "inventory.helper.tiaRelay.allWaypoints") + } + + class Sound(val name: String, val pitch: Float) +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayWaypoints.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayWaypoints.kt new file mode 100644 index 000000000..5b9e8fa6f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/tiarelay/TiaRelayWaypoints.kt @@ -0,0 +1,63 @@ +package at.hannibal2.skyhanni.features.inventory.tiarelay + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.test.GriffinUtils.drawWaypointFilled +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class TiaRelayWaypoints { + private val config get() = SkyHanniMod.feature.inventory.helper.tiaRelay + private var waypoint: LorenzVec? = null + private var waypointName: String? = null + private var island = IslandType.NONE + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!config.nextWaypoint) return + + val message = event.message + for (relay in Relay.entries) { + if (relay.chatMessage == message) { + waypoint = relay.waypoint + waypointName = relay.relayName + island = relay.island + return + } + } + + if (message == "§aYou completed the maintenance on the relay!") { + waypoint = null + island = IslandType.NONE + } + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!LorenzUtils.inSkyBlock) return + + if (config.allWaypoints) { + for (relay in Relay.entries) { + if (relay.island == LorenzUtils.skyBlockIsland) { + event.drawWaypointFilled(relay.waypoint, LorenzColor.LIGHT_PURPLE.toColor()) + event.drawDynamicText(relay.waypoint, "§d" + relay.relayName, 1.5) + } + } + return + } + + if (!config.nextWaypoint) return + if (LorenzUtils.skyBlockIsland != island) return + + waypoint?.let { + event.drawWaypointFilled(it, LorenzColor.LIGHT_PURPLE.toColor()) + event.drawDynamicText(it, "§d" + waypointName!!, 1.5) + } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/itemabilities/ChickenHeadTimer.kt b/src/main/java/at/hannibal2/skyhanni/features/itemabilities/ChickenHeadTimer.kt new file mode 100644 index 000000000..639784234 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/itemabilities/ChickenHeadTimer.kt @@ -0,0 +1,75 @@ +package at.hannibal2.skyhanni.features.itemabilities + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.name +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.renderString +import at.hannibal2.skyhanni.utils.TimeUtils +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ChickenHeadTimer { + private var hasChickenHead = false + private var lastTime = 0L + private val config get() = SkyHanniMod.feature.itemAbilities.chickenHead + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + if (!event.isMod(5)) return + + val itemStack = InventoryUtils.getArmor()[3] + val name = itemStack?.name ?: "" + hasChickenHead = name.contains("Chicken Head") + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + lastTime = System.currentTimeMillis() + } + + @SubscribeEvent + fun onChatMessage(event: LorenzChatEvent) { + if (!isEnabled()) return + if (!hasChickenHead) return + if (event.message == "§aYou laid an egg!") { + lastTime = System.currentTimeMillis() + if (config.hideChat) { + event.blockedReason = "chicken_head_timer" + } + } + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled()) return + if (!hasChickenHead) return + + val sinceLastTime = System.currentTimeMillis() - lastTime + val cooldown = 5_000 + val remainingTime = cooldown - sinceLastTime + + val displayText = if (remainingTime < 0) { + "Chicken Head Timer: §aNow" + } else { + val formatDuration = TimeUtils.formatDuration(remainingTime) + "Chicken Head Timer: §b$formatDuration" + } + + config.position.renderString(displayText, posLabel = "Chicken Head Timer") + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.chickenHeadTimerHideChat", "itemAbilities.chickenHead.hideChat") + event.move(2, "misc.chickenHeadTimerPosition", "itemAbilities.chickenHead.position") + event.move(2, "misc.chickenHeadTimerDisplay", "itemAbilities.chickenHead.displayTimer") + } + + fun isEnabled() = LorenzUtils.inSkyBlock && config.displayTimer +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderChestReward.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderChestReward.kt new file mode 100644 index 000000000..656397b94 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderChestReward.kt @@ -0,0 +1,148 @@ +package at.hannibal2.skyhanni.features.mining.powdertracker + +import java.util.regex.Pattern + +enum class PowderChestReward(val displayName: String, val pattern: Pattern) { + + + MITHRIL_POWDER("§aMithril Powder", "§aYou received §r§b[+](?.*) §r§aMithril Powder.".toPattern()), + GEMSTONE_POWDER("§dGemstone Powder", "§aYou received §r§b[+](?.*) §r§aGemstone Powder.".toPattern()), + + ROUGH_RUBY_GEMSTONE( + "§fRough Ruby Gemstone", + "§aYou received §r§f(?.*) §r§f❤ §r§fRough Ruby Gemstone§r§a.".toPattern() + ), + FLAWED_RUBY_GEMSTONE( + "§aFlawed Sapphire Gemstone", + "§aYou received §r§f(?.*) §r§a❤ §r§aFlawed RubyGemstone§r§a.".toPattern() + ), + FINE_RUBY_GEMSTONE( + "§9Fine Ruby Gemstone", + "§aYou received §r§f(?.*) §r§9❤ §r§9Fine Ruby Gemstone§r§a.".toPattern() + ), + FLAWLESS_RUBY_GEMSTONE( + "§5Flawless Ruby Gemstone", + "§aYou received §r§f(?.*) §r§9❤ §r§5Flawless Ruby Gemstone§r§a.".toPattern() + ), + + ROUGH_SAPPHIRE_GEMSTONE( + "§fRough Sapphire Gemstone", + "§aYou received §r§f(?.*) §r§f✎ §r§fRough Sapphire Gemstone§r§a.".toPattern() + ), + FLAWED_SAPPHIRE_GEMSTONE( + "§aFlawed Sapphire Gemstone", + "§aYou received §r§f(?.*) §r§a✎ §r§aFlawed Sapphire Gemstone§r§a.".toPattern() + ), + FINE_SAPPHIRE_GEMSTONE( + "§9Fine Sapphire Gemstone", + "§aYou received §r§f(?.*) §r§9✎ §r§9Fine Sapphire Gemstone§r§a.".toPattern() + ), + FLAWLESS_SAPPHIRE_GEMSTONE( + "§5Flawless Sapphire Gemstone", + "§aYou received §r§f(?.*) §r§9✎ §r§5Flawless Sapphire Gemstone§r§a.".toPattern() + ), + + ROUGH_AMBER_GEMSTONE( + "§fRough Amber Gemstone", + "§aYou received §r§f(?.*) §r§f⸕ §r§fRough Amber Gemstone§r§a.".toPattern() + ), + FLAWED_AMBER_GEMSTONE( + "§aFlawed Amber Gemstone", + "§aYou received §r§f(?.*) §r§a⸕ §r§aFlawed Amber Gemstone§r§a.".toPattern() + ), + FINE_AMBER_GEMSTONE( + "§9Fine Amber Gemstone", + "§aYou received §r§f(?.*) §r§9⸕ §r§9Fine Amber Gemstone§r§a.".toPattern() + ), + FLAWLESS_AMBER_GEMSTONE( + "§5Flawless Amber Gemstone", + "§aYou received §r§f(?.*) §r§9⸕ §r§5Flawless Amber Gemstone§r§a.".toPattern() + ), + + ROUGH_AMETHYST_GEMSTONE( + "§fRough Amethyst Gemstone", + "§aYou received §r§f(?.*) §r§f❈ §r§fRough Amethyst Gemstone§r§a.".toPattern() + ), + FLAWED_AMETHYST_GEMSTONE( + "§aFlawed Amethyst Gemstone", + "§aYou received §r§f(?.*) §r§a❈ §r§aFlawed Amethyst Gemstone§r§a.".toPattern() + ), + FINE_AMETHYST_GEMSTONE( + "§9Fine Amethyst Gemstone", + "§aYou received §r§f(?.*) §r§9❈ §r§9Fine Amethyst Gemstone§r§a.".toPattern() + ), + FLAWLESS_AMETHYST_GEMSTONE( + "§5Flawless Amethyst Gemstone", + "§aYou received §r§f(?.*) §r§9❈ §r§5Flawless Amethyst Gemstone§r§a.".toPattern() + ), + + ROUGH_JADE_GEMSTONE( + "§fRough Jade Gemstone", + "§aYou received §r§f(?.*) §r§f☘ §r§fRough Jade Gemstone§r§a.".toPattern() + ), + FLAWED_JADE_GEMSTONE( + "§aFlawed Jade Gemstone", + "§aYou received §r§f(?.*) §r§a☘ §r§aFlawed Jade Gemstone§r§a.".toPattern() + ), + FINE_JADE_GEMSTONE( + "§9Fine Jade Gemstone", + "§aYou received §r§f(?.*) §r§9☘ §r§9Fine Jade Gemstone§r§a.".toPattern() + ), + FLAWLESS_JADE_GEMSTONE( + "§5Flawless Jade Gemstone", + "§aYou received §r§f(?.*) §r§9☘ §r§5Flawless Jade Gemstone§r§a.".toPattern() + ), + + ROUGH_TOPAZ_GEMSTONE( + "§fRough Topaz Gemstone", + "§aYou received §r§f(?.*) §r§f✧ §r§fRough Topaz Gemstone§r§a.".toPattern() + ), + FLAWED_TOPAZ_GEMSTONE( + "§aFlawed Topaz Gemstone", + "§aYou received §r§f(?.*) §r§a✧ §r§aFlawed Topaz Gemstone§r§a.".toPattern() + ), + FINE_TOPAZ_GEMSTONE( + "§9Fine Topaz Gemstone", + "§aYou received §r§f(?.*) §r§9✧ §r§9Fine Topaz Gemstone§r§a.".toPattern() + ), + FLAWLESS_TOPAZ_GEMSTONE( + "§5Flawless Topaz Gemstone", + "§aYou received §r§f(?.*) §r§9✧ §r§5Flawless Topaz Gemstone§r§a.".toPattern() + ), + + FTX_3070("§9FTX 3070", "§aYou received §r§f(?.*) §r§9FTX 3070§r§a.".toPattern()), + ELECTRON_TRANSIMTTER( + "§9Electron Transmitter", + "§aYou received §r§f(?.*) §r§9Electron Transmitter§r§a.".toPattern() + ), + ROBOTRON_REFLECTOR( + "§9Robotron Reflector", + "§aYou received §r§f(?.*) §r§9Robotron Reflector§r§a.".toPattern() + ), + SUPERLITE_MOTOR("§9Superlite Motor", "§aYou received §r§f(?.*) §r§9Superlite Motor§r§a.".toPattern()), + CONTROL_SWITCH("§9Control Switch", "§aYou received §r§f(?.*) §r§9Control Switch§r§a.".toPattern()), + SYNTHETIC_HEART("§9Synthetic Heart", "§aYou received §r§f(?.*) §r§9Synthetic Heart§r§a.".toPattern()), + + GOBLIN_EGG("§9Goblin Egg", "§aYou received §r§f(?.*) §r§9Goblin Egg§r§a.".toPattern()), + GREEN_GOBLIN_EGG( + "§aGreen Goblin Egg", + "§aYou received §r§f(?.*) §r§a§r§aGreen Goblin Egg§r§a.".toPattern() + ), + RED_GOBLIN_EGG("§cRed Goblin Egg", "§aYou received §r§f(?.*) §r§9§r§cRed Goblin Egg§r§a.".toPattern()), + YELLOW_GOBLIN_EGG( + "§eYellow Goblin Egg", + "§aYou received §r§f(?.*) §r§9§r§eYellow Goblin Egg§r§a.".toPattern() + ), + BLUE_GOBLIN_EGG("§3Blue Goblin Egg", "§aYou received §r§f(?.*) §r§9§r§3Blue Goblin Egg§r§a.".toPattern()), + + WISHING_COMPASS("§aWishing Compass", "§aYou received §r§f(?.*) §r§aWishing Compass§r§a.".toPattern()), + + SLUDGE_JUICE("§aSludge Juice", "§aYou received §r§f(?.*) §r§aSludge Juice§r§a.".toPattern()), + ASCENSION_ROPE("§9Ascension Rope", "§aYou received §r§f(?.*) §r§9Ascension Rope§r§a.".toPattern()), + TREASURITE("§5Treasurite", "§aYou received §r§f(?.*) §r§5Treasurite§r§a.".toPattern()), + JUNGLE_HEART("§6Jungle Heart", "§aYou received §r§f(?.*) §r§6Jungle Heart§r§a.".toPattern()), + PICKONIMBUS_2000("§5Pickonimbus 2000", "§aYou received §r§f(?.*) §r§5Pickonimbus 2000§r§a.".toPattern()), + YOGGIE("§aYoggie", "§aYou received §r§f(?.*) §r§aYoggie§r§a.".toPattern()), + PREHISTORIC_EGG("§fPrehistoric Egg", "§aYou received §r§f(?.*) §r§fPrehistoric Egg§r§a.".toPattern()), + OIL_BARREL("§aOil Barrel", "§aYou received §r§f(?.*) §r§aOil Barrel§r§a.".toPattern()), +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt new file mode 100644 index 000000000..bb715d9f5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt @@ -0,0 +1,366 @@ +package at.hannibal2.skyhanni.features.mining.powdertracker + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.config.Storage +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.addSelector +import at.hannibal2.skyhanni.utils.LorenzUtils.afterChange +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiInventory +import net.minecraft.entity.boss.BossStatus +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.concurrent.fixedRateTimer + +class PowderTracker { + + private val config get() = SkyHanniMod.feature.mining.powderTracker + private var display = emptyList>() + private val picked = "§6You have successfully picked the lock on this chest!".toPattern() + private val uncovered = "§aYou uncovered a treasure chest!".toPattern() + private val powderEvent = ".*§r§b§l2X POWDER STARTED!.*".toPattern() + private val powderEnded = ".*§r§b§l2X POWDER ENDED!.*".toPattern() + private val powderBossBar = "§e§lPASSIVE EVENT §b§l2X POWDER §e§lRUNNING FOR §a§l(?