summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorILike2WatchMemes <ilike2watchmemes@gmail.com>2024-09-20 23:32:10 +0200
committerGitHub <noreply@github.com>2024-09-20 23:32:10 +0200
commit19f23335cc4852e8f9c7d56036e7e89c52d230fe (patch)
treeb6003aa5aca4f36f77cc6fce69b34acfed6a752e /src/main
parent62d45680f79c47479fe7d16a651eb8c7081586fe (diff)
downloadskyhanni-19f23335cc4852e8f9c7d56036e7e89c52d230fe.tar.gz
skyhanni-19f23335cc4852e8f9c7d56036e7e89c52d230fe.tar.bz2
skyhanni-19f23335cc4852e8f9c7d56036e7e89c52d230fe.zip
Feature: Inventory - Experiments rework (#2171)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Co-authored-by: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com>
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentationTableConfig.java50
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsDryStreakConfig.java31
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsProfitTrackerConfig.java37
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java24
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableAPI.kt157
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableEnums.kt28
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsDryStreakDisplay.kt113
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt258
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/GuardianReminder.kt (renamed from src/main/java/at/hannibal2/skyhanni/features/inventory/experiments/GuardianReminder.kt)32
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/SuperpairExperimentInformationDisplay.kt272
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/SuperpairsClicksAlert.kt (renamed from src/main/java/at/hannibal2/skyhanni/features/inventory/SuperpairsClicksAlert.kt)6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/UltraRareBookAlert.kt (renamed from src/main/java/at/hannibal2/skyhanni/features/inventory/experiments/UltraRareBookAlert.kt)39
16 files changed, 1011 insertions, 74 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt
index 8eb47d394..b9f21ca22 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt
@@ -12,7 +12,7 @@ import com.google.gson.JsonPrimitive
object ConfigUpdaterMigrator {
val logger = LorenzLogger("ConfigMigration")
- const val CONFIG_VERSION = 58
+ const val CONFIG_VERSION = 59
fun JsonElement.at(chain: List<String>, init: Boolean): JsonElement? {
if (chain.isEmpty()) return this
if (this !is JsonObject) return null
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 3f2ddace6..9b85c2cf9 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
@@ -57,6 +57,7 @@ import at.hannibal2.skyhanni.features.garden.pests.PestFinder
import at.hannibal2.skyhanni.features.garden.pests.PestProfitTracker
import at.hannibal2.skyhanni.features.garden.visitor.GardenVisitorDropStatistics
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryStrayTracker
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentsProfitTracker
import at.hannibal2.skyhanni.features.mining.KingTalismanHelper
import at.hannibal2.skyhanni.features.mining.MineshaftPityDisplay
import at.hannibal2.skyhanni.features.mining.fossilexcavator.ExcavatorProfitTracker
@@ -277,6 +278,10 @@ object Commands {
"Resets the Pest Profit Tracker",
) { PestProfitTracker.resetCommand() }
registerCommand(
+ "shresetexperimentsprofittracker",
+ "Resets the Experiments Profit Tracker",
+ ) { ExperimentsProfitTracker.resetCommand() }
+ registerCommand(
"shresetmythologicalcreaturetracker",
"Resets the Mythological Creature Tracker",
) { MythologicalCreatureTracker.resetCommand() }
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java
index 5c3bf3170..062f29b30 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java
@@ -4,6 +4,7 @@ import at.hannibal2.skyhanni.config.FeatureToggle;
import at.hannibal2.skyhanni.config.HasLegacyId;
import at.hannibal2.skyhanni.config.features.inventory.chocolatefactory.ChocolateFactoryConfig;
import at.hannibal2.skyhanni.config.features.inventory.customwardrobe.CustomWardrobeConfig;
+import at.hannibal2.skyhanni.config.features.inventory.experimentationtable.ExperimentationTableConfig;
import at.hannibal2.skyhanni.config.features.inventory.helper.HelperConfig;
import at.hannibal2.skyhanni.config.features.itemability.ItemAbilityConfig;
import at.hannibal2.skyhanni.config.features.misc.EstimatedItemValueConfig;
@@ -39,6 +40,10 @@ public class InventoryConfig {
public BazaarConfig bazaar = new BazaarConfig();
@Expose
+ @Category(name = "Experimentation Table", desc = "QOL features for the Experimentation Table.")
+ public ExperimentationTableConfig experimentationTable = new ExperimentationTableConfig();
+
+ @Expose
@Category(name = "Enchant Parsing", desc = "Settings for SkyHanni's Enchant Parsing")
public EnchantParsingConfig enchantParsing = new EnchantParsingConfig();
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentationTableConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentationTableConfig.java
new file mode 100644
index 000000000..dfdad2482
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentationTableConfig.java
@@ -0,0 +1,50 @@
+package at.hannibal2.skyhanni.config.features.inventory.experimentationtable;
+
+import at.hannibal2.skyhanni.config.FeatureToggle;
+import at.hannibal2.skyhanni.config.core.config.Position;
+import com.google.gson.annotations.Expose;
+import io.github.notenoughupdates.moulconfig.annotations.Accordion;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigLink;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
+
+public class ExperimentationTableConfig {
+
+ @Expose
+ @ConfigOption(name = "Profit Tracker", desc = "")
+ @Accordion
+ public ExperimentsProfitTrackerConfig experimentsProfitTracker = new ExperimentsProfitTrackerConfig();
+
+ @Expose
+ @ConfigOption(name = "Dry-Streak Display", desc = "")
+ @Accordion
+ public ExperimentsDryStreakConfig dryStreak = new ExperimentsDryStreakConfig();
+
+ @Expose
+ @ConfigOption(name = "Superpair Data", desc = "Shows a display with useful information while doing the Superpair experiment.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean superpairDisplay = false;
+
+ @Expose
+ @ConfigLink(owner = ExperimentationTableConfig.class, field = "superpairDisplay")
+ public Position superpairDisplayPosition = new Position(-372, 161, false, true);
+
+ @Expose
+ @ConfigOption(name = "Superpairs Clicks Alert", desc = "Display an alert when you reach the maximum clicks gained from Chronomatron or Ultrasequencer.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean superpairsClicksAlert = false;
+
+ @Expose
+ @ConfigOption(name = "ULTRA-RARE Book Alert", desc = "Send a chat message, title and sound when you find an ULTRA-RARE book.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean ultraRareBookAlert = false;
+
+ @Expose
+ @ConfigOption(name = "Guardian Reminder", desc = "Sends a warning when opening the Experimentation Table without a §9§lGuardian Pet §7equipped.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean guardianReminder = false;
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsDryStreakConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsDryStreakConfig.java
new file mode 100644
index 000000000..dfba626a7
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsDryStreakConfig.java
@@ -0,0 +1,31 @@
+package at.hannibal2.skyhanni.config.features.inventory.experimentationtable;
+
+import at.hannibal2.skyhanni.config.FeatureToggle;
+import at.hannibal2.skyhanni.config.core.config.Position;
+import com.google.gson.annotations.Expose;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigLink;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
+
+public class ExperimentsDryStreakConfig {
+
+ @Expose
+ @ConfigOption(name = "Enabled", desc = "Display attempts and or XP since your last ULTRA-RARE.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean enabled = false;
+
+ @Expose
+ @ConfigOption(name = "Attempts", desc = "Display Attempts since.")
+ @ConfigEditorBoolean
+ public boolean attemptsSince = true;
+
+ @Expose
+ @ConfigOption(name = "XP", desc = "Display XP since.")
+ @ConfigEditorBoolean
+ public boolean xpSince = true;
+
+ @Expose
+ @ConfigLink(owner = ExperimentsDryStreakConfig.class, field = "enabled")
+ public Position position = new Position(200, -187, false, true);
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsProfitTrackerConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsProfitTrackerConfig.java
new file mode 100644
index 000000000..62ee4f540
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/experimentationtable/ExperimentsProfitTrackerConfig.java
@@ -0,0 +1,37 @@
+package at.hannibal2.skyhanni.config.features.inventory.experimentationtable;
+
+import at.hannibal2.skyhanni.config.FeatureToggle;
+import at.hannibal2.skyhanni.config.core.config.Position;
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentMessages;
+import com.google.gson.annotations.Expose;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDraggableList;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigLink;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExperimentsProfitTrackerConfig {
+
+ @Expose
+ @ConfigOption(name = "Enabled", desc = "Tracker for drops/XP you get from experiments.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean enabled = false;
+
+ @Expose
+ @ConfigOption(name = "Hide Messages", desc = "Change the messages to be hidden after completing Add-on/Main experiments.")
+ @ConfigEditorDraggableList
+ public List<ExperimentMessages> hideMessages = new ArrayList<>();
+
+ @Expose
+ @ConfigOption(name = "Time displayed", desc = "Time displayed after completing an experiment.")
+ @ConfigEditorSlider(minValue = 5, maxValue = 60, minStep = 1)
+ public int timeDisplayed = 30;
+
+ @Expose
+ @ConfigLink(owner = ExperimentsProfitTrackerConfig.class, field = "enabled")
+ public Position position = new Position(20, 20, false, true);
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java
index 5f54e707d..717dd7659 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java
@@ -56,30 +56,4 @@ public class HelperConfig {
@ConfigOption(name = "Reforge Helper", desc = "")
@Accordion
public ReforgeHelperConfig reforge = new ReforgeHelperConfig();
-
- @Expose
- @ConfigOption(name = "Enchanting", desc = "")
- @Accordion
- public EnchantingConfig enchanting = new EnchantingConfig();
-
- public static class EnchantingConfig {
- @Expose
- @ConfigOption(name = "Superpairs Clicks Alert", desc = "Display an alert when you reach the maximum clicks gained from Chronomatron or Ultrasequencer.")
- @ConfigEditorBoolean
- @FeatureToggle
- public boolean superpairsClicksAlert = false;
-
- @Expose
- @ConfigOption(name = "ULTRA-RARE Book Alert", desc = "Send a chat message, title and sound when you find an ULTRA-RARE book.")
- @ConfigEditorBoolean
- @FeatureToggle
- public boolean ultraRareBookAlert = false;
-
- @Expose
- @ConfigOption(name = "Guardian Reminder", desc = "Sends a warning when opening the Experimentation Table without a §9§lGuardian Pet §7equipped.")
- @ConfigEditorBoolean
- @FeatureToggle
- public boolean guardianReminder = false;
- }
-
}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
index 83e7ea47c..754eea3bb 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
@@ -31,6 +31,7 @@ import at.hannibal2.skyhanni.features.garden.pests.PestProfitTracker;
import at.hannibal2.skyhanni.features.garden.pests.VinylType;
import at.hannibal2.skyhanni.features.garden.visitor.VisitorReward;
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryStrayTracker;
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentsProfitTracker;
import at.hannibal2.skyhanni.features.inventory.wardrobe.WardrobeAPI;
import at.hannibal2.skyhanni.features.mining.MineshaftPityDisplay;
import at.hannibal2.skyhanni.features.mining.fossilexcavator.ExcavatorProfitTracker;
@@ -66,6 +67,29 @@ public class ProfileSpecificStorage {
public String currentPet = "";
@Expose
+ public ExperimentationStorage experimentation = new ExperimentationStorage();
+
+ public static class ExperimentationStorage {
+
+ @Expose
+ public LorenzVec tablePos = new LorenzVec();
+
+ @Expose
+ public ExperimentsDryStreakStorage dryStreak = new ExperimentsDryStreakStorage();
+
+ public static class ExperimentsDryStreakStorage {
+ @Expose
+ public int attemptsSince = 0;
+
+ @Expose
+ public int xpSince = 0;
+ }
+
+ @Expose
+ public ExperimentsProfitTracker.Data experimentsProfitTracker = new ExperimentsProfitTracker.Data();
+ }
+
+ @Expose
public ChocolateFactoryStorage chocolateFactory = new ChocolateFactoryStorage();
public static class ChocolateFactoryStorage {
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableAPI.kt
new file mode 100644
index 000000000..80433b68e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableAPI.kt
@@ -0,0 +1,157 @@
+package at.hannibal2.skyhanni.features.inventory.experimentationtable
+
+import at.hannibal2.skyhanni.data.IslandType
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.events.InventoryUpdatedEvent
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.EntityUtils
+import at.hannibal2.skyhanni.utils.EntityUtils.hasSkullTexture
+import at.hannibal2.skyhanni.utils.InventoryUtils.openInventoryName
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzVec
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.getLorenzVec
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import net.minecraft.entity.item.EntityArmorStand
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+@SkyHanniModule
+object ExperimentationTableAPI {
+
+ private val storage get() = ProfileStorageData.profileSpecific?.experimentation
+
+ val inTable get() = inventoriesPattern.matches(openInventoryName())
+
+ fun inDistanceToTable(vec: LorenzVec, max: Double): Boolean =
+ storage?.tablePos?.let { it.distance(vec) <= max } ?: false
+
+ fun getCurrentExperiment(): Experiment? =
+ superpairsPattern.matchMatcher(openInventoryName()) {
+ Experiment.entries.find { it.nameString == group("experiment") }
+ }
+
+ @SubscribeEvent
+ fun onInventoryUpdated(event: InventoryUpdatedEvent) {
+ if (LorenzUtils.skyBlockIsland != IslandType.PRIVATE_ISLAND || !inTable) return
+
+ val entity = EntityUtils.getEntities<EntityArmorStand>().find { it.hasSkullTexture(experimentationTableSkull) } ?: return
+ val vec = entity.getLorenzVec()
+ if (storage?.tablePos != vec) storage?.tablePos = vec
+ }
+
+ private val experimentationTableSkull =
+ "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTUyOWF" +
+ "iYzg4MzA5NTNmNGQ5MWVkZmZmMjQ2OTVhOWY2Mjc1OGZhNGM1MWIyOWFjMjQ2YzM3NDllYWFlODliMyJ9fX0="
+
+ private val patternGroup = RepoPattern.group("enchanting.experiments")
+
+ /**
+ * REGEX-TEST: Superpairs (Metaphysical)
+ */
+ private val superpairsPattern by patternGroup.pattern(
+ "superpairs",
+ "Superpairs \\((?<experiment>\\w+)\\)",
+ )
+
+ /**
+ * REGEX-TEST: Gained +3 Clicks
+ */
+ val powerUpPattern by patternGroup.pattern(
+ "powerups",
+ "Gained \\+\\d Clicks?|Instant Find|\\+\\S* XP",
+ )
+
+ /**
+ * REGEX-TEST: 123k Enchanting Exp
+ * REGEX-TEST: Titanic Experience Bottle
+ */
+ val rewardPattern by patternGroup.pattern(
+ "rewards",
+ "\\d{1,3}k Enchanting Exp|Enchanted Book|(?:Titanic |Grand |\\b)Experience Bottle|Metaphysical Serum|Experiment The Fish",
+ )
+
+ /**
+ * REGEX-TEST: Superpairs (Metaphysical)
+ * REGEX-TEST: Chronomatron (Metaphysical)
+ */
+ val inventoriesPattern by patternGroup.pattern(
+ "inventories",
+ "(?:Superpairs|Chronomatron|Ultrasequencer) (?:\\(.+\\)|➜ Stakes|Rewards)|Experimentation Table",
+ )
+
+ /**
+ * REGEX-TEST: +42,000 Enchanting Exp
+ */
+ val enchantingExpChatPattern by patternGroup.pattern(
+ "chatexp",
+ "^ \\+(?<amount>\\d+|\\d+,\\d+)k? Enchanting Exp$",
+ )
+
+ /**
+ * REGEX-TEST: +Smite VII
+ * REGEX-TEST: +42,000 Enchanting Exp
+ */
+ val experimentsDropPattern by patternGroup.pattern(
+ "drop",
+ "^ \\+(?<reward>.*)\$",
+ )
+
+ /**
+ * REGEX-TEST: You claimed the Superpairs rewards!
+ */
+ val claimMessagePattern by patternGroup.pattern(
+ "claim",
+ "You claimed the \\S+ rewards!",
+ )
+
+ /**
+ * REGEX-TEST: 131k Enchanting Exp
+ * REGEX-TEST: 42,000 Enchanting Exp
+ */
+ val enchantingExpPattern by patternGroup.pattern(
+ "exp",
+ "(?<amount>\\d+|\\d+,\\d+)k? Enchanting Exp",
+ )
+
+ /**
+ * REGEX-TEST: Titanic Experience Bottle
+ */
+ val experienceBottlePattern by patternGroup.pattern(
+ "xpbottle",
+ "(?:Titanic |Grand |\\b)Experience Bottle",
+ )
+
+ /**
+ * REGEX-TEST: ☕ You renewed the experiment table! (1/3)
+ */
+ val experimentRenewPattern by patternGroup.pattern(
+ "renew",
+ "^☕ You renewed the experiment table! \\((?<current>\\d)/3\\)$",
+ )
+
+ /**
+ * REGEX-TEST: §d§kXX§5 ULTRA-RARE BOOK! §d§kXX
+ */
+ val ultraRarePattern by patternGroup.pattern(
+ "ultrarare",
+ "§d§kXX§5 ULTRA-RARE BOOK! §d§kXX",
+ )
+
+ /**
+ * REGEX-TEST: §9Smite VII
+ */
+ val bookPattern by patternGroup.pattern(
+ "book",
+ "§9(?<enchant>.*)",
+ )
+
+ /**
+ * REGEX-TEST: §dGuardian
+ * REGEX-TEST: §9Guardian§e
+ */
+ val petNamePattern by patternGroup.pattern(
+ "guardianpet",
+ "§[956d]Guardian.*",
+ )
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableEnums.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableEnums.kt
new file mode 100644
index 000000000..0071d3af3
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentationTableEnums.kt
@@ -0,0 +1,28 @@
+package at.hannibal2.skyhanni.features.inventory.experimentationtable
+
+enum class ExperimentMessages(private val str: String) {
+ DONE("§eYou claimed the §dSuperpairs §erewards! §8(§7Claim§8)"),
+ EXPERIENCE("§8 +§3141k Experience §8(§7Experience Drops§8)"),
+ ENCHANTMENTS("§8 +§9Smite VII §8(§7Enchantment Drops§8)"),
+ BOTTLES("§8 +§9Titanic Experience Bottle §8(§7Bottle Drops§8)"),
+ MISC("§8 +§5Metaphysical Serum §8(§7Misc Drops§8)");
+
+ override fun toString(): String {
+ return str
+ }
+}
+
+enum class Experiment(val nameString: String, val gridSize: Int, val startSlot: Int, val endSlot: Int, val sideSpace: Int) {
+ NONE("", 0, 0, 0, 0),
+ BEGINNER("Beginner", 14, 18, 35, 1),
+ HIGH("High", 20, 10, 43, 2),
+ GRAND("Grand", 20, 10, 43, 2),
+ SUPREME("Supreme", 28, 9, 44, 1),
+ TRANSCENDENT("Transcendent", 28, 9, 44, 1),
+ METAPHYSICAL("Metaphysical", 28, 9, 44, 1),
+ ;
+
+ override fun toString(): String {
+ return nameString
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsDryStreakDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsDryStreakDisplay.kt
new file mode 100644
index 000000000..50d033842
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsDryStreakDisplay.kt
@@ -0,0 +1,113 @@
+package at.hannibal2.skyhanni.features.inventory.experimentationtable
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.InventoryCloseEvent
+import at.hannibal2.skyhanni.events.InventoryOpenEvent
+import at.hannibal2.skyhanni.events.InventoryUpdatedEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.bookPattern
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.ultraRarePattern
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.ChatUtils
+import at.hannibal2.skyhanni.utils.InventoryUtils
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+@SkyHanniModule
+object ExperimentsDryStreakDisplay {
+
+ private val config get() = SkyHanniMod.feature.inventory.experimentationTable.dryStreak
+ private val storage get() = ProfileStorageData.profileSpecific?.experimentation?.dryStreak
+
+ private var display = emptyList<String>()
+
+ private var didJustFind = false
+
+ @SubscribeEvent
+ fun onChestGuiOverlayRendered(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) {
+ if (!isEnabled()) return
+ if (!ExperimentationTableAPI.inventoriesPattern.matches(InventoryUtils.openInventoryName())) return
+
+ display = drawDisplay()
+ config.position.renderStrings(
+ display,
+ posLabel = "Experimentation Table Dry Streak",
+ )
+ }
+
+ @SubscribeEvent
+ fun onInventoryOpen(event: InventoryOpenEvent) {
+ if (event.inventoryName == "Experimentation Table" && didJustFind) didJustFind = false
+ }
+
+ @SubscribeEvent
+ fun onInventoryUpdated(event: InventoryUpdatedEvent) {
+ if (!isEnabled() || didJustFind || ExperimentationTableAPI.getCurrentExperiment() == null) return
+
+ for (lore in event.inventoryItems.map { it.value.getLore() }) {
+ val firstLine = lore.firstOrNull() ?: continue
+ if (!ultraRarePattern.matches(firstLine)) continue
+ val bookNameLine = lore.getOrNull(2) ?: continue
+ bookPattern.matchMatcher(bookNameLine) {
+ val storage = storage ?: return
+ ChatUtils.chat(
+ "§a§lDRY-STREAK ENDED! §eYou have (finally) " +
+ "found a §5ULTRA-RARE §eafter §3${storage.xpSince.shortFormat()} Enchanting Exp " +
+ "§e and §2${storage.attemptsSince} attempts§e!",
+ )
+ storage.attemptsSince = 0
+ storage.xpSince = 0
+ didJustFind = true
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onInventoryClose(event: InventoryCloseEvent) {
+ if (didJustFind || ExperimentationTableAPI.getCurrentExperiment() == null) return
+
+ val storage = storage ?: return
+ storage.attemptsSince += 1
+ }
+
+ @SubscribeEvent
+ fun onChat(event: LorenzChatEvent) {
+ if (!isEnabled() || didJustFind) return
+
+ ExperimentationTableAPI.enchantingExpChatPattern.matchMatcher(event.message.removeColor()) {
+ val storage = storage ?: return
+ storage.xpSince += group("amount").substringBefore(",").toInt() * 1000
+ }
+ }
+
+ private fun drawDisplay() = buildList {
+ val storage = storage ?: return@buildList
+
+ add("§cDry-Streak since last §5ULTRA-RARE")
+
+ val colorPrefix = "§e"
+ val attemptsSince = storage.attemptsSince
+ val xpSince = storage.xpSince.shortFormat()
+ val attemptsSuffix = if (attemptsSince == 1) "" else "s"
+
+ if (config.attemptsSince && config.xpSince) {
+ add("$colorPrefix ├ $attemptsSince Attempt$attemptsSuffix")
+ add("$colorPrefix └ $xpSince XP")
+ } else if (config.attemptsSince) {
+ add("$colorPrefix └ $attemptsSince Attempt$attemptsSuffix")
+ } else {
+ add("$colorPrefix └ $xpSince XP")
+ }
+ }
+
+ private fun isEnabled() =
+ LorenzUtils.inSkyBlock && config.enabled && (config.xpSince || config.attemptsSince)
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt
new file mode 100644
index 000000000..16c0305cb
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt
@@ -0,0 +1,258 @@
+package at.hannibal2.skyhanni.features.inventory.experimentationtable
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.ClickType
+import at.hannibal2.skyhanni.data.IslandType
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.InventoryCloseEvent
+import at.hannibal2.skyhanni.events.InventoryUpdatedEvent
+import at.hannibal2.skyhanni.events.IslandChangeEvent
+import at.hannibal2.skyhanni.events.ItemClickEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.claimMessagePattern
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.enchantingExpPattern
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.experienceBottlePattern
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.experimentRenewPattern
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.experimentsDropPattern
+import at.hannibal2.skyhanni.features.inventory.experimentationtable.ExperimentationTableAPI.inventoriesPattern
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut
+import at.hannibal2.skyhanni.utils.CollectionUtils.addSearchString
+import at.hannibal2.skyhanni.utils.InventoryUtils
+import at.hannibal2.skyhanni.utils.ItemPriceUtils.getNpcPriceOrNull
+import at.hannibal2.skyhanni.utils.ItemPriceUtils.getPrice
+import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName
+import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzUtils.round
+import at.hannibal2.skyhanni.utils.LorenzVec
+import at.hannibal2.skyhanni.utils.NEUInternalName
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import at.hannibal2.skyhanni.utils.renderables.Searchable
+import at.hannibal2.skyhanni.utils.renderables.toSearchable
+import at.hannibal2.skyhanni.utils.tracker.ItemTrackerData
+import at.hannibal2.skyhanni.utils.tracker.SkyHanniItemTracker
+import com.google.gson.annotations.Expose
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import kotlin.math.absoluteValue
+
+@SkyHanniModule
+object ExperimentsProfitTracker {
+
+ private val config get() = SkyHanniMod.feature.inventory.experimentationTable.experimentsProfitTracker
+
+ private val tracker = SkyHanniItemTracker(
+ "Experiments Profit Tracker",
+ { Data() },
+ { it.experimentation.experimentsProfitTracker },
+ ) { drawDisplay(it) }
+
+ private var lastSplashes = mutableListOf<ItemStack>()
+ private var lastSplashTime = SimpleTimeMark.farPast()
+ private var lastBottlesInInventory = mutableMapOf<NEUInternalName, Int>()
+ private var currentBottlesInInventory = mutableMapOf<NEUInternalName, Int>()
+
+ class Data : ItemTrackerData() {
+ override fun resetItems() {
+ experimentsDone = 0L
+ xpGained = 0L
+ bitCost = 0L
+ startCost = 0L
+ }
+
+ override fun getDescription(timesGained: Long): List<String> {
+ val percentage = timesGained.toDouble() / experimentsDone
+ val dropRate = LorenzUtils.formatPercentage(percentage.coerceAtMost(1.0))
+ return listOf(
+ "§7Dropped §e${timesGained.addSeparators()} §7times.",
+ "§7Your drop rate: §c$dropRate.",
+ )
+ }
+
+ override fun getCoinName(item: TrackedItem) = ""
+
+ override fun getCoinDescription(item: TrackedItem) = listOf<String>()
+
+ @Expose
+ var experimentsDone = 0L
+
+ @Expose
+ var xpGained = 0L
+
+ @Expose
+ var bitCost = 0L
+
+ @Expose
+ var startCost = 0L
+ }
+
+ @SubscribeEvent
+ fun onChat(event: LorenzChatEvent) {
+ if