diff options
author | Kevin <92656833+kevinthegreat1@users.noreply.github.com> | 2024-05-23 22:19:13 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-23 22:19:13 -0400 |
commit | b6da8de30929b4722244062e948dca42a810a230 (patch) | |
tree | f5ea29a18d285f29397d5e2a5bbaac06194eb38e /src/main/java/de/hysky/skyblocker | |
parent | 610c64758fc8d0b8bea0145c33881b60c0747bd7 (diff) | |
parent | 1f6798df58af5eec4dfe954ab413c5a92d0fee63 (diff) | |
download | Skyblocker-b6da8de30929b4722244062e948dca42a810a230.tar.gz Skyblocker-b6da8de30929b4722244062e948dca42a810a230.tar.bz2 Skyblocker-b6da8de30929b4722244062e948dca42a810a230.zip |
Merge pull request #683 from Emirlol/chocolate-factory-helper
Add chocolate factory helper
Diffstat (limited to 'src/main/java/de/hysky/skyblocker')
15 files changed, 843 insertions, 18 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index e6b2d25a..a57b4177 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -2,15 +2,16 @@ package de.hysky.skyblocker; import com.google.gson.Gson; import com.google.gson.GsonBuilder; - -import de.hysky.skyblocker.config.datafixer.ConfigDataFixer; import de.hysky.skyblocker.config.ImageRepoLoader; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.datafixer.ConfigDataFixer; import de.hysky.skyblocker.debug.Debug; import de.hysky.skyblocker.skyblock.*; import de.hysky.skyblocker.skyblock.calculators.CalculatorCommand; import de.hysky.skyblocker.skyblock.chat.ChatRuleAnnouncementScreen; import de.hysky.skyblocker.skyblock.chat.ChatRulesHandler; +import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; +import de.hysky.skyblocker.skyblock.chocolatefactory.TimeTowerReminder; import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; import de.hysky.skyblocker.skyblock.dungeon.*; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; @@ -45,10 +46,7 @@ import de.hysky.skyblocker.skyblock.waypoint.FairySouls; import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual; import de.hysky.skyblocker.skyblock.waypoint.OrderedWaypoints; import de.hysky.skyblocker.skyblock.waypoint.Relics; -import de.hysky.skyblocker.utils.ApiUtils; -import de.hysky.skyblocker.utils.NEURepoManager; -import de.hysky.skyblocker.utils.ProfileUtils; -import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.chat.ChatMessageListener; import de.hysky.skyblocker.utils.discord.DiscordRPCManager; import de.hysky.skyblocker.utils.render.RenderHelper; @@ -183,6 +181,9 @@ public class SkyblockerMod implements ClientModInitializer { BeaconHighlighter.init(); WarpAutocomplete.init(); MobBoundingBoxes.init(); + EggFinder.init(); + TimeTowerReminder.init(); + SkyblockTime.init(); Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20); Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 200); diff --git a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java index 1528f853..9e4935cb 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java @@ -2,10 +2,11 @@ package de.hysky.skyblocker.config.categories; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; -import dev.isxander.yacl3.api.*; +import de.hysky.skyblocker.utils.waypoint.Waypoint; import dev.isxander.yacl3.api.ConfigCategory; import dev.isxander.yacl3.api.Option; import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.api.OptionGroup; import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder; import net.minecraft.text.Text; @@ -137,6 +138,52 @@ public class HelperCategory { .build()) .build()) + //Chocolate Factory + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory")) + .collapsed(true) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.enableChocolateFactoryHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.enableChocolateFactoryHelper.@Tooltip"))) + .binding(defaults.helpers.chocolateFactory.enableChocolateFactoryHelper, + () -> config.helpers.chocolateFactory.enableChocolateFactoryHelper, + newValue -> config.helpers.chocolateFactory.enableChocolateFactoryHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.enableEggFinder")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.enableEggFinder.@Tooltip"))) + .binding(defaults.helpers.chocolateFactory.enableEggFinder, + () -> config.helpers.chocolateFactory.enableEggFinder, + newValue -> config.helpers.chocolateFactory.enableEggFinder = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages.@Tooltip"))) + .binding(defaults.helpers.chocolateFactory.sendEggFoundMessages, + () -> config.helpers.chocolateFactory.sendEggFoundMessages, + newValue -> config.helpers.chocolateFactory.sendEggFoundMessages = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Waypoint.Type>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.waypointType")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.waypointType.@Tooltip"))) + .binding(defaults.helpers.chocolateFactory.waypointType, + () -> config.helpers.chocolateFactory.waypointType, + newValue -> config.helpers.chocolateFactory.waypointType = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder.@Tooltip"))) + .binding(defaults.helpers.chocolateFactory.enableTimeTowerReminder, + () -> config.helpers.chocolateFactory.enableTimeTowerReminder, + newValue -> config.helpers.chocolateFactory.enableTimeTowerReminder = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) + .build(); } } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java index 2abff6ac..c0314924 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.config.configs; +import de.hysky.skyblocker.utils.waypoint.Waypoint; import dev.isxander.yacl3.config.v2.api.SerialEntry; public class HelperConfig { @@ -19,6 +20,9 @@ public class HelperConfig { @SerialEntry public FairySouls fairySouls = new FairySouls(); + @SerialEntry + public ChocolateFactory chocolateFactory = new ChocolateFactory(); + public static class MythologicalRitual { @SerialEntry public boolean enableMythologicalRitualHelper = true; @@ -62,4 +66,21 @@ public class HelperConfig { @SerialEntry public boolean highlightOnlyNearbySouls = false; } + + public static class ChocolateFactory { + @SerialEntry + public boolean enableChocolateFactoryHelper = true; + + @SerialEntry + public boolean enableEggFinder = true; + + @SerialEntry + public boolean sendEggFoundMessages = true; + + @SerialEntry + public Waypoint.Type waypointType = Waypoint.Type.WAYPOINT; + + @SerialEntry + public boolean enableTimeTowerReminder = true; + } } diff --git a/src/main/java/de/hysky/skyblocker/debug/Debug.java b/src/main/java/de/hysky/skyblocker/debug/Debug.java index d9ac668c..d642ca5b 100644 --- a/src/main/java/de/hysky/skyblocker/debug/Debug.java +++ b/src/main/java/de/hysky/skyblocker/debug/Debug.java @@ -83,9 +83,7 @@ public class Debug { Iterable<ItemStack> equippedItems = armorStand.getEquippedItems(); for (ItemStack stack : equippedItems) { - String texture = ItemUtils.getHeadTexture(stack); - - if (!texture.isEmpty()) context.getSource().sendFeedback(Text.of(texture)); + ItemUtils.getHeadTextureOptional(stack).ifPresent(texture -> context.getSource().sendFeedback(Text.of(texture))); } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 2c2c1376..48389d40 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -3,9 +3,9 @@ package de.hysky.skyblocker.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.sugar.Local; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.CompactDamage; import de.hysky.skyblocker.skyblock.FishingHelper; +import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.end.BeaconHighlighter; @@ -84,7 +84,7 @@ public abstract class ClientPlayNetworkHandlerMixin { return !(Utils.isOnHypixel() && ((Identifier) identifier).getNamespace().equals("badlion")); } - @WrapWithCondition(method = { "onScoreboardScoreUpdate", "onScoreboardScoreReset" }, at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false)) + @WrapWithCondition(method = {"onScoreboardScoreUpdate", "onScoreboardScoreReset"}, at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false)) private boolean skyblocker$cancelUnknownScoreboardObjectiveWarnings(Logger instance, String message, Object objectiveName) { return !Utils.isOnHypixel(); } @@ -111,11 +111,18 @@ public abstract class ClientPlayNetworkHandlerMixin { @Inject(method = "onEntityTrackerUpdate", at = @At("TAIL")) private void skyblocker$onEntityTrackerUpdate(EntityTrackerUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) { - if (!SkyblockerConfigManager.get().uiAndVisuals.compactDamage.enabled || !(entity instanceof ArmorStandEntity armorStandEntity)) return; + if (!(entity instanceof ArmorStandEntity armorStandEntity)) return; + + EggFinder.checkIfEgg(armorStandEntity); try { //Prevent packet handling fails if something goes wrong so that entity trackers still update, just without compact damage numbers CompactDamage.compactDamage(armorStandEntity); } catch (Exception e) { LOGGER.error("[Skyblocker Compact Damage] Failed to compact damage number", e); } } + + @Inject(method = "onEntityEquipmentUpdate", at = @At(value = "TAIL")) + private void skyblocker$onEntityEquip(EntityEquipmentUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) { + EggFinder.checkIfEgg(entity); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/CompactDamage.java b/src/main/java/de/hysky/skyblocker/skyblock/CompactDamage.java index 2837364b..8285a823 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/CompactDamage.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/CompactDamage.java @@ -19,6 +19,7 @@ public class CompactDamage { } public static void compactDamage(ArmorStandEntity entity) { + if (!SkyblockerConfigManager.get().uiAndVisuals.compactDamage.enabled) return; if (!entity.isInvisible() || !entity.hasCustomName() || !entity.isCustomNameVisible()) return; Text customName = entity.getCustomName(); String customNameStringified = customName.getString(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java new file mode 100644 index 00000000..2d530b6d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -0,0 +1,348 @@ +package de.hysky.skyblocker.skyblock.chocolatefactory; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RegexUtils; +import de.hysky.skyblocker.utils.render.gui.ColorHighlight; +import de.hysky.skyblocker.utils.render.gui.ContainerSolver; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.item.TooltipType; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ChocolateFactorySolver extends ContainerSolver { + private static final Pattern CPS_PATTERN = Pattern.compile("([\\d,.]+) Chocolate per second"); + private static final Pattern CPS_INCREASE_PATTERN = Pattern.compile("\\+([\\d,]+) Chocolate per second"); + private static final Pattern COST_PATTERN = Pattern.compile("Cost ([\\d,]+) Chocolate"); + private static final Pattern TOTAL_MULTIPLIER_PATTERN = Pattern.compile("Total Multiplier: ([\\d.]+)x"); + private static final Pattern MULTIPLIER_INCREASE_PATTERN = Pattern.compile("\\+([\\d.]+)x Chocolate per second"); + private static final Pattern CHOCOLATE_PATTERN = Pattern.compile("^([\\d,]+) Chocolate$"); + private static final Pattern PRESTIGE_REQUIREMENT_PATTERN = Pattern.compile("Chocolate this Prestige: ([\\d,]+) +Requires (\\S+) Chocolate this Prestige!"); + private static final Pattern TIME_TOWER_STATUS_PATTERN = Pattern.compile("Status: (ACTIVE|INACTIVE)"); + private static final ObjectArrayList<Rabbit> cpsIncreaseFactors = new ObjectArrayList<>(6); + private static long totalChocolate = -1L; + private static double totalCps = -1.0; + private static double totalCpsMultiplier = -1.0; + private static long requiredUntilNextPrestige = -1L; + private static double timeTowerMultiplier = -1.0; + private static boolean isTimeTowerActive = false; + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + private static ItemStack bestUpgrade = null; + private static ItemStack bestAffordableUpgrade = null; + + public ChocolateFactorySolver() { + super("^Chocolate Factory$"); + ItemTooltipCallback.EVENT.register(ChocolateFactorySolver::handleTooltip); + } + + @Override + protected boolean isEnabled() { + return SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper; + } + + @Override + protected List<ColorHighlight> getColors(String[] groups, Int2ObjectMap<ItemStack> slots) { + updateFactoryInfo(slots); + List<ColorHighlight> highlights = new ArrayList<>(); + + getPrestigeHighlight(slots.get(28)).ifPresent(highlights::add); + + if (totalChocolate <= 0 || cpsIncreaseFactors.isEmpty()) return highlights; //Something went wrong or there's nothing we can afford. + Rabbit bestRabbit = cpsIncreaseFactors.getFirst(); + bestUpgrade = bestRabbit.itemStack; + if (bestRabbit.cost <= totalChocolate) { + highlights.add(ColorHighlight.green(bestRabbit.slot)); + return highlights; + } + highlights.add(ColorHighlight.yellow(bestRabbit.slot)); + + for (Rabbit rabbit : cpsIncreaseFactors.subList(1, cpsIncreaseFactors.size())) { + if (rabbit.cost <= totalChocolate) { + bestAffordableUpgrade = rabbit.itemStack; + highlights.add(ColorHighlight.green(rabbit.slot)); + break; + } + } + + return highlights; + } + + private static void updateFactoryInfo(Int2ObjectMap<ItemStack> slots) { + cpsIncreaseFactors.clear(); + + for (int i = 29; i <= 33; i++) { // The 5 rabbits slots are in 29, 30, 31, 32 and 33. + ItemStack item = slots.get(i); + if (item.isOf(Items.PLAYER_HEAD)) { + getRabbit(item, i).ifPresent(cpsIncreaseFactors::add); + } + } + + //Coach is in slot 42 + getCoach(slots.get(42)).ifPresent(cpsIncreaseFactors::add); + + //The clickable chocolate is in slot 13, holds the total chocolate + RegexUtils.getLongFromMatcher(CHOCOLATE_PATTERN.matcher(slots.get(13).getName().getString())).ifPresent(l -> totalChocolate = l); + + //Cps item (cocoa bean) is in slot 45 + String cpsItemLore = getConcatenatedLore(slots.get(45)); + Matcher cpsMatcher = CPS_PATTERN.matcher(cpsItemLore); + RegexUtils.getDoubleFromMatcher(cpsMatcher).ifPresent(d -> totalCps = d); + Matcher multiplierMatcher = TOTAL_MULTIPLIER_PATTERN.matcher(cpsItemLore); + RegexUtils.getDoubleFromMatcher(multiplierMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0).ifPresent(d -> totalCpsMultiplier = d); + + //Prestige item is in slot 28 + Matcher prestigeMatcher = PRESTIGE_REQUIREMENT_PATTERN.matcher(getConcatenatedLore(slots.get(28))); + OptionalLong currentChocolate = RegexUtils.getLongFromMatcher(prestigeMatcher); + if (currentChocolate.isPresent()) { + String requirement = prestigeMatcher.group(2); //If the first one matched, we can assume the 2nd one is also matched since it's one whole regex + //Since the last character is either M or B we can just try to replace both characters. Only the correct one will actually replace anything. + String amountString = requirement.replace("M", "000000").replace("B", "000000000"); + if (NumberUtils.isParsable(amountString)) { + requiredUntilNextPrestige = Long.parseLong(amountString) - currentChocolate.getAsLong(); + } + } + + //Time Tower is in slot 39 + timeTowerMultiplier = romanToDecimal(StringUtils.substringAfterLast(slots.get(39).getName().getString(), ' ')) / 10.0; //The name holds the level, which is multiplier * 10 in roman numerals + Matcher timeTowerStatusMatcher = TIME_TOWER_STATUS_PATTERN.matcher(getConcatenatedLore(slots.get(39))); + if (timeTowerStatusMatcher.find()) { + isTimeTowerActive = timeTowerStatusMatcher.group(1).equals("ACTIVE"); + } + + //Compare cost/cpsIncrease rather than cpsIncrease/cost to avoid getting close to 0 and losing precision. + cpsIncreaseFactors.sort(Comparator.comparingDouble(rabbit -> rabbit.cost() / rabbit.cpsIncrease())); //Ascending order, lower = better + } + + private static void handleTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType tooltipType, List<Text> lines) { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper) return; + if (!(MinecraftClient.getInstance().currentScreen instanceof GenericContainerScreen screen) || !screen.getTitle().getString().equals("Chocolate Factory")) return; + + int lineIndex = lines.size(); + //This boolean is used to determine if we should add a smooth line to separate the added information from the rest of the tooltip. + //It should be set to true if there's any information added, false otherwise. + boolean shouldAddLine = false; + + String lore = concatenateLore(lines); + Matcher costMatcher = COST_PATTERN.matcher(lore); + OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher); + //Available on all items with a chocolate cost + if (cost.isPresent()) shouldAddLine = addUpgradeTimerToLore(lines, cost.getAsLong()); + + //Prestige item + if (stack.isOf(Items.DROPPER) && requiredUntilNextPrestige != -1L) { + shouldAddLine = addPrestigeTimerToLore(lines) || shouldAddLine; + } + //Time tower + else if (stack.isOf(Items.CLOCK)) { + shouldAddLine = addTimeTowerStatsToLore(lines) || shouldAddLine; + } + //Rabbits + else if (stack.isOf(Items.PLAYER_HEAD)) { + shouldAddLine = addRabbitStatsToLore(lines, stack) || shouldAddLine; + } + + //This is an ArrayList, so this operation is probably not very efficient, but logically it's pretty much the only way I can think of + if (shouldAddLine) lines.add(lineIndex, ItemTooltip.createSmoothLine()); + } + + private static boolean addUpgradeTimerToLore(List<Text> lines, long cost) { + if (totalChocolate < 0L || totalCps < 0.0) return false; + lines.add(Text.empty() + .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY)) + .append(formatTime((cost - totalChocolate) / totalCps))); + return true; + } + + private static boolean addPrestigeTimerToLore(List<Text> lines) { + if (requiredUntilNextPrestige == -1L || totalCps == -1.0) return false; + lines.add(Text.empty() + .append(Text.literal("Chocolate until next prestige: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(requiredUntilNextPrestige)).formatted(Formatting.GOLD))); + lines.add(Text.empty() + .append(Text.literal("Time until next prestige: ").formatted(Formatting.GRAY)) + .append(formatTime(requiredUntilNextPrestige / totalCps))); + return true; + } + + private static boolean addTimeTowerStatsToLore(List<Text> lines) { + if (totalCps < 0.0 || totalCpsMultiplier < 0.0 || timeTowerMultiplier < 0.0) return false; + lines.add(Text.literal("Current stats:").formatted(Formatting.GRAY)); + lines.add(Text.empty() + .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD))); + lines.add(Text.empty() + .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps : totalCps / totalCpsMultiplier * (timeTowerMultiplier + totalCpsMultiplier))).formatted(Formatting.GOLD))); + if (timeTowerMultiplier < 1.5) { + lines.add(Text.literal("Stats after upgrade:").formatted(Formatting.GRAY)); + lines.add(Text.empty() + .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(totalCps / (totalCpsMultiplier) * (timeTowerMultiplier + 0.1))).formatted(Formatting.GOLD))); + lines.add(Text.empty() + .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD))); + } + return true; + } + + private static boolean addRabbitStatsToLore(List<Text> lines, ItemStack stack) { + if (cpsIncreaseFactors.isEmpty()) return false; + boolean changed = false; + for (Rabbit rabbit : cpsIncreaseFactors) { + if (rabbit.itemStack != stack) continue; + changed = true; + lines.add(Text.empty() + .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + + lines.add(Text.empty() + .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + + if (rabbit.itemStack == bestUpgrade) { + if (rabbit.cost <= totalChocolate) { + lines.add(Text.literal("Best upgrade").formatted(Formatting.GREEN)); + } else { + lines.add(Text.literal("Best upgrade, can't afford").formatted(Formatting.YELLOW)); + } + } else if (rabbit.itemStack == bestAffordableUpgrade && rabbit.cost <= totalChocolate) { + lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN)); + } + } + return changed; + } + + private static MutableText formatTime(double seconds) { + seconds = Math.ceil(seconds); + if (seconds <= 0) return Text.literal("Now").formatted(Formatting.GREEN); + + StringBuilder builder = new StringBuilder(); + if (seconds >= 86400) { + builder.append((int) (seconds / 86400)).append("d "); + seconds %= 86400; + } + if (seconds >= 3600) { + builder.append((int) (seconds / 3600)).append("h "); + seconds %= 3600; + } + if (seconds >= 60) { + builder.append((int) (seconds / 60)).append("m "); + seconds %= 60; + } + if (seconds >= 1) { + builder.append((int) seconds).append("s"); + } + return Text.literal(builder.toString()).formatted(Formatting.GOLD); + } + + /** + * Utility method. + */ + private static String getConcatenatedLore(ItemStack item) { + return concatenateLore(ItemUtils.getLore(item)); + } + + /** + * Concatenates the lore of an item into one string. + * This is useful in case some pattern we're looking for is split into multiple lines, which would make it harder to regex. + */ + private static String concatenateLore(List<Text> lore) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < lore.size(); i++) { + stringBuilder.append(lore.get(i).getString()); + if (i != lore.size() - 1) stringBuilder.append(" "); + } + return stringBuilder.toString(); + } + + private static Optional<Rabbit> getCoach(ItemStack coachItem) { + if (!coachItem.isOf(Items.PLAYER_HEAD)) return Optional.empty(); + String coachLore = getConcatenatedLore(coachItem); + + if (totalCpsMultiplier == -1.0) return Optional.empty(); //We need the total multiplier to calculate the increase in cps. + + Matcher multiplierIncreaseMatcher = MULTIPLIER_INCREASE_PATTERN.matcher(coachLore); + OptionalDouble currentCpsMultiplier = RegexUtils.getDoubleFromMatcher(multiplierIncreaseMatcher); + if (currentCpsMultiplier.isEmpty()) return Optional.empty(); + + OptionalDouble nextCpsMultiplier = RegexUtils.getDoubleFromMatcher(multiplierIncreaseMatcher); + if (nextCpsMultiplier.isEmpty()) { //This means that the coach isn't hired yet. + nextCpsMultiplier = currentCpsMultiplier; //So the first instance of the multiplier is actually the amount we'll get upon upgrading. + currentCpsMultiplier = OptionalDouble.of(0.0); //And so, we can re-assign values to the variables to make the calculation more readable. + } + + Matcher costMatcher = COST_PATTERN.matcher(coachLore); + OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line + if (cost.isEmpty()) return Optional.empty(); + + return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), 42, coachItem)); + } + + private static Optional<Rabbit> getRabbit(ItemStack item, int slot) { + String lore = getConcatenatedLore(item); + Matcher cpsMatcher = CPS_INCREASE_PATTERN.matcher(lore); + OptionalInt currentCps = RegexUtils.getIntFromMatcher(cpsMatcher); + if (currentCps.isEmpty()) return Optional.empty(); + OptionalInt nextCps = RegexUtils.getIntFromMatcher(cpsMatcher); + if (nextCps.isEmpty()) { + nextCps = currentCps; //This means that the rabbit isn't hired yet. + currentCps = OptionalInt.of(0); //So the first instance of the cps is actually the amount we'll get upon hiring. + } + + Matcher costMatcher = COST_PATTERN.matcher(lore); + OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line + if (cost.isEmpty()) return Optional.empty(); + return Optional.of(new Rabbit(nextCps.getAsInt() - currentCps.getAsInt(), cost.getAsInt(), slot, item)); + } + + private static Optional<ColorHighlight> getPrestigeHighlight(ItemStack item) { + List<Text> loreList = ItemUtils.getLore(item); + if (loreList.isEmpty()) return Optional.empty(); + + String lore = loreList.getLast().getString(); //The last line holds the text we're looking for + if (lore.equals("Click to prestige!")) return Optional.of(ColorHighlight.green(28)); + return Optional.of(ColorHighlight.red(28)); + } + + private record Rabbit(double cpsIncrease, int cost, int slot, ItemStack itemStack) { + } + + //Perhaps the part below can go to a separate file later on, but I couldn't find a proper name for the class, so they're staying here. + private static final Map<Character, Integer> romanMap = Map.of( + 'I', 1, + 'V', 5, + 'X', 10, + 'L', 50, + 'C', 100, + 'D', 500, + 'M', 1000 + ); + + public static int romanToDecimal(String romanNumeral) { + int decimal = 0; + int lastNumber = 0; + for (int i = romanNumeral.length() - 1; i >= 0; i--) { + char ch = romanNumeral.charAt(i); + decimal = romanMap.get(ch) >= lastNumber ? decimal + romanMap.get(ch) : decimal - romanMap.get(ch); + lastNumber = romanMap.get(ch); + } + return decimal; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java new file mode 100644 index 00000000..c4fd7d4d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java @@ -0,0 +1,168 @@ +package de.hysky.skyblocker.skyblock.chocolatefactory; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.*; +import de.hysky.skyblocker.utils.waypoint.Waypoint; +import it.unimi.dsi.fastutil.objects.ObjectImmutableList; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.mutable.MutableObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EggFinder { + private static final Pattern eggFoundPattern = Pattern.compile("^(?:HOPPITY'S HUNT You found a Chocolate|You have already collected this Chocolate) (Breakfast|Lunch|Dinner)"); + private static final Pattern newEggPattern = Pattern.compile("^HOPPITY'S HUNT A Chocolate (Breakfast|Lunch|Dinner) Egg has appeared!$"); + private static final Logger logger = LoggerFactory.getLogger("Skyblocker Egg Finder"); + private static final LinkedList<ArmorStandEntity> armorStandQueue = new LinkedList<>(); + private static final Location[] possibleLocations = {Location.CRIMSON_ISLE, Location.CRYSTAL_HOLLOWS, Location.DUNGEON_HUB, Location.DWARVEN_MINES, Location.HUB, Location.THE_END, Location.THE_PARK, Location.GOLD_MINE}; + private static boolean isLocationCorrect = false; + + private EggFinder() { + } + + public static void init() { + ClientPlayConnectionEvents.JOIN.register((ignored, ignored2, ignored3) -> invalidateState()); + SkyblockEvents.LOCATION_CHANGE.register(EggFinder::handleLocationChange); + ClientReceiveMessageEvents.GAME.register(EggFinder::onChatMessage); + WorldRenderEvents.AFTER_TRANSLUCENT.register(EggFinder::renderWaypoints); + } + + private static void handleLocationChange(Location location) { + for (Location possibleLocation : possibleLocations) { + if (location == possibleLocation) { + isLocationCorrect = true; + break; + } + } + if (!isLocationCorrect) { + armorStandQueue.clear(); + return; + } + + while (!armorStandQueue.isEmpty()) { + handleArmorStand(armorStandQueue.poll()); + } + } + + public static void checkIfEgg(Entity entity) { + if (entity instanceof ArmorStandEntity armorStand) checkIfEgg(armorStand); + } + + public static void checkIfEgg(ArmorStandEntity armorStand) { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; + if (SkyblockTime.skyblockSeason.get() != SkyblockTime.Season.SPRING) return; + if (armorStand.hasCustomName() || !armorStand.isInvisible() || !armorStand.shouldHideBasePlate()) return; + if (Utils.getLocation() == Location.UNKNOWN) { //The location is unknown upon world change and will be changed via /locraw soon, so we can queue it for now + armorStandQueue.add(armorStand); + return; + } + if (isLocationCorrect) handleArmorStand(armorStand); + } + + private static void handleArmorStand(ArmorStandEntity armorStand) { + for (ItemStack itemStack : armorStand.getArmorItems()) { + ItemUtils.getHeadTextureOptional(itemStack).ifPresent(texture -> { + for (EggType type : EggType.entries) { //Compare blockPos rather than entity to avoid incorrect matches when the entity just moves rather than a new one being spawned elsewhere + if (texture.equals(type.texture) && (type.egg.getValue() == null || !type.egg.getValue().entity.getBlockPos().equals(armorStand.getBlockPos()))) { + handleFoundEgg(armorStand, type); + return; + } + } + }); + } + } + + private static void invalidateState() { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; + isLocationCorrect = false; + for (EggType type : EggType.entries) { + type.egg.setValue(null); + } + } + + private static void handleFoundEgg(ArmorStandEntity entity, EggType eggType) { + eggType.egg.setValue(new Egg(entity, new Waypoint(entity.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(eggType.color)))); + + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages) return; + MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get() + .append("Found a ") + .append(Text.literal("Chocolate " + eggType + " Egg") + .withColor(eggType.color)) + .append(" at " + entity.getBlockPos().up(2).toShortString() + "!")); + } + + private static void renderWaypoints(WorldRenderContext context) { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; + for (EggType type : EggType.entries) { + Egg egg = type.egg.getValue(); + if (egg != null && egg.waypoint.shouldRender()) egg.waypoint.render(context); + } + } + + private static void onChatMessage(Text text, boolean overlay) { + if (overlay || !SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; + Matcher matcher = eggFoundPattern.matcher(text.getString()); + if (matcher.find()) { + try { + Egg egg = EggType.valueOf(matcher.group(1).toUpperCase()).egg.getValue(); + if (egg != null) egg.waypoint.setFound(); + } catch (IllegalArgumentException e) { + logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg found message. Tried to match against: " + matcher.group(0), e); + } + } + + //There's only one egg of the same type at any given time, so we can set the changed egg to null + matcher = newEggPattern.matcher(text.getString()); + if (matcher.find()) { + try { + EggType.valueOf(matcher.group(1).toUpperCase()).egg.setValue(null); + } catch (IllegalArgumentException e) { + logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg spawn message. Tried to match against: " + matcher.group(0), e); + } + } + } + + record Egg(ArmorStandEntity entity, Waypoint waypoint) { } + + enum EggType { + LUNCH(new MutableObject<>(), Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"), + DINNER(new MutableObject<>(), Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"), + BREAKFAST(new MutableObject<>(), Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0"); + + public final MutableObject<Egg> egg; + public final int color; + public final String texture; + + //This is to not create an array each time we iterate over the values + public static final ObjectImmutableList<EggType> entries = ObjectImmutableList.of(BREAKFAST, LUNCH, DINNER); + + EggType(MutableObject<Egg> egg, int color, String texture) { + this.egg = egg; + this.color = color; + this.texture = texture; + } + + @Override + public String toString() { + return switch (this) { + case LUNCH -> "Lunch"; + case DINNER -> "Dinner"; + case BREAKFAST -> "Breakfast"; + }; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java new file mode 100644 index 00000000..72cbeb2a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java @@ -0,0 +1,86 @@ +package de.hysky.skyblocker.skyblock.chocolatefactory; + +import com.mojang.brigadier.Message; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class TimeTowerReminder { + private static final String TIME_TOWER_FILE = "time_tower.txt"; + private static final Pattern TIME_TOWER_PATTERN = Pattern.compile("^TIME TOWER! Your Chocolate Factory production has increased by \\+[\\d.]+x for \\dh!$"); + private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Time Tower Reminder"); + private static boolean scheduled = false; + + private TimeTowerReminder() { + } + + public static void init() { + SkyblockEvents.JOIN.register(TimeTowerReminder::checkTempFile); + ClientReceiveMessageEvents.GAME.register(TimeTowerReminder::checkIfTimeTower); + } + + public static void checkIfTimeTower(Message message, boolean overlay) { + if (!TIME_TOWER_PATTERN.matcher(message.getString()).matches() || scheduled) return; + Scheduler.INSTANCE.schedule(TimeTowerReminder::sendMessage, 60 * 60 * 20); // 1 hour + scheduled = true; + File tempFile = SkyblockerMod.CONFIG_DIR.resolve(TIME_TOWER_FILE).toFile(); + if (!tempFile.exists()) { + try { + tempFile.createNewFile(); + } catch (IOException e) { + LOGGER.error("[Skyblocker Time Tower Reminder] Failed to create temp file for Time Tower Reminder!", e); + return; + } + } + + try (FileWriter writer = new FileWriter(tempFile)) { + writer.write(String.valueOf(System.currentTimeMillis())); + } catch (IOException e) { + LOGGER.error("[Skyblocker Time Tower Reminder] Failed to write to temp file for Time Tower Reminder!", e); + } + } + + private static void sendMessage() { + if (MinecraftClient.getInstance().player == null || !Utils.isOnSkyblock()) return; + MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.literal("Your Chocolate Factory's Time Tower has deactivated!").formatted(Formatting.RED))); + + File tempFile = SkyblockerMod.CONFIG_DIR.resolve(TIME_TOWER_FILE).toFile(); + try { + scheduled = false; + if (tempFile.exists()) Files.delete(tempFile.toPath()); + } catch (Exception e) { + LOGGER.error("[Skyblocker Time Tower Reminder] Failed to delete temp file for Time Tower Reminder!", e); + } + } + + private static void checkTempFile() { + File tempFile = SkyblockerMod.CONFIG_DIR.resolve(TIME_TOWER_FILE).toFile(); + if (!tempFile.exists() || scheduled) return; + + long time; + try (Stream<String> file = Files.lines(tempFile.toPath())) { + time = Long.parseLong(file.findFirst().orElseThrow()); + } catch (Exception e) { + LOGGER.error("[Skyblocker Time Tower Reminder] Failed to read temp file for Time Tower Reminder!", e); + return; + } + + if (System.currentTimeMillis() - time >= 60 * 60 * 1000) sendMessage(); + else Scheduler.INSTANCE.schedule(TimeTowerReminder::sendMessage, 60 * 60 * 20 - (int) ((System.currentTimeMillis() - time) / 50)); // 50 milliseconds is 1 tick + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index c6caaf41..8c083e25 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -2,7 +2,6 @@ package de.hysky.skyblocker.skyblock.item.tooltip; import com.google.gson.JsonObject; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; @@ -394,15 +393,23 @@ public class ItemTooltip { return message; } + //This is static to not create a new text object for each line in every item + private static final Text BUMPY_LINE = Text.literal("-----------------").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH); + private static void smoothenLines(List<Text> lines) { for (int i = 0; i < lines.size(); i++) { - Text line = lines.get(i); - if (line.getString().equals("-----------------")) { - lines.set(i, Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD)); + List<Text> lineSiblings = lines.get(i).getSiblings(); + //Compare the first sibling rather than the whole object as the style of the root object can change while visually staying the same + if (lineSiblings.size() == 1 && lineSiblings.getFirst().equals(BUMPY_LINE)) { + lines.set(i, createSmoothLine()); } } } + public static Text createSmoothLine() { + return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD); + } + // If these options is true beforehand, the client will get first data of these options while loading. // After then, it will only fetch the data if it is on Skyblock. public static int minute = 0; diff --git a/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java b/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java new file mode 100644 index 00000000..0196edf2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java @@ -0,0 +1,16 @@ +package de.hysky.skyblocker.utils; + +public class ColorUtils { + /** + * Takes an RGB color as an integer and returns an array of the color's components as floats, in RGB format. + * @param color The color to get the components of. + * @return An array of the color's components as floats. + */ + public static float[] getFloatComponents(int color) { + return new float[] { + ((color >> 16) & 0xFF) / 255f, + ((color >> 8) & 0xFF) / 255f, + (color & 0xFF) / 255f + }; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 1aa77080..13b28808 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -33,6 +33,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -214,6 +215,12 @@ public class ItemUtils { return texture; } + public static Optional<String> getHeadTextureOptional(ItemStack stack) { + String texture = getHeadTexture(stack); + if (texture.isBlank()) return Optional.empty(); + return Optional.of(texture); + } + public static ItemStack getSkyblockerStack() { try { ItemStack stack = new ItemStack(Items.PLAYER_HEAD); diff --git a/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java b/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java new file mode 100644 index 00000000..5b91a80b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java @@ -0,0 +1,55 @@ +package de.hysky.skyblocker.utils; + +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.regex.Matcher; + +public class RegexUtils { + /** + * @return An OptionalLong of the first group in the matcher, or an empty OptionalLong if the matcher doesn't find anything. + */ + public static OptionalLong getLongFromMatcher(Matcher matcher) { + return getLongFromMatcher(matcher, matcher.hasMatch() ? matcher.end() : 0); + } + + /** + * @return An OptionalLong of the first group in the matcher, or an empty OptionalLong if the matcher doesn't find anything. + */ + public static OptionalLong getLongFromMatcher(Matcher matcher, int startingIndex) { + if (!matcher.find(startingIndex)) return OptionalLong.empty(); + return OptionalLong.of(Long.parseLong(matcher.group(1).replace(",", ""))); + } + + /** + * @return An OptionalInt of the first group in the matcher, or an empty OptionalInt if the matcher doesn't find anything. + */ + public static OptionalInt getIntFromMatcher(Matcher matcher) { + return getIntFromMatcher(matcher, matcher.hasMatch() ? matcher.end() : 0); + } + + /** + * @return An OptionalInt of the first group in the matcher, or an empty OptionalInt if the matcher doesn't find anything. + */ + public static OptionalInt getIntFromMatcher(Matcher matcher, int startingIndex) { + if (!matcher.find(startingIndex)) return OptionalInt.empty(); + return OptionalInt.of(Integer.parseInt(matcher.group(1).replace(",", ""))); + } + + /** + * @return An OptionalDouble of the first group in the matcher, or an empty OptionalDouble if the matcher doesn't find anything. + * @implNote Assumes the decimal separator is `.` + */ + public static OptionalDouble getDoubleFromMatcher(Matcher matcher) { + return getDoubleFromMatcher(matcher, matcher.hasMatch() ? matcher.end() : 0); + } + + /** + * @return An OptionalDouble of the first group in the matcher, or an empty OptionalDouble if the matcher doesn't find anything. + * @implNote Assumes the decimal separator is `.` + */ + public static OptionalDouble getDoubleFromMatcher(Matcher matcher, int startingIndex) { + if (!matcher.find(startingIndex)) return OptionalDouble.empty(); + return OptionalDouble.of(Double.parseDouble(matcher.group(1).replace(",", ""))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java new file mode 100644 index 00000000..045ecc4e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java @@ -0,0 +1,61 @@ +package de.hysky.skyblocker.utils; + +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class SkyblockTime { + private static final long SKYBLOCK_EPOCH = 1560275700000L; + public static final AtomicInteger skyblockYear = new AtomicInteger(0); + public static final AtomicReference<Season> skyblockSeason = new AtomicReference<>(Season.SPRING); + public static final AtomicReference<Month> skyblockMonth = new AtomicReference<>(Month.EARLY_SPRING); + public static final AtomicInteger skyblockDay = new AtomicInteger(0); + private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Time"); + + private SkyblockTime() { + } + + public static void init() { + updateTime(); + //ScheduleCyclic already runs the task upon scheduling, so there's no need to call updateTime() here + Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 24), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50); + } + + private static long getSkyblockMillis() { + return System.currentTimeMillis() - SKYBLOCK_EPOCH; + } + + private static int getSkyblockYear() { + return (int) (Math.floor(getSkyblockMillis() / 446400000.0) + 1); + } + + private static int getSkyblockMonth() { + return (int) (Math.floor(getSkyblockMillis() / 37200000.0) % 12); + } + + private static int getSkyblockDay() { + return (int) (Math.floor(getSkyblockMillis() / 1200000.0) % 31 + 1); + } + + private static void updateTime() { + skyblockYear.set(getSkyblockYear()); + skyblockSeason.set(Season.values()[getSkyblockMonth() / 3]); + skyblockMonth.set(Month.values()[getSkyblockMonth()]); + skyblockDay.set(getSkyblockDay()); + LOGGER.info("[Skyblocker Time] Skyblock time updated to Year {}, Season {}, Month {}, Day {}", skyblockYear.get(), skyblockSeason.get(), skyblockMonth.get(), skyblockDay.get()); + } + + public enum Season { + SPRING, SUMMER, FALL, WINTER + } + + public enum Month { + EARLY_SPRING, SPRING, LATE_SPRING, + EARLY_SUMMER, SUMMER, LATE_SUMMER, + EARLY_FALL, FALL, LATE_FALL, + EARLY_WINTER, WINTER, LATE_WINTER + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java index 08fb6a86..8a5d32be 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java @@ -5,6 +5,7 @@ import com.mojang.blaze3d.systems.RenderSystem; import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakeBagHelper; import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakesHelper; +import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver; import de.hysky.skyblocker.skyblock.dungeon.CroesusHelper; import de.hysky.skyblocker.skyblock.dungeon.CroesusProfit; import de.hysky.skyblocker.skyblock.dungeon.terminal.ColorTerminal; @@ -55,7 +56,8 @@ public class ContainerSolverManager { new SuperpairsSolver(), UltrasequencerSolver.INSTANCE, new NewYearCakeBagHelper(), - NewYearCakesHelper.INSTANCE + NewYearCakesHelper.INSTANCE, + new ChocolateFactorySolver() }; } |