diff options
Diffstat (limited to 'src/main')
240 files changed, 8953 insertions, 1786 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index a9aab3b6..05c7fb1e 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -2,7 +2,6 @@ package de.hysky.skyblocker; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -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; @@ -26,15 +25,18 @@ import de.hysky.skyblocker.skyblock.end.EnderNodes; import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes; import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars; +import de.hysky.skyblocker.skyblock.events.EventNotifications; import de.hysky.skyblocker.skyblock.garden.FarmingHud; import de.hysky.skyblocker.skyblock.garden.LowerSensitivity; import de.hysky.skyblocker.skyblock.garden.VisitorHelper; import de.hysky.skyblocker.skyblock.item.*; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager; import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipManager; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; -import de.hysky.skyblocker.skyblock.quicknav.QuickNav; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; import de.hysky.skyblocker.skyblock.rift.TheRift; import de.hysky.skyblocker.skyblock.searchoverlay.SearchOverManager; import de.hysky.skyblocker.skyblock.shortcut.Shortcuts; @@ -46,6 +48,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.skyblock.waypoint.Waypoints; import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.chat.ChatMessageListener; import de.hysky.skyblocker.utils.discord.DiscordRPCManager; @@ -103,15 +106,17 @@ public class SkyblockerMod implements ClientModInitializer { Utils.init(); SkyblockerConfigManager.init(); SkyblockerScreen.initClass(); + ProfileViewerScreen.initClass(); Tips.init(); NEURepoManager.init(); - ImageRepoLoader.init(); + //ImageRepoLoader.init(); ItemRepository.init(); PlayerHeadHashCache.init(); HotbarSlotLock.init(); ItemTooltip.init(); AccessoriesHelper.init(); WikiLookup.init(); + Waypoints.init(); FairySouls.init(); Relics.init(); MythologicalRitual.init(); @@ -170,13 +175,15 @@ public class SkyblockerMod implements ClientModInitializer { VisitorHelper.init(); ItemRarityBackgrounds.init(); MuseumItemCache.init(); + PetCache.init(); SecretsTracker.init(); + ApiAuthentication.init(); ApiUtils.init(); - ProfileUtils.init(); Debug.init(); Kuudra.init(); RenderHelper.init(); FancyStatusBars.init(); + EventNotifications.init(); containerSolverManager.init(); statusBarTracker.init(); BeaconHighlighter.init(); @@ -185,6 +192,8 @@ public class SkyblockerMod implements ClientModInitializer { EggFinder.init(); TimeTowerReminder.init(); SkyblockTime.init(); + TooltipManager.init(); + SlotTextManager.init(); Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20); Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 200); diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java b/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java index 15c855a0..0196a37a 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java @@ -41,7 +41,7 @@ public class SkyblockerScreen extends Screen { static { LocalDate date = LocalDate.now(); - ICON = date.getMonthValue() == 4 && date.getDayOfMonth() == 1 ? new Identifier(SkyblockerMod.NAMESPACE, "icons.png") : new Identifier(SkyblockerMod.NAMESPACE, "icon.png"); + ICON = date.getMonthValue() == 4 && date.getDayOfMonth() == 1 ? Identifier.of(SkyblockerMod.NAMESPACE, "icons.png") : Identifier.of(SkyblockerMod.NAMESPACE, "icon.png"); } private SkyblockerScreen() { diff --git a/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java b/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java index 8dfc5dc9..c02300ec 100644 --- a/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java +++ b/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java @@ -16,9 +16,9 @@ import net.minecraft.util.Identifier; * EMI integration */ public class SkyblockerEMIPlugin implements EmiPlugin { - public static final Identifier SIMPLIFIED_TEXTURES = new Identifier("emi", "textures/gui/widgets.png"); + public static final Identifier SIMPLIFIED_TEXTURES = Identifier.of("emi", "textures/gui/widgets.png"); // TODO: Custom simplified texture for Skyblock - public static final EmiRecipeCategory SKYBLOCK = new EmiRecipeCategory(new Identifier(SkyblockerMod.NAMESPACE, "skyblock"), EmiStack.of(ItemUtils.getSkyblockerStack()), new EmiTexture(SIMPLIFIED_TEXTURES, 240, 240, 16, 16)); + public static final EmiRecipeCategory SKYBLOCK = new EmiRecipeCategory(Identifier.of(SkyblockerMod.NAMESPACE, "skyblock"), EmiStack.of(ItemUtils.getSkyblockerStack()), new EmiTexture(SIMPLIFIED_TEXTURES, 240, 240, 16, 16)); @Override public void register(EmiRegistry registry) { diff --git a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java index 781f7f15..ee8dfb19 100644 --- a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java +++ b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java @@ -52,7 +52,7 @@ public class ConfigUtils { public static OptionDescription withImage(Path imagePath, @Nullable Text... texts) { return OptionDescription.createBuilder() .text(ArrayUtils.isNotEmpty(texts) ? texts : new Text[] {}) - .image(IMAGE_DIRECTORY.resolve(imagePath), new Identifier(SkyblockerMod.NAMESPACE, "config_image_" + FileUtils.normalizePath(imagePath))) + .image(IMAGE_DIRECTORY.resolve(imagePath), Identifier.of(SkyblockerMod.NAMESPACE, "config_image_" + FileUtils.normalizePath(imagePath))) .build(); } } diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index 9c495382..c9246599 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -44,5 +44,8 @@ public class SkyblockerConfig { public QuickNavigationConfig quickNav = new QuickNavigationConfig(); @SerialEntry + public EventNotificationsConfig eventNotifications = new EventNotificationsConfig(); + + @SerialEntry public MiscConfig misc = new MiscConfig(); } diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java index dd406b8a..f519473c 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java @@ -83,6 +83,7 @@ public class SkyblockerConfigManager { .category(SlayersCategory.create(defaults, config)) .category(ChatCategory.create(defaults, config)) .category(QuickNavigationCategory.create(defaults, config)) + .category(EventNotificationsCategory.create(defaults, config)) .category(MiscCategory.create(defaults, config))).generateScreen(parent); } diff --git a/src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java new file mode 100644 index 00000000..ae4882f2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java @@ -0,0 +1,75 @@ +package de.hysky.skyblocker.config.categories; + +import de.hysky.skyblocker.config.ConfigUtils; +import de.hysky.skyblocker.config.SkyblockerConfig; +import de.hysky.skyblocker.config.configs.EventNotificationsConfig; +import de.hysky.skyblocker.skyblock.events.EventNotifications; +import de.hysky.skyblocker.utils.config.DurationController; +import dev.isxander.yacl3.api.*; +import it.unimi.dsi.fastutil.ints.IntImmutableList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class EventNotificationsCategory { + + private static boolean shouldPlaySound = false; + + public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig config) { + shouldPlaySound = false; + return ConfigCategory.createBuilder() + .name(Text.translatable("skyblocker.config.eventNotifications")) + .option(Option.<EventNotificationsConfig.Criterion>createBuilder() + .binding(defaults.eventNotifications.criterion, + () -> config.eventNotifications.criterion, + criterion -> config.eventNotifications.criterion = criterion) + .controller(ConfigUtils::createEnumCyclingListController) + .name(Text.translatable("skyblocker.config.eventNotifications.criterion")) + .build()) + .option(Option.<EventNotificationsConfig.Sound>createBuilder() + .binding(defaults.eventNotifications.reminderSound, + () -> config.eventNotifications.reminderSound, + sound -> config.eventNotifications.reminderSound = sound) + .controller(ConfigUtils::createEnumCyclingListController) + .name(Text.translatable("skyblocker.config.eventNotifications.notificationSound")) + .listener((soundOption, sound) -> { + if (!shouldPlaySound) { + shouldPlaySound = true; + return; + } + if (sound.getSoundEvent() != null) + MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(sound.getSoundEvent(), 1f, 1f)); + }) + .build()) + .groups(createGroups(config)) + .build(); + + } + + private static List<OptionGroup> createGroups(SkyblockerConfig config) { + Map<String, IntList> eventsReminderTimes = config.eventNotifications.eventsReminderTimes; + List<OptionGroup> groups = new ArrayList<>(eventsReminderTimes.size()); + if (eventsReminderTimes.isEmpty()) return List.of(OptionGroup.createBuilder().option(LabelOption.create(Text.translatable("skyblocker.config.eventNotifications.monologue"))).build()); + for (Map.Entry<String, IntList> entry : eventsReminderTimes.entrySet()) { + groups.add(ListOption.<Integer>createBuilder() + .name(Text.literal(entry.getKey())) + .binding(EventNotifications.DEFAULT_REMINDERS, entry::getValue, integers -> entry.setValue(new IntImmutableList(integers))) + .controller(option -> () -> new DurationController(option)) // yea + .description(OptionDescription.of(Text.translatable("skyblocker.config.eventNotifications.@Tooltip[0]"), + Text.empty(), + Text.translatable("skyblocker.config.eventNotifications.@Tooltip[1]"), + Text.empty(), + Text.translatable("skyblocker.config.eventNotifications.@Tooltip[2]", entry.getKey()))) + .initial(60) + .collapsed(true) + .build() + ); + } + return groups; + } +} diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java index 1477d669..fa87be3d 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -211,6 +211,14 @@ public class GeneralCategory { .name(Text.translatable("skyblocker.config.general.itemInfoDisplay")) .collapsed(true) .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.general.itemInfoDisplay.slotText")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.general.itemInfoDisplay.slotText.@Tooltip"))) + .binding(defaults.general.itemInfoDisplay.slotText, + () -> config.general.itemInfoDisplay.slotText, + newValue -> config.general.itemInfoDisplay.slotText = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() .name(Text.translatable("skyblocker.config.general.itemInfoDisplay.attributeShardInfo")) .description(OptionDescription.of(Text.translatable("skyblocker.config.general.itemInfoDisplay.attributeShardInfo.@Tooltip"))) .binding(defaults.general.itemInfoDisplay.attributeShardInfo, diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index 369a9a90..a2a0f815 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.config.categories; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.skyblock.fancybars.StatusBarsConfigScreen; +import de.hysky.skyblocker.skyblock.waypoint.WaypointsScreen; import de.hysky.skyblocker.utils.render.title.TitleContainerConfigScreen; import de.hysky.skyblocker.config.configs.UIAndVisualsConfig; import de.hysky.skyblocker.utils.waypoint.Waypoint; @@ -226,12 +227,17 @@ public class UIAndVisualsCategory { .option(Option.<Waypoint.Type>createBuilder() .name(Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType")) .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType.@Tooltip"), - Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType"))) + Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType.generalNote"))) .binding(defaults.uiAndVisuals.waypoints.waypointType, () -> config.uiAndVisuals.waypoints.waypointType, newValue -> config.uiAndVisuals.waypoints.waypointType = newValue) .controller(ConfigUtils::createEnumCyclingListController) .build()) + .option(ButtonOption.createBuilder() + .name(Text.translatable("skyblocker.waypoints.config")) + .text(Text.translatable("text.skyblocker.open")) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new WaypointsScreen(screen))) + .build()) .build()) //Teleport Overlays diff --git a/src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java new file mode 100644 index 00000000..1fa7016c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java @@ -0,0 +1,44 @@ +package de.hysky.skyblocker.config.configs; + +import dev.isxander.yacl3.config.v2.api.SerialEntry; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; + +import java.util.HashMap; +import java.util.Map; + +public class EventNotificationsConfig { + @SerialEntry + public Criterion criterion = Criterion.SKYBLOCK; + + @SerialEntry + public Sound reminderSound = Sound.PLING; + + @SerialEntry + public Map<String, IntList> eventsReminderTimes = new HashMap<>(); + + public enum Criterion { + NONE, + SKYBLOCK, + HYPIXEL, + EVERYWHERE + } + + public enum Sound { + NONE(null), + BELL(SoundEvents.BLOCK_BELL_USE), + DING(SoundEvents.ENTITY_ARROW_HIT_PLAYER), + PLING(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value()), + GOAT(SoundEvents.GOAT_HORN_SOUNDS.getFirst().value()); + + public SoundEvent getSoundEvent() { + return soundEvent; + } + + final SoundEvent soundEvent; + Sound(SoundEvent soundEvent) { + this.soundEvent = soundEvent; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java index 56e110b6..9f612028 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java @@ -141,6 +141,9 @@ public class GeneralConfig { public static class ItemInfoDisplay { @SerialEntry + public boolean slotText = true; + + @SerialEntry public boolean attributeShardInfo = true; @SerialEntry @@ -154,8 +157,8 @@ public class GeneralConfig { } public enum RarityBackgroundStyle { - CIRCULAR(new Identifier(SkyblockerMod.NAMESPACE, "item_rarity_background_circular")), - SQUARE(new Identifier(SkyblockerMod.NAMESPACE, "item_rarity_background_square")); + CIRCULAR(Identifier.of(SkyblockerMod.NAMESPACE, "item_rarity_background_circular")), + SQUARE(Identifier.of(SkyblockerMod.NAMESPACE, "item_rarity_background_square")); public final Identifier tex; @@ -178,8 +181,8 @@ public class GeneralConfig { } public enum SlotLockStyle { - CLASSIC(new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/slot_lock.png")), - FANCY(new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/fancy_slot_lock.png")); + CLASSIC(Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/slot_lock.png")), + FANCY(Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/fancy_slot_lock.png")); public final Identifier tex; diff --git a/src/main/java/de/hysky/skyblocker/config/configs/QuickNavigationConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/QuickNavigationConfig.java index 20a0c9cc..e8e9c27f 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/QuickNavigationConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/QuickNavigationConfig.java @@ -91,7 +91,7 @@ public class QuickNavigationConfig { } public ItemData(String item, int count, String components) { - this(Registries.ITEM.get(new Identifier(item)), count, components); + this(Registries.ITEM.get(Identifier.ofVanilla(item)), count, components); } public ItemData(Item item) { diff --git a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java index f4e4aad1..b887d415 100644 --- a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java +++ b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java @@ -69,7 +69,7 @@ public class ConfigDataFixer { Schema schema3 = builder.addSchema(3, Schema::new); builder.addFixer(new ConfigFix2QuickNav(schema3, true)); - return builder.buildUnoptimized(); + return builder.build().fixer(); } private static JsonObject loadConfig(Path path) { diff --git a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix2QuickNav.java b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix2QuickNav.java index bd67b1b0..48dea0a7 100644 --- a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix2QuickNav.java +++ b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix2QuickNav.java @@ -33,6 +33,6 @@ public class ConfigFix2QuickNav extends ConfigDataFix { } private <T> Dynamic<T> fixButton(Dynamic<T> button) { - return button.renameAndFixField("item", "itemData", itemData -> itemData.renameAndFixField("id", "item", id -> id.createString(new Identifier(id.asString().getOrThrow()).toString()))); + return button.renameAndFixField("item", "itemData", itemData -> itemData.renameAndFixField("id", "item", id -> id.createString(Identifier.of(id.asString().getOrThrow()).toString()))); } } diff --git a/src/main/java/de/hysky/skyblocker/events/HudRenderEvents.java b/src/main/java/de/hysky/skyblocker/events/HudRenderEvents.java index 13e70498..f6ee1ff5 100644 --- a/src/main/java/de/hysky/skyblocker/events/HudRenderEvents.java +++ b/src/main/java/de/hysky/skyblocker/events/HudRenderEvents.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.events; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderTickCounter; /** * HUD render events that allow for proper layering between different HUD elements. @@ -43,8 +44,8 @@ public class HudRenderEvents { * Called sometime during a specific HUD render stage. * * @param drawContext The {@link DrawContext} instance - * @param tickDelta Progress for linearly interpolating between the previous and current game state + * @param tickCounter The {@link RenderTickCounter} instance */ - void onRender(DrawContext context, float tickDelta); + void onRender(DrawContext context, RenderTickCounter tickCounter); } } diff --git a/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java b/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java new file mode 100644 index 00000000..2f54917b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java @@ -0,0 +1,20 @@ +package de.hysky.skyblocker.injected; + +import org.jetbrains.annotations.Nullable; + +public interface SkyblockerStack { + @Nullable + default String getSkyblockId() { + return ""; + } + + @Nullable + default String getSkyblockApiId() { + return ""; + } + + @Nullable + default String getNeuName() { + return ""; + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java index 8fa03cdc..199f5c26 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java @@ -11,7 +11,7 @@ import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.component.ComponentHolder; -import net.minecraft.component.DataComponentType; +import net.minecraft.component.ComponentType; import net.minecraft.component.DataComponentTypes; import net.minecraft.item.ItemStack; import net.minecraft.item.trim.ArmorTrim; @@ -21,7 +21,7 @@ public interface ComponentHolderMixin { @SuppressWarnings("unchecked") @ModifyReturnValue(method = "get", at = @At("RETURN")) - private <T> T skyblocker$customArmorTrims(T original, DataComponentType<? extends T> dataComponentType) { + private <T> T skyblocker$customArmorTrims(T original, ComponentType<? extends T> dataComponentType) { if (Utils.isOnSkyblock() && ((Object) this) instanceof ItemStack stack) { if (dataComponentType == DataComponentTypes.TRIM) { Object2ObjectOpenHashMap<String, CustomArmorTrims.ArmorTrimId> customTrims = SkyblockerConfigManager.get().general.customArmorTrims; diff --git a/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java b/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java index 7964b114..9f09c37a 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java @@ -2,67 +2,15 @@ package de.hysky.skyblocker.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; -import com.llamalad7.mixinextras.sugar.ref.LocalRef; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.AttributeShards; import de.hysky.skyblocker.skyblock.item.ItemCooldowns; -import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; -import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.util.Formatting; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(DrawContext.class) public abstract class DrawContextMixin { - @Shadow - @Final - private MatrixStack matrices; - - @Shadow - public abstract int drawText(TextRenderer textRenderer, @Nullable String text, int x, int y, int color, boolean shadow); - - @Inject(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At("HEAD")) - private void skyblocker$renderAttributeShardDisplay(CallbackInfo ci, @Local(argsOnly = true) TextRenderer textRenderer, @Local(argsOnly = true) ItemStack stack, @Local(argsOnly = true, ordinal = 0) int x, @Local(argsOnly = true, ordinal = 1) int y, @Local(argsOnly = true) LocalRef<String> countOverride) { - if (!SkyblockerConfigManager.get().general.itemInfoDisplay.attributeShardInfo) return; - - if (Utils.isOnSkyblock()) { - NbtCompound customData = ItemUtils.getCustomData(stack); - - if (ItemUtils.getItemId(stack).equals("ATTRIBUTE_SHARD")) { - NbtCompound attributesTag = customData.getCompound("attributes"); - String[] attributes = attributesTag.getKeys().toArray(String[]::new); - - if (attributes.length != 0) { - String attributeId = attributes[0]; - int attributeLevel = attributesTag.getInt(attributeId); - - //Set item count - countOverride.set(Integer.toString(attributeLevel)); - - //Draw the attribute name - this.matrices.push(); - this.matrices.translate(0f, 0f, 200f); - - String attributeInitials = AttributeShards.getShortName(attributeId); - - this.drawText(textRenderer, attributeInitials, x, y, Formatting.AQUA.getColorValue(), true); - - this.matrices.pop(); - } - } - } - } - @ModifyExpressionValue(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/ItemCooldownManager;getCooldownProgress(Lnet/minecraft/item/Item;F)F")) private float skyblocker$modifyItemCooldown(float cooldownProgress, @Local(argsOnly = true) ItemStack stack) { diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index f662be7c..f2e3e907 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -4,6 +4,7 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.systems.RenderSystem; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.PetCache; import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver; import de.hysky.skyblocker.skyblock.experiment.ExperimentSolver; import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver; @@ -11,7 +12,10 @@ import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver; import de.hysky.skyblocker.skyblock.garden.VisitorHelper; import de.hysky.skyblocker.skyblock.item.ItemProtection; import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds; +import de.hysky.skyblocker.skyblock.item.MuseumItemCache; import de.hysky.skyblocker.skyblock.item.WikiLookup; +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; import de.hysky.skyblocker.skyblock.item.tooltip.CompactorDeletorPreview; import de.hysky.skyblocker.skyblock.quicknav.QuickNav; @@ -22,6 +26,7 @@ import de.hysky.skyblocker.utils.render.gui.ContainerSolver; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.util.math.MatrixStack; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -33,6 +38,7 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -50,251 +56,299 @@ import java.util.regex.Matcher; @Mixin(HandledScreen.class) public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen { - /** - * This is the slot id returned for when a click is outside the screen's bounds - */ - @Unique - private static final int OUT_OF_BOUNDS_SLOT = -999; - - @Unique - private static final Identifier ITEM_PROTECTION = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); - - @Unique - private static final Set<String> FILLER_ITEMS = Set.of( - " ", // Empty menu item - "Locked Page", - "Quick Crafting Slot", - "Locked Backpack Slot 2", //Regular expressions won't be utilized here since the search by contains is based on plain text rather than regex syntax - "Locked Backpack Slot 3", - "Locked Backpack Slot 4", - "Locked Backpack Slot 5", - "Locked Backpack Slot 6", - "Locked Backpack Slot 7", - "Locked Backpack Slot 8", - "Locked Backpack Slot 9", - "Locked Backpack Slot 10", - "Locked Backpack Slot 11", - "Locked Backpack Slot 12", - "Locked Backpack Slot 13", - "Locked Backpack Slot 14", - "Locked Backpack Slot 15", - "Locked Backpack Slot 16", - "Locked Backpack Slot 17", - "Locked Backpack Slot 18", - "Preparing" - ); - - @Shadow - @Nullable - protected Slot focusedSlot; - - @Shadow - @Final - protected T handler; - - @Unique - private List<QuickNavButton> quickNavButtons; - - protected HandledScreenMixin(Text title) { - super(title); - } - - @Inject(method = "init", at = @At("RETURN")) - private void skyblocker$initQuickNav(CallbackInfo ci) { - if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().quickNav.enableQuickNav && client != null && client.player != null && !client.player.isCreative()) { - for (QuickNavButton quickNavButton : quickNavButtons = QuickNav.init(getTitle().getString().trim())) { - addSelectableChild(quickNavButton); - } - } - } - - @Inject(at = @At("HEAD"), method = "keyPressed") - public void skyblocker$keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) { - if (this.client != null && this.focusedSlot != null && keyCode != 256) { - //wiki lookup - if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && client.player != null) { - WikiLookup.openWiki(this.focusedSlot, client.player); - } - //item protection - if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && ItemProtection.itemProtection.matchesKey(keyCode, scanCode)) { - ItemProtection.handleKeyPressed(this.focusedSlot.getStack()); - } - } - } - - @Inject(at = @At("HEAD"), method = "mouseClicked") - public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) { - if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) { - VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer); - } - } - - /** - * Draws the unselected tabs in front of the background blur, but behind the main inventory, similar to creative inventory tabs - */ - @Inject(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawBackground(Lnet/minecraft/client/gui/DrawContext;FII)V")) - private void skyblocker$drawUnselectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) { - if (!quickNavButton.toggled()) { - quickNavButton.render(context, mouseX, mouseY, delta); - } - } - } - - /** - * Draws the selected tab in front of the background blur and the main inventory, similar to creative inventory tabs - */ - @Inject(method = "renderBackground", at = @At("RETURN")) - private void skyblocker$drawSelectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) { - if (quickNavButton.toggled()) { - quickNavButton.render(context, mouseX, mouseY, delta); - } - } - } - - @SuppressWarnings("DataFlowIssue") - // makes intellij be quiet about this.focusedSlot maybe being null. It's already null checked in mixined method. - @Inject(method = "drawMouseoverTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;II)V"), cancellable = true) - public void skyblocker$drawMouseOverTooltip(DrawContext context, int x, int y, CallbackInfo ci, @Local(ordinal = 0) ItemStack stack) { - if (!Utils.isOnSkyblock()) return; - - // Hide Empty Tooltips - if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && stack.getName().getString().equals(" ")) { - ci.cancel(); - } - - // Backpack Preview - boolean shiftDown = SkyblockerConfigManager.get().uiAndVisuals.backpackPreviewWithoutShift ^ Screen.hasShiftDown(); - if (shiftDown && getTitle().getString().equals("Storage") && focusedSlot.inventory != client.player.getInventory() && BackpackPreview.renderPreview(context, this, focusedSlot.getIndex(), x, y)) { - ci.cancel(); - } - - // Compactor Preview - if (SkyblockerConfigManager.get().uiAndVisuals.compactorDeletorPreview) { - Matcher matcher = CompactorDeletorPreview.NAME.matcher(ItemUtils.getItemId(stack)); - if (matcher.matches() && CompactorDeletorPreview.drawPreview(context, stack, matcher.group("type"), matcher.group("size"), x, y)) { - ci.cancel(); - } - } - } - - @ModifyVariable(method = "drawMouseoverTooltip", at = @At(value = "LOAD", ordinal = 0)) - private ItemStack skyblocker$experimentSolvers$replaceTooltipDisplayStack(ItemStack stack) { - return skyblocker$experimentSolvers$getStack(focusedSlot, stack); - } - - @ModifyVariable(method = "drawSlot", at = @At(value = "LOAD", ordinal = 3), ordinal = 0) - private ItemStack skyblocker$experimentSolvers$replaceDisplayStack(ItemStack stack, DrawContext context, Slot slot) { - return skyblocker$experimentSolvers$getStack(slot, stack); - } - - /** - * Redirects getStack calls to account for different stacks in experiment solvers. - */ - @Unique - private ItemStack skyblocker$experimentSolvers$getStack(Slot slot, @NotNull ItemStack stack) { - ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver(); - if ((currentSolver instanceof SuperpairsSolver || currentSolver instanceof UltrasequencerSolver) && ((ExperimentSolver) currentSolver).getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) { - ItemStack itemStack = ((ExperimentSolver) currentSolver).getSlots().get(slot.getIndex()); - return itemStack == null ? stack : itemStack; - } - return stack; - } - - /** - * The naming of this method in yarn is half true, its mostly to handle slot/item interactions (which are mouse or keyboard clicks) - * For example, using the drop key bind while hovering over an item will invoke this method to drop the players item - * - * @implNote This runs before {@link ScreenHandler#onSlotClick(int, int, SlotActionType, net.minecraft.entity.player.PlayerEntity)} - */ - @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;clickSlot(IIILnet/minecraft/screen/slot/SlotActionType;Lnet/minecraft/entity/player/PlayerEntity;)V"), cancellable = true) - private void skyblocker$onSlotClick(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { - if (!Utils.isOnSkyblock()) return; - - // Item Protection - // When you try and drop the item by picking it up then clicking outside the screen - if (slotId == OUT_OF_BOUNDS_SLOT && ItemProtection.isItemProtected(this.handler.getCursorStack())) { - ci.cancel(); - return; - } - - if (slot == null) return; - String title = getTitle().getString(); - ItemStack stack = skyblocker$experimentSolvers$getStack(slot, slot.getStack()); - ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver(); - - // Prevent clicks on filler items - if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && FILLER_ITEMS.contains(stack.getName().getString()) && - // Allow clicks in Ultrasequencer and Superpairs - (!UltrasequencerSolver.INSTANCE.getName().matcher(title).matches() || SkyblockerConfigManager.get().helpers.experiments.enableUltrasequencerSolver)) { - ci.cancel(); - return; - } - // Item Protection - // When you click your drop key while hovering over an item - if (actionType == SlotActionType.THROW && ItemProtection.isItemProtected(stack)) { - ci.cancel(); - return; - } - // Prevent salvaging - if (title.equals("Salvage Items") && ItemProtection.isItemProtected(stack)) { - ci.cancel(); - return; - } - if (this.handler instanceof GenericContainerScreenHandler genericContainerScreenHandler && genericContainerScreenHandler.getRows() == 6) { - VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack()); - - // Prevent selling to NPC shops - ItemStack sellStack = this.handler.slots.get(49).getStack(); - if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) { - if (slotId != 49 && ItemProtection.isItemProtected(stack)) { - ci.cancel(); - return; - } - } - } - - if (currentSolver != null) { - boolean disallowed = SkyblockerMod.getInstance().containerSolverManager.onSlotClick(slotId, stack); - - if (disallowed) ci.cancel(); - } - - // Experiment Solvers - if (currentSolver instanceof ExperimentSolver experimentSolver && experimentSolver.getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) { - switch (experimentSolver) { - case ChronomatronSolver chronomatronSolver -> { - Item item = chronomatronSolver.getChronomatronSlots().get(chronomatronSolver.getChronomatronCurrentOrdinal()); - if ((stack.isOf(item) || ChronomatronSolver.TERRACOTTA_TO_GLASS.get(stack.getItem()) == item) && chronomatronSolver.incrementChronomatronCurrentOrdinal() >= chronomatronSolver.getChronomatronSlots().size()) { - chronomatronSolver.setState(ExperimentSolver.State.END); - } - } - - case SuperpairsSolver superpairsSolver -> { - superpairsSolver.setSuperpairsPrevClickedSlot(slot.getIndex()); - superpairsSolver.setSuperpairsCurrentSlot(ItemStack.EMPTY); - } - - case UltrasequencerSolver ultrasequencerSolver when slot.getIndex() == ultrasequencerSolver.getUltrasequencerNextSlot() -> { - int count = ultrasequencerSolver.getSlots().get(ultrasequencerSolver.getUltrasequencerNextSlot()).getCount() + 1; - ultrasequencerSolver.getSlots().entrySet().stream().filter(entry -> entry.getValue().getCount() == count).findAny().map(Map.Entry::getKey).ifPresentOrElse(ultrasequencerSolver::setUltrasequencerNextSlot, () -> ultrasequencerSolver.setState(ExperimentSolver.State.END)); - } - - default -> { /*Do Nothing*/ } - } - } - } - - @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V")) - private void skyblocker$drawItemRarityBackground(DrawContext context, Slot slot, CallbackInfo ci) { - if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) - ItemRarityBackgrounds.tryDraw(slot.getStack(), context, slot.x, slot.y); - // Item protection - if (ItemProtection.isItemProtected(slot.getStack())) { - RenderSystem.enableBlend(); - context.drawTexture(ITEM_PROTECTION, slot.x, slot.y, 0, 0, 16, 16, 16, 16); - RenderSystem.disableBlend(); - } - } + /** + * This is the slot id returned for when a click is outside the screen's bounds + */ + @Unique + private static final int OUT_OF_BOUNDS_SLOT = -999; + + @Unique + private static final Identifier ITEM_PROTECTION = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); + + @Unique + private static final Set<String> FILLER_ITEMS = Set.of( + " ", // Empty menu item + "Locked Page", + "Quick Crafting Slot", + "Locked Backpack Slot 2", //Regular expressions won't be utilized here since the search by contains is based on plain text rather than regex syntax + "Locked Backpack Slot 3", + "Locked Backpack Slot 4", + "Locked Backpack Slot 5", + "Locked Backpack Slot 6", + "Locked Backpack Slot 7", + "Locked Backpack Slot 8", + "Locked Backpack Slot 9", + "Locked Backpack Slot 10", + "Locked Backpack Slot 11", + "Locked Backpack Slot 12", + "Locked Backpack Slot 13", + "Locked Backpack Slot 14", + "Locked Backpack Slot 15", + "Locked Backpack Slot 16", + "Locked Backpack Slot 17", + "Locked Backpack Slot 18", + "Preparing" + ); + + @Shadow + @Nullable + protected Slot focusedSlot; + + @Shadow + @Final + protected T handler; + + @Shadow + protected abstract List<Text> getTooltipFromItem(ItemStack stack); + + @Unique + private List<QuickNavButton> quickNavButtons; + + protected HandledScreenMixin(Text title) { + super(title); + } + + @Inject(method = "init", at = @At("RETURN")) + private void skyblocker$initQuickNav(CallbackInfo ci) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().quickNav.enableQuickNav && client != null && client.player != null && !client.player.isCreative()) { + for (QuickNavButton quickNavButton : quickNavButtons = QuickNav.init(getTitle().getString().trim())) { + addSelectableChild(quickNavButton); + } + } + } + + @Inject(at = @At("HEAD"), method = "keyPressed") + public void skyblocker$keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) { + if (this.client != null && this.focusedSlot != null && keyCode != 256) { + //wiki lookup + if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && client.player != null) { + WikiLookup.openWiki(this.focusedSlot, client.player); + } + //item protection + if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && ItemProtection.itemProtection.matchesKey(keyCode, scanCode)) { + ItemProtection.handleKeyPressed(this.focusedSlot.getStack()); + } + } + } + + @Inject(at = @At("HEAD"), method = "mouseClicked") + public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) { + if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) { + VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer); + } + } + + /** + * Draws the unselected tabs in front of the background blur, but behind the main inventory, similar to creative inventory tabs + */ + @Inject(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawBackground(Lnet/minecraft/client/gui/DrawContext;FII)V")) + private void skyblocker$drawUnselectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) { + if (!quickNavButton.toggled()) { + quickNavButton.render(context, mouseX, mouseY, delta); + } + } + } + + /** + * Draws the selected tab in front of the background blur and the main inventory, similar to creative inventory tabs + */ + @Inject(method = "renderBackground", at = @At("RETURN")) + private void skyblocker$drawSelectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) { + if (quickNavButton.toggled()) { + quickNavButton.render(context, mouseX, mouseY, delta); + } + } + } + + @SuppressWarnings("DataFlowIssue") + // makes intellij be quiet about this.focusedSlot maybe being null. It's already null checked in mixined method. + @Inject(method = "drawMouseoverTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;II)V"), cancellable = true) + public void skyblocker$drawMouseOverTooltip(DrawContext context, int x, int y, CallbackInfo ci, @Local(ordinal = 0) ItemStack stack) { + if (!Utils.isOnSkyblock()) return; + + // Hide Empty Tooltips + if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && stack.getName().getString().equals(" ")) { + ci.cancel(); + } + + // Backpack Preview + boolean shiftDown = SkyblockerConfigManager.get().uiAndVisuals.backpackPreviewWithoutShift ^ Screen.hasShiftDown(); + if (shiftDown && getTitle().getString().equals("Storage") && focusedSlot.inventory != client.player.getInventory() && BackpackPreview.renderPreview(context, this, focusedSlot.getIndex(), x, y)) { + ci.cancel(); + } + + // Compactor Preview + if (SkyblockerConfigManager.get().uiAndVisuals.compactorDeletorPreview) { + Matcher matcher = CompactorDeletorPreview.NAME.matcher(ItemUtils.getItemId(stack)); + if (matcher.matches() && CompactorDeletorPreview.drawPreview(context, stack, getTooltipFromItem(stack), matcher.group("type"), matcher.group("size"), x, y)) { + ci.cancel(); + } + } + } + + @ModifyVariable(method = "drawMouseoverTooltip", at = @At(value = "LOAD", ordinal = 0)) + private ItemStack skyblocker$experimentSolvers$replaceTooltipDisplayStack(ItemStack stack) { + return skyblocker$experimentSolvers$getStack(focusedSlot, stack); + } + + @ModifyVariable(method = "drawSlot", at = @At(value = "LOAD", ordinal = 3), ordinal = 0) + private ItemStack skyblocker$experimentSolvers$replaceDisplayStack(ItemStack stack, DrawContext context, Slot slot) { + return skyblocker$experimentSolvers$getStack(slot, stack); + } + + /** + * Redirects getStack calls to account for different stacks in experiment solvers. + */ + @Unique + private ItemStack skyblocker$experimentSolvers$getStack(Slot slot, @NotNull ItemStack stack) { + ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver(); + if ((currentSolver instanceof SuperpairsSolver || currentSolver instanceof UltrasequencerSolver) && ((ExperimentSolver) currentSolver).getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) { + ItemStack itemStack = ((ExperimentSolver) currentSolver).getSlots().get(slot.getIndex()); + return itemStack == null ? stack : itemStack; + } + return stack; + } + + /** + * The naming of this method in yarn is half true, its mostly to handle slot/item interactions (which are mouse or keyboard clicks) + * For example, using the drop key bind while hovering over an item will invoke this method to drop the players item + * + * @implNote This runs before {@link ScreenHandler#onSlotClick(int, int, SlotActionType, net.minecraft.entity.player.PlayerEntity)} + */ + @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;clickSlot(IIILnet/minecraft/screen/slot/SlotActionType;Lnet/minecraft/entity/player/PlayerEntity;)V"), cancellable = true) + private void skyblocker$onSlotClick(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { + if (!Utils.isOnSkyblock()) return; + + // Item Protection + // When you try and drop the item by picking it up then clicking outside the screen + if (slotId == OUT_OF_BOUNDS_SLOT && ItemProtection.isItemProtected(this.handler.getCursorStack())) { + ci.cancel(); + return; + } + + if (slot == null) return; + String title = getTitle().getString(); + ItemStack stack = skyblocker$experimentSolvers$getStack(slot, slot.getStack()); + ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver(); + + // Prevent clicks on filler items + if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && FILLER_ITEMS.contains(stack.getName().getString()) && + // Allow clicks in Ultrasequencer and Superpairs + (!UltrasequencerSolver.INSTANCE.getName().matcher(title).matches() || SkyblockerConfigManager.get().helpers.experiments.enableUltrasequencerSolver)) { + ci.cancel(); + return; + } + // Item Protection + // When you click your drop key while hovering over an item + if (actionType == SlotActionType.THROW && ItemProtection.isItemProtected(stack)) { + ci.cancel(); + return; + } + // Prevent salvaging + if (title.equals("Salvage Items") && ItemProtection.isItemProtected(stack)) { + ci.cancel(); + return; + } + + switch (this.handler) { + case GenericContainerScreenHandler genericContainerScreenHandler when genericContainerScreenHandler.getRows() == 6 -> { + VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack()); + + // Prevent selling to NPC shops + ItemStack sellStack = this.handler.slots.get(49).getStack(); + if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) { + if (slotId != 49 && ItemProtection.isItemProtected(stack)) { + ci.cancel(); + return; + } + } + } + + case GenericContainerScreenHandler genericContainerScreenHandler when title.equals(MuseumItemCache.DONATION_CONFIRMATION_SCREEN_TITLE) -> { + //Museum Item Cache donation tracking + MuseumItemCache.handleClick(slot, slotId, genericContainerScreenHandler.slots); + } + + case null, default -> {} + } + + //Pet Caching + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && title.startsWith("Pets")) { + PetCache.handlePetEquip(slot, slotId); + } + + if (currentSolver != null) { + boolean disallowed = SkyblockerMod.getInstance().containerSolverManager.onSlotClick(slotId, stack); + + if (disallowed) ci.cancel(); + } + + // Experiment Solvers + if (currentSolver instanceof ExperimentSolver experimentSolver && experimentSolver.getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) { + switch (experimentSolver) { + case ChronomatronSolver chronomatronSolver -> { + Item item = chronomatronSolver.getChronomatronSlots().get(chronomatronSolver.getChronomatronCurrentOrdinal()); + if ((stack.isOf(item) || ChronomatronSolver.TERRACOTTA_TO_GLASS.get(stack.getItem()) == item) && chronomatronSolver.incrementChronomatronCurrentOrdinal() >= chronomatronSolver.getChronomatronSlots().size()) { + chronomatronSolver.setState(ExperimentSolver.State.END); + } + } + + case SuperpairsSolver superpairsSolver -> { + superpairsSolver.setSuperpairsPrevClickedSlot(slot.getIndex()); + superpairsSolver.setSuperpairsCurrentSlot(ItemStack.EMPTY); + } + + case UltrasequencerSolver ultrasequencerSolver when slot.getIndex() == ultrasequencerSolver.getUltrasequencerNextSlot() -> { + int count = ultrasequencerSolver.getSlots().get(ultrasequencerSolver.getUltrasequencerNextSlot()).getCount() + 1; + ultrasequencerSolver.getSlots().entrySet().stream().filter(entry -> entry.getValue().getCount() == count).findAny().map(Map.Entry::getKey).ifPresentOrElse(ultrasequencerSolver::setUltrasequencerNextSlot, () -> ultrasequencerSolver.setState(ExperimentSolver.State.END)); + } + + default -> { /*Do Nothing*/ } + } + } + } + + @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V")) + private void skyblocker$drawItemRarityBackground(DrawContext context, Slot slot, CallbackInfo ci) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) + ItemRarityBackgrounds.tryDraw(slot.getStack(), context, slot.x, slot.y); + // Item protection + if (ItemProtection.isItemProtected(slot.getStack())) { + RenderSystem.enableBlend(); + context.drawTexture(ITEM_PROTECTION, slot.x, slot.y, 0, 0, 16, 16, 16, 16); + RenderSystem.disableBlend(); + } + } + + @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")) + private void skyblocker$drawSlotText(DrawContext context, Slot slot, CallbackInfo ci) { + List<SlotText> textList = SlotTextManager.getText(slot); + if (textList.isEmpty()) return; + MatrixStack matrices = context.getMatrices(); + + for (SlotText slotText : textList) { + matrices.push(); + matrices.translate(0.0f, 0.0f, 200.0f); + int length = textRenderer.getWidth(slotText.text()); + if (length > 16) { + matrices.scale(16.0f / length, 16.0f / length, 1.0f); //Make them fit in the slot. FYI, a slot is sized 16×16. + final float x = (slot.x * length / 16.0f) - slot.x; //Save in a variable to not recalculate + switch (slotText.position()) { + case TOP_LEFT, TOP_RIGHT -> matrices.translate(x, (slot.y * length / 16.0f) - slot.y, 0.0f); + case BOTTOM_LEFT, BOTTOM_RIGHT -> matrices.translate(x, ((slot.y + 16f - textRenderer.fontHeight + 2f + 0.7f) * length / 16.0f) - slot.y, 0.0f); + } + } else { + switch (slotText.position()) { + case TOP_LEFT -> { /*Do Nothing*/ } + case TOP_RIGHT -> matrices.translate(16f - length, 0.0f, 0.0f); + case BOTTOM_LEFT -> matrices.translate(0.0f, 16f - textRenderer.fontHeight + 2f, 0.0f); + case BOTTOM_RIGHT -> matrices.translate(16f - length, 16f - textRenderer.fontHeight + 2f, 0.0f); + } + } + context.drawText(textRenderer, slotText.text(), slot.x, slot.y, 0xFFFFFF, true); + matrices.pop(); + } + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java index 7f4721a5..fffd74f9 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java @@ -43,7 +43,7 @@ public abstract class InGameHudMixin { private static final Supplier<Identifier> SLOT_LOCK_ICON = () -> SkyblockerConfigManager.get().general.itemProtection.slotLockStyle.tex; @Unique - private static final Identifier ITEM_PROTECTION = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); + private static final Identifier ITEM_PROTECTION = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); @Unique private static final Pattern DICER_TITLE_BLACKLIST = Pattern.compile(".+? DROP!"); @@ -58,7 +58,7 @@ public abstract class InGameHudMixin { @Final private LayeredDrawer layeredDrawer; - @Inject(method = "renderHotbar", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;renderHotbarItem(Lnet/minecraft/client/gui/DrawContext;IIFLnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/item/ItemStack;I)V", ordinal = 0)) + @Inject(method = "renderHotbar", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;renderHotbarItem(Lnet/minecraft/client/gui/DrawContext;IILnet/minecraft/client/render/RenderTickCounter;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/item/ItemStack;I)V", ordinal = 0)) public void skyblocker$renderHotbarItemLockOrRarityBg(CallbackInfo ci, @Local(argsOnly = true) DrawContext context, @Local(ordinal = 4, name = "m") int index, @Local(ordinal = 5, name = "n") int x, @Local(ordinal = 6, name = "o") int y, @Local PlayerEntity player) { if (Utils.isOnSkyblock()) { // slot lock @@ -141,17 +141,17 @@ public abstract class InGameHudMixin { @ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/LayeredDrawer;addLayer(Lnet/minecraft/client/gui/LayeredDrawer$Layer;)Lnet/minecraft/client/gui/LayeredDrawer;", ordinal = 2)) private LayeredDrawer.Layer skyblocker$afterMainHud(LayeredDrawer.Layer mainHudLayer) { - return (context, tickDelta) -> { - mainHudLayer.render(context, tickDelta); - HudRenderEvents.AFTER_MAIN_HUD.invoker().onRender(context, tickDelta); + return (context, tickCounter) -> { + mainHudLayer.render(context, tickCounter); + HudRenderEvents.AFTER_MAIN_HUD.invoker().onRender(context, tickCounter); }; } @ModifyArg(method = "<init>", slice = @Slice(from = @At(value = "NEW", target = "Lnet/minecraft/client/gui/LayeredDrawer;", ordinal = 1)), at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/LayeredDrawer;addLayer(Lnet/minecraft/client/gui/LayeredDrawer$Layer;)Lnet/minecraft/client/gui/LayeredDrawer;", ordinal = 5)) private LayeredDrawer.Layer skyblocker$beforeChat(LayeredDrawer.Layer beforeChatLayer) { - return (context, tickDelta) -> { - HudRenderEvents.BEFORE_CHAT.invoker().onRender(context, tickDelta); - beforeChatLayer.render(context, tickDelta); + return (context, tickCounter) -> { + HudRenderEvents.BEFORE_CHAT.invoker().onRender(context, tickCounter); + beforeChatLayer.render(context, tickCounter); }; } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java index 878a93ac..1493cf26 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java @@ -1,14 +1,26 @@ package de.hysky.skyblocker.mixins; +import com.google.gson.JsonParser; import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.mojang.serialization.JsonOps; + import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.injected.SkyblockerStack; +import de.hysky.skyblocker.skyblock.PetCache.PetInfo; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.client.MinecraftClient; +import net.minecraft.component.ComponentHolder; import net.minecraft.component.type.ItemEnchantmentsComponent; import net.minecraft.item.ItemStack; -import net.minecraft.item.TooltipAppender; +import net.minecraft.item.tooltip.TooltipAppender; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -17,8 +29,11 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.Locale; +import java.util.Optional; + @Mixin(ItemStack.class) -public abstract class ItemStackMixin { +public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack { @Shadow public abstract int getDamage(); @@ -29,10 +44,19 @@ public abstract class ItemStackMixin { @Unique private int maxDamage; + @Unique + private String skyblockId; + + @Unique + private String skyblockApiId; + + @Unique + private String neuName; + @ModifyReturnValue(method = "getName", at = @At("RETURN")) private Text skyblocker$customItemNames(Text original) { if (Utils.isOnSkyblock()) { - return SkyblockerConfigManager.get().general.customItemNames.getOrDefault(ItemUtils.getItemUuid((ItemStack) (Object) this), original); + return SkyblockerConfigManager.get().general.customItemNames.getOrDefault(ItemUtils.getItemUuid(this), original); } return original; @@ -86,8 +110,8 @@ public abstract class ItemStackMixin { } @Unique - private boolean skyblocker$shouldProcess() { - return Utils.isOnSkyblock() && SkyblockerConfigManager.get().mining.enableDrillFuel && ItemUtils.hasCustomDurability((ItemStack) (Object) this); + private boolean skyblocker$shouldProcess() { // Durability bar renders atop of tooltips in ProfileViewer so disable on this screen + return !(MinecraftClient.getInstance().currentScreen instanceof ProfileViewerScreen) && Utils.isOnSkyblock() && SkyblockerConfigManager.get().mining.enableDrillFuel && ItemUtils.hasCustomDurability((ItemStack) (Object) this); } @Unique @@ -103,4 +127,146 @@ public abstract class ItemStackMixin { setDamage(durability.rightInt() - durability.leftInt()); return true; } + + @Override + @Nullable + public String getSkyblockId() { + if (skyblockId != null && !skyblockId.isEmpty()) return skyblockId; + return skyblockId = skyblocker$getSkyblockId(true); + } + + @Override + @Nullable + public String getSkyblockApiId() { + if (skyblockApiId != null && !skyblockApiId.isEmpty()) return skyblockApiId; + return skyblockApiId = skyblocker$getSkyblockId(false); + } + + @Override + @Nullable + public String getNeuName() { + if (neuName != null && !neuName.isEmpty()) return neuName; + String apiId = getSkyblockApiId(); + String id = getSkyblockId(); + if (apiId == null || id == null) return null; + + if (apiId.startsWith("ISSHINY_")) apiId = id; + + return neuName = ItemTooltip.getNeuName(id, apiId); + } + + @Unique + private String skyblocker$getSkyblockId(boolean internalIDOnly) { + NbtCompound customData = ItemUtils.getCustomData((ItemStack) (Object) this); + + if (customData == null || !customData.contains(ItemUtils.ID, NbtElement.STRING_TYPE)) { + return null; + } + String customDataString = customData.getString(ItemUtils.ID); + + if (internalIDOnly) { + return customDataString; + } + + // Transformation to API format. + //TODO future - remove this and just handle it directly for the NEU id conversion because this whole system is confusing and hard to follow + if (customData.contains("is_shiny")) { + return "ISSHINY_" + customDataString; + } + + switch (customDataString) { + case "ENCHANTED_BOOK" -> { + if (customData.contains("enchantments")) { + NbtCompound enchants = customData.getCompound("enchantments"); + Optional<String> firstEnchant = enchants.getKeys().stream().findFirst(); + String enchant = firstEnchant.orElse(""); + return "ENCHANTMENT_" + enchant.toUpperCase(Locale.ENGLISH) + "_" + enchants.getInt(enchant); + } + } + + case "PET" -> { + if (customData.contains("petInfo")) { + PetInfo petInfo = PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(customData.getString("petInfo"))).getOrThrow(); + return "LVL_1_" + petInfo.tier() + "_" + petInfo.type(); + } + } + + case "POTION" -> { + String enhanced = customData.contains("enhanced") ? "_ENHANCED" : ""; + String extended = customData.contains("extended") ? "_EXTENDED" : ""; + String splash = customData.contains("splash") ? "_SPLASH" : ""; + if (customData.contains("potion") && customData.contains("potion_level")) { + return (customData.getString("potion") + "_" + customDataString + "_" + customData.getInt("potion_level") + + enhanced + extended + splash).toUpperCase(Locale.ENGLISH); + } + } + + case "RUNE" -> { + if (customData.contains("runes")) { + NbtCompound runes = customData.getCompound("runes"); + Optional<String> firstRunes = runes.getKeys().stream().findFirst(); + String rune = firstRunes.orElse(""); + return rune.toUpperCase(Locale.ENGLISH) + "_RUNE_" + runes.getInt(rune); + } + } + + case "ATTRIBUTE_SHARD" -> { + if (customData.contains("attributes")) { + NbtCompound shards = customData.getCompound("attributes"); + Optional<String> firstShards = shards.getKeys().stream().findFirst(); + String shard = firstShards.orElse(""); + return customDataString + "-" + shard.toUpperCase(Locale.ENGLISH) + "_" + shards.getInt(shard); + } + } + + case "NEW_YEAR_CAKE" -> { + return customDataString + "_" + customData.getInt("new_years_cake"); + } + + case "PARTY_HAT_CRAB", "PARTY_HAT_CRAB_ANIMATED", "BALLOON_HAT_2024" -> { + return customDataString + "_" + customData.getString("party_hat_color").toUpperCase(Locale.ENGLISH); + } + + case "PARTY_HAT_SLOTH" -> { + return customDataString + "_" + customData.getString("party_hat_emoji").toUpperCase(Locale.ENGLISH); + } + + case "CRIMSON_HELMET", "CRIMSON_CHESTPLATE", "CRIMSON_LEGGINGS", "CRIMSON_BOOTS" -> { + NbtCompound attributes = customData.getCompound("attributes"); + + if (attributes.contains("magic_find") && attributes.contains("veteran")) { + return customDataString + "-MAGIC_FIND-VETERAN"; + } + } + + case "AURORA_HELMET", "AURORA_CHESTPLATE", "AURORA_LEGGINGS", "AURORA_BOOTS" -> { + NbtCompound attributes = customData.getCompound("attributes"); + + if (attributes.contains("mana_pool") && attributes.contains("mana_regeneration")) { + return customDataString + "-MANA_POOL-MANA_REGENERATION"; + } + } + + case "TERROR_HELMET", "TERROR_CHESTPLATE", "TERROR_LEGGINGS", "TERROR_BOOTS" -> { + NbtCompound attributes = customData.getCompound("attributes"); + + if (attributes.contains("lifeline") && attributes.contains("mana_pool")) { + return customDataString + "-LIFELINE-MANA_POOL"; + } + } + + case "MIDAS_SWORD" -> { + if (customData.getInt("winning_bid") >= 50000000) { + return customDataString + "_50M"; + } + } + + case "MIDAS_STAFF" -> { + if (customData.getInt("winning_bid") >= 100000000) { + return customDataString + "_100M"; + } + } + } + return customDataString; + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/BeaconBlockEntityRendererInvoker.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/BeaconBlockEntityRendererInvoker.java index 833cfc22..a2a98a68 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/accessors/BeaconBlockEntityRendererInvoker.java +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/BeaconBlockEntityRendererInvoker.java @@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.gen.Invoker; public interface BeaconBlockEntityRendererInvoker { @SuppressWarnings("unused") @Invoker("renderBeam") - static void renderBeam(MatrixStack matrices, VertexConsumerProvider vertexConsumers, float tickDelta, long worldTime, int yOffset, int maxY, float[] color) { + static void renderBeam(MatrixStack matrices, VertexConsumerProvider vertexConsumers, float tickDelta, long worldTime, int yOffset, int maxY, int color) { throw new UnsupportedOperationException(); } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java new file mode 100644 index 00000000..5ec4a8e8 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java @@ -0,0 +1,11 @@ +package de.hysky.skyblocker.mixins.accessors; + +import net.minecraft.client.gui.widget.CheckboxWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CheckboxWidget.class) +public interface CheckboxWidgetAccessor { + @Accessor + void setChecked(boolean checked); +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/MinecraftClientAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/MinecraftClientAccessor.java new file mode 100644 index 00000000..a750ded2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/MinecraftClientAccessor.java @@ -0,0 +1,12 @@ +package de.hysky.skyblocker.mixins.accessors; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.session.ProfileKeys; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MinecraftClient.class) +public interface MinecraftClientAccessor { + @Accessor + ProfileKeys getProfileKeys(); +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java index 7f8b2c71..908525e1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java @@ -7,13 +7,11 @@ import de.hysky.skyblocker.config.configs.UIAndVisualsConfig; import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.mixins.accessors.ScreenAccessor; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; -import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; -import it.unimi.dsi.fastutil.longs.LongBooleanPair; +import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.fabricmc.fabric.api.client.screen.v1.Screens; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; @@ -45,7 +43,7 @@ public class ChestValue { if (DUNGEON_CHESTS.contains(titleString)) { if (SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator) { ScreenEvents.afterTick(screen).register(screen_ -> - ((ScreenAccessor) screen).setTitle(getDungeonChestProfit(genericContainerScreen.getScreenHandler(), title, titleString, client)) + ((ScreenAccessor) screen).setTitle(getDungeonChestProfit(genericContainerScreen.getScreenHandler(), title, titleString)) ); } } else if (SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue && !titleString.equals("SkyBlock Menu")) { @@ -65,9 +63,9 @@ public class ChestValue { }); } - private static Text getDungeonChestProfit(GenericContainerScreenHandler handler, Text title, String titleString, MinecraftClient client) { + private static Text getDungeonChestProfit(GenericContainerScreenHandler handler, Text title, String titleString) { try { - long profit = 0; + double profit = 0; boolean hasIncompleteData = false, usedKismet = false; List<Slot> slots = handler.slots.subList(0, handler.getRows() * 9); @@ -81,16 +79,16 @@ public class ChestValue { } String name = stack.getName().getString(); - String id = ItemTooltip.getInternalNameFromNBT(stack, false); + String id = stack.getSkyblockApiId(); //Regular item price if (id != null) { - LongBooleanPair priceData = getItemPrice(id); + DoubleBooleanPair priceData = ItemUtils.getItemPrice(id); if (!priceData.rightBoolean()) hasIncompleteData = true; //Add the item price to the profit - profit += priceData.leftLong() * stack.getCount(); + profit += priceData.leftDouble() * stack.getCount(); continue; } @@ -103,12 +101,12 @@ public class ChestValue { String type = matcher.group("type"); int amount = Integer.parseInt(matcher.group("amount")); - LongBooleanPair priceData = getItemPrice(("ESSENCE_" + type).toUpperCase()); + DoubleBooleanPair priceData = ItemUtils.getItemPrice(("ESSENCE_" + type).toUpperCase()); if (!priceData.rightBoolean()) hasIncompleteData = true; //Add the price of the essence to the profit - profit += priceData.leftLong() * amount; + profit += priceData.leftDouble() * amount; continue; } @@ -116,7 +114,7 @@ public class ChestValue { //Determine the cost of the chest if (name.contains("Open Reward Chest")) { - String foundString = searchLoreFor(stack, client, "Coins"); + String foundString = searchLoreFor(stack, "Coins"); //Incase we're searching the free chest if (!StringUtils.isBlank(foundString)) { @@ -128,19 +126,19 @@ public class ChestValue { //Determine if a kismet was used or not if (name.contains("Reroll Chest")) { - usedKismet = !StringUtils.isBlank(searchLoreFor(stack, client, "You already rerolled a chest!")); + usedKismet = !StringUtils.isBlank(searchLoreFor(stack, "You already rerolled a chest!")); } } if (SkyblockerConfigManager.get().dungeons.dungeonChestProfit.includeKismet && usedKismet) { - LongBooleanPair kismetPriceData = getItemPrice("KISMET_FEATHER"); + DoubleBooleanPair kismetPriceData = ItemUtils.getItemPrice("KISMET_FEATHER"); if (!kismetPriceData.rightBoolean()) hasIncompleteData = true; - profit -= kismetPriceData.leftLong(); + profit -= kismetPriceData.leftDouble(); } - return Text.literal(titleString).append(getProfitText(profit, hasIncompleteData)); + return Text.literal(titleString).append(getProfitText((long) profit, hasIncompleteData)); } catch (Exception e) { LOGGER.error("[Skyblocker Profit Calculator] Failed to calculate dungeon chest profit! ", e); } @@ -150,7 +148,7 @@ public class ChestValue { private static Text getChestValue(GenericContainerScreenHandler handler, Text title, String titleString) { try { - long value = 0; + double value = 0; boolean hasIncompleteData = false; List<Slot> slots = handler.slots.subList(0, handler.getRows() * 9); @@ -160,18 +158,18 @@ public class ChestValue { continue; } - String id = ItemTooltip.getInternalNameFromNBT(stack, false); + String id = stack.getSkyblockApiId(); if (id != null) { - LongBooleanPair priceData = getItemPrice(id); + DoubleBooleanPair priceData = ItemUtils.getItemPrice(id); if (!priceData.rightBoolean()) hasIncompleteData = true; - value += priceData.leftLong() * stack.getCount(); + value += priceData.leftDouble() * stack.getCount(); } } - return Text.literal(titleString).append(getValueText(value, hasIncompleteData)); + return Text.literal(titleString).append(getValueText((long) value, hasIncompleteData)); } catch (Exception e) { LOGGER.error("[Skyblocker Value Calculator] Failed to calculate dungeon chest value! ", e); } @@ -180,33 +178,9 @@ public class ChestValue { } /** - * @return An {@link LongBooleanPair} with the {@code left long} representing the item's price, and the {@code right boolean} indicating if the price - * was based on complete data. - */ - private static LongBooleanPair getItemPrice(String id) { - JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData(); - JsonObject lbinPrices = TooltipInfoType.LOWEST_BINS.getData(); - - if (bazaarPrices == null || lbinPrices == null) return LongBooleanPair.of(0L, false); - - if (bazaarPrices.has(id)) { - JsonObject item = bazaarPrices.get(id).getAsJsonObject(); - boolean isPriceNull = item.get("sellPrice").isJsonNull(); - - return LongBooleanPair.of(isPriceNull ? 0L : (long) item.get("sellPrice").getAsDouble(), !isPriceNull); - } - - if (lbinPrices.has(id)) { - return LongBooleanPair.of((long) lbinPrices.get(id).getAsDouble(), true); - } - - return LongBooleanPair.of(0L, false); - } - - /** * Searches for a specific string of characters in the name and lore of an item */ - private static String searchLoreFor(ItemStack stack, MinecraftClient client, String searchString) { + private static String searchLoreFor(ItemStack stack, String searchString) { return ItemUtils.getLoreLineIf(stack, line -> line.contains(searchString)); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java new file mode 100644 index 00000000..8d0406cb --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java @@ -0,0 +1,148 @@ +package de.hysky.skyblocker.skyblock; + +import com.google.gson.JsonParser; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Doesn't work with auto pet right now because thats complicated. + * + * Want support? Ask the Admins for a Mod API event or open your pets menu. + */ +public class PetCache { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("pet_cache.json"); + private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>> CACHED_PETS = new Object2ObjectOpenHashMap<>(); + + /** + * Used in case the server lags to prevent the screen tick check from overwriting the clicked pet logic + */ + private static boolean shouldLook4Pets; + + public static void init() { + load(); + + ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> { + if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen) { + if (genericContainerScreen.getTitle().getString().startsWith("Pets")) { + shouldLook4Pets = true; + + ScreenEvents.afterTick(screen).register(screen1 -> { + if (shouldLook4Pets) { + for (Slot slot : genericContainerScreen.getScreenHandler().slots) { + ItemStack stack = slot.getStack(); + + if (!stack.isEmpty() && ItemUtils.getLoreLineIf(stack, line -> line.equals("Click to despawn!")) != null) { + shouldLook4Pets = false; + parsePet(stack, false); + + break; + } + } + } + }); + } + } + }); + } + + private static void load() { + CompletableFuture.runAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(FILE)) { + CACHED_PETS.putAll(PetInfo.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow()); + } catch (NoSuchFileException ignored) { + } catch (Exception e) { + LOGGER.error("[Skyblocker Pet Cache] Failed to load saved pet!", e); + } + }); + } + + private static void save() { + CompletableFuture.runAsync(() -> { + try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { + SkyblockerMod.GSON.toJson(PetInfo.SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, CACHED_PETS).getOrThrow(), writer); + } catch (Exception e) { + LOGGER.error("[Skyblocker Pet Cache] Failed to save pet data to the cache!", e); + } + }); + } + + public static void handlePetEquip(Slot slot, int slotId) { + //Ignore inventory clicks + if (slotId >= 0 && slotId <= 53) { + ItemStack stack = slot.getStack(); + + if (!stack.isEmpty()) parsePet(stack, true); + } + } + + private static void parsePet(ItemStack stack, boolean clicked) { + String id = ItemUtils.getItemId(stack); + String profileId = Utils.getProfileId(); + + if (id.equals("PET") && !profileId.isEmpty()) { + NbtCompound customData = ItemUtils.getCustomData(stack); + + //Should never fail, all pets must have this but you never know with Hypixel + try { + PetInfo petInfo = PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(customData.getString("petInfo"))).getOrThrow(); + shouldLook4Pets = false; + + Object2ObjectOpenHashMap<String, PetInfo> playerData = CACHED_PETS.computeIfAbsent(Utils.getUndashedUuid(), _uuid -> new Object2ObjectOpenHashMap<>()); + + //Handle deselecting pets + if (clicked && getCurrentPet() != null && getCurrentPet().uuid().orElse("").equals(petInfo.uuid().orElse(""))) { + playerData.remove(profileId); + } else { + playerData.put(profileId, petInfo); + } + + save(); + } catch (Exception e) { + LOGGER.error(LogUtils.FATAL_MARKER, "[Skyblocker Pet Cache] Failed to parse pet's pet info!", e); + } + } + } + + @Nullable + public static PetInfo getCurrentPet() { + String uuid = Utils.getUndashedUuid(); + String profileId = Utils.getProfileId(); + + return CACHED_PETS.containsKey(uuid) && CACHED_PETS.get(uuid).containsKey(profileId) ? CACHED_PETS.get(uuid).get(profileId) : null; + } + + public record PetInfo(String type, double exp, String tier, Optional<String> uuid, Optional<String> item) { + public static final Codec<PetInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("type").forGetter(PetInfo::type), + Codec.DOUBLE.fieldOf("exp").forGetter(PetInfo::exp), + Codec.STRING.fieldOf("tier").forGetter(PetInfo::tier), + Codec.STRING.optionalFieldOf("uuid").forGetter(PetInfo::uuid), + Codec.STRING.optionalFieldOf("heldItem").forGetter(PetInfo::item)) + .apply(instance, PetInfo::new)); + private static final Codec<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING, + Codec.unboundedMap(Codec.STRING, CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new) + ).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java index 042b126b..354c92fb 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java @@ -1,7 +1,6 @@ package de.hysky.skyblocker.skyblock; 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.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; @@ -27,7 +26,7 @@ public class TeleportOverlay { private static void render(WorldRenderContext wrc) { if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.teleportOverlay.enableTeleportOverlays && client.player != null && client.world != null) { ItemStack heldItem = client.player.getMainHandStack(); - String itemId = ItemTooltip.getInternalNameFromNBT(heldItem, true); + String itemId = heldItem.getSkyblockId(); NbtCompound customData = ItemUtils.getCustomData(heldItem); if (itemId != null) { @@ -86,7 +85,7 @@ public class TeleportOverlay { render(wrc, blockHitResult); } else if (client.interactionManager != null && range > client.player.getAttributeInstance(EntityAttributes.PLAYER_BLOCK_INTERACTION_RANGE).getValue()) { @SuppressWarnings("DataFlowIssue") - HitResult result = client.player.raycast(range, wrc.tickDelta(), false); + HitResult result = client.player.raycast(range, wrc.tickCounter().getTickDelta(true), false); if (result.getType() == HitResult.Type.BLOCK && result instanceof BlockHitResult blockHitResult) { render(wrc, blockHitResult); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java index fd69d886..b690baa1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java @@ -41,11 +41,11 @@ import java.util.function.Supplier; public class AuctionBrowserScreen extends AbstractCustomHypixelGUI<AuctionHouseScreenHandler> { private static final Logger LOGGER = LoggerFactory.getLogger(AuctionBrowserScreen.class); - private static final Identifier TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/browser/background.png"); - private static final Identifier SCROLLER_TEXTURE = new Identifier("container/creative_inventory/scroller"); + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/browser/background.png"); + private static final Identifier SCROLLER_TEXTURE = Identifier.ofVanilla("container/creative_inventory/scroller"); - private static final Identifier up_arrow_tex = new Identifier(SkyblockerMod.NAMESPACE, "up_arrow_even"); // Put them in their own fields to avoid object allocation on each frame - private static final Identifier down_arrow_tex = new Identifier(SkyblockerMod.NAMESPACE, "down_arrow_even"); + private static final Identifier up_arrow_tex = Identifier.of(SkyblockerMod.NAMESPACE, "up_arrow_even"); // Put them in their own fields to avoid object allocation on each frame + private static final Identifier down_arrow_tex = Identifier.of(SkyblockerMod.NAMESPACE, "down_arrow_even"); public static final Supplier<Sprite> UP_ARROW = () -> MinecraftClient.getInstance().getGuiAtlasManager().getSprite(up_arrow_tex); public static final Supplier<Sprite> DOWN_ARROW = () -> MinecraftClient.getInstance().getGuiAtlasManager().getSprite(down_arrow_tex); @@ -295,8 +295,8 @@ public class AuctionBrowserScreen extends AbstractCustomHypixelGUI<AuctionHouseS String coins = split[1].replace(",", "").replace("coins", "").trim(); try { long parsed = Long.parseLong(coins); - String name = ItemTooltip.getInternalNameFromNBT(stack, false); - String internalID = ItemTooltip.getInternalNameFromNBT(stack, true); + String name = stack.getSkyblockApiId(); + String internalID = stack.getSkyblockId(); String neuName = name; if (name == null || internalID == null) break; if (name.startsWith("ISSHINY_")) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java index f3db2a25..ed5e0f59 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java @@ -31,7 +31,7 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; public class AuctionViewScreen extends AbstractCustomHypixelGUI<AuctionHouseScreenHandler> { - protected static final Identifier BACKGROUND_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/browser/background_view.png"); + protected static final Identifier BACKGROUND_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/browser/background_view.png"); public static final int BACK_BUTTON_SLOT = 49; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/AuctionTypeWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/AuctionTypeWidget.java index 2204d39c..86202ffe 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/AuctionTypeWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/AuctionTypeWidget.java @@ -23,11 +23,11 @@ public class AuctionTypeWidget extends SliderWidget<AuctionTypeWidget.Option> { private final Identifier texture; private static final String prefix = "textures/gui/auctions_gui/auction_type_widget/"; - private static final Identifier HOVER_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, prefix + "hover.png"); - private static final Identifier BACK_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, prefix + "back.png"); + private static final Identifier HOVER_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, prefix + "hover.png"); + private static final Identifier BACK_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, prefix + "back.png"); Option(String textureName) { - texture = new Identifier(SkyblockerMod.NAMESPACE, prefix + textureName); + texture = Identifier.of(SkyblockerMod.NAMESPACE, prefix + textureName); } private static final AuctionTypeWidget.Option[] values = values(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java index 02dbc132..89744085 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java @@ -1,42 +1,27 @@ package de.hysky.skyblocker.skyblock.auction.widgets; import de.hysky.skyblocker.skyblock.auction.SlotClickHandler; +import de.hysky.skyblocker.utils.render.gui.SideTabButtonWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.ButtonTextures; -import net.minecraft.client.gui.widget.ToggleButtonWidget; -import net.minecraft.client.item.TooltipType; import net.minecraft.item.Item.TooltipContext; import net.minecraft.item.ItemStack; -import net.minecraft.util.Identifier; -import org.jetbrains.annotations.NotNull; - -public class CategoryTabWidget extends ToggleButtonWidget { - private static final ButtonTextures TEXTURES = new ButtonTextures(new Identifier("recipe_book/tab"), new Identifier("recipe_book/tab_selected")); +import net.minecraft.item.tooltip.TooltipType; - public void setIcon(@NotNull ItemStack icon) { - this.icon = icon.copy(); - } +import org.jetbrains.annotations.NotNull; - private @NotNull ItemStack icon; +public class CategoryTabWidget extends SideTabButtonWidget { private final SlotClickHandler slotClick; private int slotId = -1; public CategoryTabWidget(@NotNull ItemStack icon, SlotClickHandler slotClick) { - super(0, 0, 35, 27, false); - this.icon = icon.copy(); // copy prevents item disappearing on click + super(0, 0, false, icon); this.slotClick = slotClick; - setTextures(TEXTURES); } @Override public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { - if (textures == null) return; - Identifier identifier = textures.get(true, this.toggled); - int x = getX(); - if (toggled) x -= 2; - context.drawGuiTexture(identifier, x, this.getY(), this.width, this.height); - context.drawItem(icon, x + 9, getY() + 5); + super.renderWidget(context, mouseX, mouseY, delta); if (isMouseOver(mouseX, mouseY)) { context.getMatrices().push(); @@ -52,8 +37,8 @@ public class CategoryTabWidget extends ToggleButtonWidget { @Override public void onClick(double mouseX, double mouseY) { - if (this.toggled || slotId == -1) return; + if (isToggled() || slotId == -1) return; + super.onClick(mouseX, mouseY); slotClick.click(slotId); - this.setToggled(true); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/RarityWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/RarityWidget.java index b6bd42a9..696b9f50 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/RarityWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/RarityWidget.java @@ -19,8 +19,8 @@ import java.util.Map; public class RarityWidget extends ClickableWidget { - private static final Identifier HOVER_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/rarity_widget/hover.png"); - private static final Identifier TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/rarity_widget/background.png"); + private static final Identifier HOVER_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/rarity_widget/hover.png"); + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/rarity_widget/background.png"); private final SlotClickHandler onClick; private int slotId = -1; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/SortWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/SortWidget.java index 84b8ae4d..205cba33 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/SortWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/SortWidget.java @@ -24,11 +24,11 @@ public class SortWidget extends SliderWidget<SortWidget.Option> { private final Identifier texture; private static final String prefix = "textures/gui/auctions_gui/sort_widget/"; - private static final Identifier HOVER_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, prefix + "hover.png"); - private static final Identifier BACK_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, prefix + "back.png"); + private static final Identifier HOVER_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, prefix + "hover.png"); + private static final Identifier BACK_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, prefix + "back.png"); Option(String textureName) { - texture = new Identifier(SkyblockerMod.NAMESPACE, prefix + textureName); + texture = Identifier.of(SkyblockerMod.NAMESPACE, prefix + textureName); } public Identifier getOptionTexture() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java index b8d409ae..5fac426e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.HudRenderEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderTickCounter; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; @@ -13,11 +14,11 @@ public class ChatRuleAnnouncementScreen { private static Text text = null; public static void init() { - HudRenderEvents.BEFORE_CHAT.register((context, tickDelta) -> { + HudRenderEvents.BEFORE_CHAT.register((context, tickCounter) -> { if (timer <= 0 || text == null) { return; } - render(context, tickDelta); + render(context, tickCounter); }); } @@ -26,10 +27,10 @@ public class ChatRuleAnnouncementScreen { * @param context render context * @param tickDelta difference from last render to remove from timer */ - private static void render(DrawContext context, float tickDelta) { + private static void render(DrawContext context, RenderTickCounter tickCounter) { int scale = SkyblockerConfigManager.get().chat.chatRuleConfig.announcementScale; //decrement timer - timer -= tickDelta; + timer -= tickCounter.getTickDelta(true); //scale text up and center MatrixStack matrices = context.getMatrices(); matrices.push(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java index 2d530b6d..f984d751 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -1,25 +1,23 @@ package de.hysky.skyblocker.skyblock.chocolatefactory; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.LineSmoothener; 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.screen.slot.Slot; 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 org.jetbrains.annotations.Nullable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -28,6 +26,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class ChocolateFactorySolver extends ContainerSolver { + //Patterns 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"); @@ -36,20 +35,51 @@ public class ChocolateFactorySolver extends ContainerSolver { 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 final Pattern TIME_TOWER_MULTIPLIER_PATTERN = Pattern.compile("by \\+([\\d.]+)x for \\dh\\."); + + private static final ObjectArrayList<Rabbit> cpsIncreaseFactors = new ObjectArrayList<>(8); private static long totalChocolate = -1L; private static double totalCps = -1.0; private static double totalCpsMultiplier = -1.0; private static long requiredUntilNextPrestige = -1L; + private static boolean canPrestige = false; + private static boolean reachedMaxPrestige = false; private static double timeTowerMultiplier = -1.0; + private static boolean isTimeTowerMaxed = false; private static boolean isTimeTowerActive = false; + private static int bestUpgrade = -1; + private static int bestAffordableUpgrade = -1; private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - private static ItemStack bestUpgrade = null; - private static ItemStack bestAffordableUpgrade = null; + + @Override + protected void reset() { + cpsIncreaseFactors.clear(); + totalChocolate = -1L; + totalCps = -1.0; + totalCpsMultiplier = -1.0; + requiredUntilNextPrestige = -1L; + canPrestige = false; + reachedMaxPrestige = false; + timeTowerMultiplier = -1.0; + isTimeTowerMaxed = false; + isTimeTowerActive = false; + bestUpgrade = -1; + bestAffordableUpgrade = -1; + } + + //Slots, for ease of maintenance rather than using magic numbers everywhere. + private static final byte RABBITS_START = 28; + private static final byte RABBITS_END = 34; + private static final byte COACH_SLOT = 42; + private static final byte CHOCOLATE_SLOT = 13; + private static final byte CPS_SLOT = 45; + private static final byte PRESTIGE_SLOT = 27; + private static final byte TIME_TOWER_SLOT = 39; + private static final byte STRAY_RABBIT_START = 0; + private static final byte STRAY_RABBIT_END = 26; public ChocolateFactorySolver() { - super("^Chocolate Factory$"); - ItemTooltipCallback.EVENT.register(ChocolateFactorySolver::handleTooltip); + super("^Chocolate Factory$"); //There are multiple screens that fit the pattern `^Chocolate Factory`, so the $ is required } @Override @@ -62,11 +92,12 @@ public class ChocolateFactorySolver extends ContainerSolver { updateFactoryInfo(slots); List<ColorHighlight> highlights = new ArrayList<>(); - getPrestigeHighlight(slots.get(28)).ifPresent(highlights::add); + getPrestigeHighlight().ifPresent(highlights::add); + highlights.addAll(getStrayRabbitHighlight(slots)); if (totalChocolate <= 0 || cpsIncreaseFactors.isEmpty()) return highlights; //Something went wrong or there's nothing we can afford. Rabbit bestRabbit = cpsIncreaseFactors.getFirst(); - bestUpgrade = bestRabbit.itemStack; + bestUpgrade = bestRabbit.slot; if (bestRabbit.cost <= totalChocolate) { highlights.add(ColorHighlight.green(bestRabbit.slot)); return highlights; @@ -75,7 +106,7 @@ public class ChocolateFactorySolver extends ContainerSolver { for (Rabbit rabbit : cpsIncreaseFactors.subList(1, cpsIncreaseFactors.size())) { if (rabbit.cost <= totalChocolate) { - bestAffordableUpgrade = rabbit.itemStack; + bestAffordableUpgrade = rabbit.slot; highlights.add(ColorHighlight.green(rabbit.slot)); break; } @@ -87,7 +118,7 @@ public class ChocolateFactorySolver extends ContainerSolver { 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. + for (int i = RABBITS_START; i <= RABBITS_END; i++) { // The 7 rabbits slots are in 28, 29, 30, 31, 32, 33 and 34. ItemStack item = slots.get(i); if (item.isOf(Items.PLAYER_HEAD)) { getRabbit(item, i).ifPresent(cpsIncreaseFactors::add); @@ -95,20 +126,21 @@ public class ChocolateFactorySolver extends ContainerSolver { } //Coach is in slot 42 - getCoach(slots.get(42)).ifPresent(cpsIncreaseFactors::add); + getCoach(slots.get(COACH_SLOT)).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); + RegexUtils.getLongFromMatcher(CHOCOLATE_PATTERN.matcher(slots.get(CHOCOLATE_SLOT).getName().getString())).ifPresent(l -> totalChocolate = l); //Cps item (cocoa bean) is in slot 45 - String cpsItemLore = getConcatenatedLore(slots.get(45)); + String cpsItemLore = getConcatenatedLore(slots.get(CPS_SLOT)); 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))); + String prestigeLore = getConcatenatedLore(slots.get(PRESTIGE_SLOT)); + Matcher prestigeMatcher = PRESTIGE_REQUIREMENT_PATTERN.matcher(prestigeLore); 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 @@ -117,12 +149,22 @@ public class ChocolateFactorySolver extends ContainerSolver { if (NumberUtils.isParsable(amountString)) { requiredUntilNextPrestige = Long.parseLong(amountString) - currentChocolate.getAsLong(); } + canPrestige = false; + } else if (prestigeLore.endsWith("Click to prestige!")) { + canPrestige = true; + reachedMaxPrestige = false; + } else if (prestigeLore.endsWith("You have reached max prestige!")) { + canPrestige = false; + reachedMaxPrestige = true; } //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()) { + isTimeTowerMaxed = StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ').equals("XV"); + String timeTowerLore = getConcatenatedLore(slots.get(TIME_TOWER_SLOT)); + Matcher timeTowerMultiplierMatcher = TIME_TOWER_MULTIPLIER_PATTERN.matcher(timeTowerLore); + RegexUtils.getDoubleFromMatcher(timeTowerMultiplierMatcher).ifPresent(d -> timeTowerMultiplier = d); + Matcher timeTowerStatusMatcher = TIME_TOWER_STATUS_PATTERN.matcher(timeTowerLore); + if (timeTowerStatusMatcher.find(timeTowerMultiplierMatcher.hasMatch() ? timeTowerMultiplierMatcher.end() : 0)) { isTimeTowerActive = timeTowerStatusMatcher.group(1).equals("ACTIVE"); } @@ -130,128 +172,6 @@ public class ChocolateFactorySolver extends ContainerSolver { 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. */ @@ -276,7 +196,7 @@ public class ChocolateFactorySolver extends ContainerSolver { 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. + if (totalCps < 0 || totalCpsMultiplier < 0) return Optional.empty(); //We need these 2 to calculate the increase in cps. Matcher multiplierIncreaseMatcher = MULTIPLIER_INCREASE_PATTERN.matcher(coachLore); OptionalDouble currentCpsMultiplier = RegexUtils.getDoubleFromMatcher(multiplierIncreaseMatcher); @@ -289,10 +209,10 @@ public class ChocolateFactorySolver extends ContainerSolver { } Matcher costMatcher = COST_PATTERN.matcher(coachLore); - OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line + OptionalLong cost = RegexUtils.getLongFromMatcher(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)); + return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsLong(), COACH_SLOT)); } private static Optional<Rabbit> getRabbit(ItemStack item, int slot) { @@ -307,42 +227,161 @@ public class ChocolateFactorySolver extends ContainerSolver { } Matcher costMatcher = COST_PATTERN.matcher(lore); - OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line + OptionalLong cost = RegexUtils.getLongFromMatcher(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)); + return Optional.of(new Rabbit((nextCps.getAsInt() - currentCps.getAsInt())*(totalCpsMultiplier < 0 ? 1 : totalCpsMultiplier), cost.getAsLong(), slot)); } - 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 static Optional<ColorHighlight> getPrestigeHighlight() { + if (reachedMaxPrestige) return Optional.empty(); + if (canPrestige) return Optional.of(ColorHighlight.green(PRESTIGE_SLOT)); + return Optional.of(ColorHighlight.red(PRESTIGE_SLOT)); } - private record Rabbit(double cpsIncrease, int cost, int slot, ItemStack itemStack) { + private static List<ColorHighlight> getStrayRabbitHighlight(Int2ObjectMap<ItemStack> slots) { + final List<ColorHighlight> highlights = new ArrayList<>(); + for (byte i = STRAY_RABBIT_START; i <= STRAY_RABBIT_END; i++) { + ItemStack item = slots.get(i); + if (!item.isOf(Items.PLAYER_HEAD)) continue; + String name = item.getName().getString(); + if (name.equals("CLICK ME!") || name.startsWith("Golden Rabbit - ")) { + highlights.add(ColorHighlight.green(i)); + } + } + return highlights; } - //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); + private record Rabbit(double cpsIncrease, long cost, int slot) { } + + public static final class Tooltip extends TooltipAdder { + public Tooltip() { + super("^Chocolate Factory$", 0); //The priority doesn't really matter here as this is the only tooltip adder for the Chocolate Factory. + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper || focusedSlot == null) 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()); + + int index = focusedSlot.id; + + //Prestige item + if (index == PRESTIGE_SLOT) { + shouldAddLine |= addPrestigeTimerToLore(lines); + } + //Time tower + else if (index == TIME_TOWER_SLOT) { + shouldAddLine |= addTimeTowerStatsToLore(lines); + } + //Rabbits + else if (index == COACH_SLOT || (index >= RABBITS_START && index <= RABBITS_END)) { + shouldAddLine |= addRabbitStatsToLore(lines, index); + } + + //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, LineSmoothener.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 (totalCps < 0.0 || reachedMaxPrestige) return false; + if (requiredUntilNextPrestige > 0 && !canPrestige) { + 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() //Keep this outside of the `if` to match the format of the upgrade tooltips, that say "Time until upgrade: Now" when it's possible + .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 (!isTimeTowerMaxed) { + 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, int slot) { + if (cpsIncreaseFactors.isEmpty()) return false; + for (Rabbit rabbit : cpsIncreaseFactors) { + if (rabbit.slot == slot) { + 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.slot == 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.slot == bestAffordableUpgrade && rabbit.cost <= totalChocolate) { + lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN)); + } + return true; + } + } + return false; + } + + 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); } - 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 index c4fd7d4d..42883c4f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java @@ -1,10 +1,17 @@ package de.hysky.skyblocker.skyblock.chocolatefactory; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.utils.*; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.waypoint.Waypoint; import it.unimi.dsi.fastutil.objects.ObjectImmutableList; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; 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; @@ -13,6 +20,8 @@ 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.ClickEvent; +import net.minecraft.text.HoverEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.apache.commons.lang3.mutable.MutableObject; @@ -25,10 +34,9 @@ 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 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, Location.DEEP_CAVERNS, Location.SPIDERS_DEN, Location.THE_FARMING_ISLAND}; private static boolean isLocationCorrect = false; private EggFinder() { @@ -39,6 +47,17 @@ public class EggFinder { SkyblockEvents.LOCATION_CHANGE.register(EggFinder::handleLocationChange); ClientReceiveMessageEvents.GAME.register(EggFinder::onChatMessage); WorldRenderEvents.AFTER_TRANSLUCENT.register(EggFinder::renderWaypoints); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE) + .then(ClientCommandManager.literal("eggFinder") + .then(ClientCommandManager.literal("shareLocation") + .then(ClientCommandManager.argument("x", IntegerArgumentType.integer()) + .then(ClientCommandManager.argument("y", IntegerArgumentType.integer()) + .then(ClientCommandManager.argument("z", IntegerArgumentType.integer()) + .then(ClientCommandManager.argument("eggType", StringArgumentType.word()) + .executes(context -> { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("[Skyblocker] Chocolate " + context.getArgument("eggType", String.class) + " Egg found at " + context.getArgument("x", Integer.class) + " " + context.getArgument("y", Integer.class) + " " + context.getArgument("z", Integer.class) + "!"); + return Command.SINGLE_SUCCESS; + }))))))))); } private static void handleLocationChange(Location location) { @@ -102,7 +121,9 @@ public class EggFinder { .append("Found a ") .append(Text.literal("Chocolate " + eggType + " Egg") .withColor(eggType.color)) - .append(" at " + entity.getBlockPos().up(2).toShortString() + "!")); + .append(" at " + entity.getBlockPos().up(2).toShortString() + "!") + .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + entity.getBlockX() + " " + (entity.getBlockY() + 2) + " " + entity.getBlockZ() + " " + eggType)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN))))); } private static void renderWaypoints(WorldRenderContext context) { @@ -124,16 +145,6 @@ public class EggFinder { 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) { } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java index 72cbeb2a..6ed11676 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/TimeTowerReminder.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.chocolatefactory; import com.mojang.brigadier.Message; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; @@ -49,7 +50,7 @@ public class TimeTowerReminder { } try (FileWriter writer = new FileWriter(tempFile)) { - writer.write(String.valueOf(System.currentTimeMillis())); + writer.write(String.valueOf(System.currentTimeMillis())); //Overwrites the file so no need to handle case where the file already exists and has text } catch (IOException e) { LOGGER.error("[Skyblocker Time Tower Reminder] Failed to write to temp file for Time Tower Reminder!", e); } @@ -57,8 +58,9 @@ public class TimeTowerReminder { 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))); - + if (SkyblockerConfigManager.get().helpers.chocolateFactory.enableTimeTowerReminder) { + 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; @@ -81,6 +83,9 @@ public class TimeTowerReminder { } 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 + else { + Scheduler.INSTANCE.schedule(TimeTowerReminder::sendMessage, 60 * 60 * 20 - (int) ((System.currentTimeMillis() - time) / 50)); // 50 milliseconds is 1 tick + scheduled = true; + } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/KuudraWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/KuudraWaypoints.java index 5b586f71..dbf7409a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/KuudraWaypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/KuudraWaypoints.java @@ -17,7 +17,6 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.PosUtils; import de.hysky.skyblocker.utils.Utils; @@ -53,8 +52,8 @@ public class KuudraWaypoints { private static boolean loaded; static void load(MinecraftClient client) { - CompletableFuture<Void> safeSpots = loadWaypoints(client, new Identifier(SkyblockerMod.NAMESPACE, "crimson/kuudra/safe_spot_waypoints.json"), SAFE_SPOT_WAYPOINTS, SAFE_SPOT_COLOR); - CompletableFuture<Void> pearls = loadWaypoints(client, new Identifier(SkyblockerMod.NAMESPACE, "crimson/kuudra/pearl_waypoints.json"), PEARL_WAYPOINTS, PEARL_COLOR); + CompletableFuture<Void> safeSpots = loadWaypoints(client, Identifier.of(SkyblockerMod.NAMESPACE, "crimson/kuudra/safe_spot_waypoints.json"), SAFE_SPOT_WAYPOINTS, SAFE_SPOT_COLOR); + CompletableFuture<Void> pearls = loadWaypoints(client, Identifier.of(SkyblockerMod.NAMESPACE, "crimson/kuudra/pearl_waypoints.json"), PEARL_WAYPOINTS, PEARL_COLOR); CompletableFuture.allOf(safeSpots, pearls).whenComplete((_result, _throwable) -> loaded = true); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java index 70e785cb..0f459c9d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java @@ -35,13 +35,13 @@ public class CroesusProfit extends ContainerSolver { protected List<ColorHighlight> getColors(String[] groups, Int2ObjectMap<ItemStack> slots) { List<ColorHighlight> highlights = new ArrayList<>(); ItemStack bestChest = null, secondBestChest = null; - long bestValue = 0, secondBestValue = 0; // If negative value of chest - it is out of the question - long dungeonKeyPriceData = getItemPrice("DUNGEON_CHEST_KEY") * 2; // lesser ones don't worth the hassle + double bestValue = 0, secondBestValue = 0; // If negative value of chest - it is out of the question + double dungeonKeyPriceData = getItemPrice("DUNGEON_CHEST_KEY") * 2; // lesser ones don't worth the hassle for (Int2ObjectMap.Entry<ItemStack> entry : slots.int2ObjectEntrySet()) { ItemStack stack = entry.getValue(); if (stack.getName().getString().contains("Chest")) { - long value = valueChest(stack); + double value = valueChest(stack); if (value > bestValue) { secondBestChest = bestChest; secondBestValue = bestValue; @@ -68,8 +68,8 @@ public class CroesusProfit extends ContainerSolver { } - private long valueChest(@NotNull ItemStack chest) { - long chestValue = 0; + private double valueChest(@NotNull ItemStack chest) { + double chestValue = 0; int chestPrice = 0; List<String> chestItems = new ArrayList<>(); @@ -107,22 +107,8 @@ public class CroesusProfit extends ContainerSolver { } - private long getItemPrice(String itemDisplayName) { - JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData(); - JsonObject lbinPrices = TooltipInfoType.LOWEST_BINS.getData(); - long itemValue = 0; - String id = dungeonDropsNameToId.get(itemDisplayName); - - if (bazaarPrices == null || lbinPrices == null) return 0; - - if (bazaarPrices.has(id)) { - JsonObject item = bazaarPrices.get(id).getAsJsonObject(); - boolean isPriceNull = item.get("sellPrice").isJsonNull(); - return (isPriceNull ? 0L : item.get("sellPrice").getAsLong()); - } else if (lbinPrices.has(id)) { - return lbinPrices.get(id).getAsLong(); - } - return itemValue; + private double getItemPrice(String itemDisplayName) { + return ItemUtils.getItemPrice(dungeonDropsNameToId.get(itemDisplayName)).leftDouble(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java index 1e896794..a46daf52 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java @@ -26,7 +26,7 @@ public class DungeonMap { private static MapIdComponent cachedMapIdComponent = null; public static void init() { - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickDelta) -> render(context)); + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> render(context)); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker") .then(ClientCommandManager.literal("hud") .then(ClientCommandManager.literal("dungeon") diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java index 068510c2..bcddbee3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java @@ -15,7 +15,7 @@ public class DungeonMapConfigScreen extends Screen { private int mapY = SkyblockerConfigManager.get().dungeons.dungeonMap.mapY; private int scoreX = SkyblockerConfigManager.get().dungeons.dungeonScore.scoreX; private int scoreY = SkyblockerConfigManager.get().dungeons.dungeonScore.scoreY; - private static final Identifier MAP_BACKGROUND = new Identifier("textures/map/map_background.png"); + private static final Identifier MAP_BACKGROUND = Identifier.ofVanilla("textures/map/map_background.png"); private final Screen parent; protected DungeonMapConfigScreen() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java index 7a5abed1..d1fc08ec 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java @@ -304,7 +304,7 @@ public class DungeonScore { if (s.equals("You")) return MinecraftClient.getInstance().getSession().getUsername(); //This will be wrong if the dead player is called 'You' but that's unlikely else return s; }); - ProfileUtils.updateProfile(whoDied).thenAccept(player -> firstDeathHasSpiritPet = hasSpiritPet(player, whoDied)); + ProfileUtils.updateProfileByName(whoDied).thenAccept(player -> firstDeathHasSpiritPet = hasSpiritPet(player, whoDied)); } private static void checkMessageForWatcher(String message) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java index 1896eb53..cd8b9706 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java @@ -14,7 +14,7 @@ public class DungeonScoreHUD { } public static void init() { - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickDelta) -> render(context)); + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> render(context)); } //This is 4+5 wide, needed to offset the extra width from bold numbers (3×1 wide) in S+ and the "+" (6 wide) so that it doesn't go off the screen if the score is S+ and the hud element is at the right edge of the screen diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonTextures.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonTextures.java index 1d55491f..502efc6f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonTextures.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonTextures.java @@ -8,7 +8,7 @@ import net.minecraft.util.Identifier; public class DungeonTextures { public static void init() { ResourceManagerHelper.registerBuiltinResourcePack( - new Identifier(SkyblockerMod.NAMESPACE, "recolored_dungeon_items"), + Identifier.of(SkyblockerMod.NAMESPACE, "recolored_dungeon_items"), SkyblockerMod.SKYBLOCKER_MOD, ResourcePackActivationType.NORMAL ); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/FireFreezeStaffTimer.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/FireFreezeStaffTimer.java index b2a9b521..3422bc0f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/FireFreezeStaffTimer.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/FireFreezeStaffTimer.java @@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderTickCounter; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -19,7 +20,7 @@ public class FireFreezeStaffTimer { ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> FireFreezeStaffTimer.reset()); } - private static void onDraw(DrawContext context, float v) { + private static void onDraw(DrawContext context, RenderTickCounter tickCounter) { MinecraftClient client = MinecraftClient.getInstance(); if (client.currentScreen != null) return; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java index 588e2431..0b52f1ed 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java @@ -33,8 +33,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class PartyEntry extends ElementListWidget.Entry<PartyEntry> { - private static final Identifier PARTY_CARD_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/party_card.png"); - private static final Identifier PARTY_CARD_TEXTURE_HOVER = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/party_card_hover.png"); + private static final Identifier PARTY_CARD_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/party_card.png"); + private static final Identifier PARTY_CARD_TEXTURE_HOVER = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/party_card_hover.png"); public static final Text JOIN_TEXT = Text.translatable("skyblocker.partyFinder.join"); private static final Map<String, ProfileComponent> SKULL_CACHE = new Object2ObjectOpenHashMap<>(); protected final PartyFinderScreen screen; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java index d22084f2..ad002057 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java @@ -37,8 +37,8 @@ import java.util.concurrent.CompletableFuture; public class PartyFinderScreen extends Screen { protected static final Logger LOGGER = LoggerFactory.getLogger(PartyFinderScreen.class); - protected static final Identifier BACKGROUND_TEXTURE = new Identifier("social_interactions/background"); - protected static final Identifier SEARCH_ICON_TEXTURE = new Identifier("icon/search"); + protected static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("social_interactions/background"); + protected static final Identifier SEARCH_ICON_TEXTURE = Identifier.ofVanilla("icon/search"); protected static final Text SEARCH_TEXT = Text.translatable("gui.socialInteractions.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY); public static boolean isInKuudraPartyFinder = false; @@ -110,7 +110,7 @@ public class PartyFinderScreen extends Screen { CompletableFuture.runAsync(() -> { floorIconsNormal = new HashMap<>(); floorIconsMaster = new HashMap<>(); - try (BufferedReader skullTextureReader = client.getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/catacombs/floorskulls.json"))) { + try (BufferedReader skullTextureReader = client.getResourceManager().openAsReader(Identifier.of(SkyblockerMod.NAMESPACE, "dungeons/catacombs/floorskulls.json"))) { JsonObject json = SkyblockerMod.GSON.fromJson(skullTextureReader, JsonObject.class); json.getAsJsonObject("normal").asMap().forEach((s, tex) -> floorIconsNormal.put(s, ItemUtils.propertyMapWithTexture(tex.getAsString()))); json.getAsJsonObject("master").asMap().forEach((s, tex) -> floorIconsMaster.put(s, ItemUtils.propertyMapWithTexture(tex.getAsString()))); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java index 3d9e05ae..75dd7708 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.dungeon.puzzle; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; import it.unimi.dsi.fastutil.objects.ObjectDoublePair; @@ -28,13 +29,13 @@ public class CreeperBeams extends DungeonPuzzle { private static final Logger LOGGER = LoggerFactory.getLogger(CreeperBeams.class.getName()); private static final float[][] COLORS = { - DyeColor.LIGHT_BLUE.getColorComponents(), - DyeColor.LIME.getColorComponents(), - DyeColor.YELLOW.getColorComponents(), - DyeColor.MAGENTA.getColorComponents(), - DyeColor.PINK.getColorComponents(), + ColorUtils.getFloatComponents(DyeColor.LIGHT_BLUE), + ColorUtils.getFloatComponents(DyeColor.LIME), + ColorUtils.getFloatComponents(DyeColor.YELLOW), + ColorUtils.getFloatComponents(DyeColor.MAGENTA), + ColorUtils.getFloatComponents(DyeColor.PINK), }; - private static final float[] GREEN_COLOR_COMPONENTS = DyeColor.GREEN.getColorComponents(); + private static final float[] GREEN_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.GREEN); private static final int FLOOR_Y = 68; private static final int BASE_Y = 74; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java index 8bc14b52..883a469a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java @@ -7,6 +7,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.debug.Debug; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.render.RenderHelper; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -27,7 +28,7 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit public class IceFill extends DungeonPuzzle { public static final IceFill INSTANCE = new IceFill(); - private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); + private static final float[] RED_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.RED); private static final BlockPos[] BOARD_ORIGINS = { new BlockPos(16, 70, 9), new BlockPos(17, 71, 16), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java index 3b0ee65c..f4376c54 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java @@ -6,6 +6,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.debug.Debug; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.render.RenderHelper; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -19,17 +20,14 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import org.joml.Vector2i; import org.joml.Vector2ic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class Silverfish extends DungeonPuzzle { - private static final Logger LOGGER = LoggerFactory.getLogger(Silverfish.class); public static final Silverfish INSTANCE = new Silverfish(); - private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); + private static final float[] RED_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.RED); final boolean[][] silverfishBoard = new boolean[17][17]; Vector2ic silverfishPos; final List<Vector2ic> silverfishPath = new ArrayList<>(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java index 3c0b0dc9..5a497c19 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java @@ -55,7 +55,7 @@ public class TicTacToe extends DungeonPuzzle { char[][] board = new char[3][3]; for (ItemFrameEntity itemFrame : itemFramesThatHoldMaps) { - MapState mapState = client.world.getMapState(itemFrame.getMapId()); + MapState mapState = client.world.getMapState(itemFrame.getMapId(itemFrame.getHeldItemStack())); if (mapState == null) continue; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java index 68671896..aef9109f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.title.Title; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; @@ -18,17 +19,14 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; import net.minecraft.world.BlockView; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.List; public class Boulder extends DungeonPuzzle { - private static final Logger LOGGER = LoggerFactory.getLogger(Boulder.class.getName()); private static final Boulder INSTANCE = new Boulder(); - private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); - private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents(); + private static final float[] RED_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.RED); + private static final float[] ORANGE_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.ORANGE); private static final int BASE_Y = 65; static Vec3d[] linePoints; static Box boundingBox; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java index 4e8122c3..511827b9 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java @@ -11,6 +11,7 @@ import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle; import de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard.Cell.SwitchCell; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.scheduler.Scheduler; @@ -71,7 +72,7 @@ public class Waterboard extends DungeonPuzzle { new BlockPos(10, 61, 10) }; public static final BlockPos WATER_LEVER = new BlockPos(15, 60, 5); - private static final float[] LIME_COLOR_COMPONENTS = DyeColor.LIME.getColorComponents(); + private static final float[] LIME_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.LIME); private CompletableFuture<Void> solve; private final Cell[][] cells = new Cell[19][19]; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java index e4504cc9..b9986731 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java @@ -295,7 +295,7 @@ public class DungeonManager { })); } dungeonFutures.add(CompletableFuture.runAsync(() -> { - try (BufferedReader roomsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/dungeonrooms.json")); BufferedReader waypointsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/secretlocations.json"))) { + try (BufferedReader roomsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(Identifier.of(SkyblockerMod.NAMESPACE, "dungeons/dungeonrooms.json")); BufferedReader waypointsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(Identifier.of(SkyblockerMod.NAMESPACE, "dungeons/secretlocations.json"))) { loadJson(roomsReader, roomsJson); loadJson(waypointsReader, waypointsJson); LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secret waypoints json"); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java index e6a8b9d1..c0e54904 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java @@ -242,7 +242,7 @@ public class Room implements Tickable, Renderable { protected void removeCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) { SecretWaypoint waypoint = removeCustomWaypoint(pos); if (waypoint != null) { - context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.stringifiedTranslatable("skyblocker.dungeons.secrets.customWaypointRemoved", pos.getX(), pos.getY(), pos.getZ(), name, waypoint.secretIndex, waypoint.category, waypoint.name))); + context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointRemoved", pos.getX(), pos.getY(), pos.getZ(), name, waypoint.secretIndex, waypoint.category.asString(), waypoint.getName()))); } else { context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointNotFound", pos.getX(), pos.getY(), pos.getZ(), name))); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java index 42fe6dbe..3779a66e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java @@ -5,10 +5,10 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; -import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.DungeonsConfig; import de.hysky.skyblocker.utils.render.RenderHelper; +import de.hysky.skyblocker.utils.waypoint.NamedWaypoint; import de.hysky.skyblocker.utils.waypoint.Waypoint; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.minecraft.client.MinecraftClient; @@ -29,7 +29,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToDoubleFunction; -public class SecretWaypoint extends Waypoint { +public class SecretWaypoint extends NamedWaypoint { private static final Logger LOGGER = LoggerFactory.getLogger(SecretWaypoint.class); public static final Codec<SecretWaypoint> CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.INT.fieldOf("secretIndex").forGetter(secretWaypoint -> secretWaypoint.secretIndex), @@ -43,8 +43,6 @@ public class SecretWaypoint extends Waypoint { static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.get().waypointType; final int secretIndex; final Category category; - final Text name; - private final Vec3d centerPos; SecretWaypoint(int secretIndex, JsonObject waypoint, String name, BlockPos pos) { this(secretIndex, Category.get(waypoint), name, pos); @@ -55,11 +53,9 @@ public class SecretWaypoint extends Waypoint { } SecretWaypoint(int secretIndex, Category category, Text name, BlockPos pos) { - super(pos, TYPE_SUPPLIER, category.colorComponents); + super(pos, name, TYPE_SUPPLIER, category.colorComponents); this.secretIndex = secretIndex; this.category = category; - this.name = name; - this.centerPos = pos.toCenterPos(); } static ToDoubleFunction<SecretWaypoint> getSquaredDistanceToFunction(Entity entity) { @@ -96,6 +92,11 @@ public class SecretWaypoint extends Waypoint { return super.equals(obj) || obj instanceof SecretWaypoint other && secretIndex == other.secretIndex && category == other.category && name.equals(other.name) && pos.equals(other.pos); } + @Override + protected boolean shouldRenderName() { + return CONFIG.get().showSecretText; + } + /** * Renders the secret waypoint, including a waypoint through {@link Waypoint#render(WorldRenderContext)}, the name, and the distance from the player. */ @@ -106,7 +107,6 @@ public class SecretWaypoint extends Waypoint { if (CONFIG.get().showSecretText) { Vec3d posUp = centerPos.add(0, 1, 0); - RenderHelper.renderText(context, name, posUp, true); double distance = context.camera().getPos().distanceTo(centerPos); RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java index 2abe5183..7a966b48 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java @@ -72,7 +72,7 @@ public final class ColorTerminal extends ContainerSolver implements TerminalSolv itemColor = new HashMap<>(); for (DyeColor color : DyeColor.values()) for (String item : new String[]{"dye", "wool", "stained_glass", "terracotta"}) - itemColor.put(Registries.ITEM.get(new Identifier(color.getName() + '_' + item)), color); + itemColor.put(Registries.ITEM.get(Identifier.ofVanilla(color.getName() + '_' + item)), color); itemColor.put(Items.BONE_MEAL, DyeColor.WHITE); itemColor.put(Items.LAPIS_LAZULI, DyeColor.BLUE); itemColor.put(Items.COCOA_BEANS, DyeColor.BROWN); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java index 63430489..7f4dbdbf 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java @@ -21,8 +21,8 @@ import java.util.Map; public class CrystalsHud { private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); - protected static final Identifier MAP_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/crystals_map.png"); - private static final Identifier MAP_ICON = new Identifier("textures/map/decorations/player.png"); + protected static final Identifier MAP_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/crystals_map.png"); + private static final Identifier MAP_ICON = Identifier.ofVanilla("textures/map/decorations/player.png"); private static final List<String> SMALL_LOCATIONS = List.of("Fairy Grotto", "King Yolkar", "Corleone", "Odawa", "Key Guardian"); @@ -34,13 +34,13 @@ public class CrystalsHud { .then(ClientCommandManager.literal("crystals") .executes(Scheduler.queueOpenScreenCommand(CrystalsHudConfigScreen::new)))))); - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickDelta) -> { + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { if (!SkyblockerConfigManager.get().mining.crystalsHud.enabled || CLIENT.player == null || !visible) { return; } - render(context, tickDelta, SkyblockerConfigManager.get().mining.crystalsHud.x, + render(context, SkyblockerConfigManager.get().mining.crystalsHud.x, SkyblockerConfigManager.get().mining.crystalsHud.y); }); } @@ -54,11 +54,10 @@ public class CrystalsHud { * Renders the map to the players UI. renders the background image ({@link CrystalsHud#MAP_TEXTURE}) of the map then if enabled special locations on the map. then finally the player to the map. * * @param context DrawContext to draw map to - * @param tickDelta For interpolating the player's yaw for map marker * @param hudX Top left X coordinate of the map * @param hudY Top left Y coordinate of the map */ - private static void render(DrawContext context, float tickDelta, int hudX, int hudY) { + private static void render(DrawContext context, int hudX, int hudY) { float scale = SkyblockerConfigManager.get().mining.crystalsHud.mapScaling; //make sure the map renders infront of some stuff - improve this in the future with better layering (1.20.5?) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java index 2cf0ea9d..16c36dde 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.dwarven; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.MiningConfig; import de.hysky.skyblocker.events.HudRenderEvents; import de.hysky.skyblocker.mixins.accessors.PlayerListHudAccessor; import de.hysky.skyblocker.skyblock.tabhud.util.Colors; @@ -61,7 +62,7 @@ public class DwarvenHud { ) )); - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickDelta) -> { + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { if (!SkyblockerConfigManager.get().mining.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().mining.dwarvenHud.enabledPowder || CLIENT.options.playerListKey.isPressed() || CLIENT.player == null @@ -164,7 +165,9 @@ public class DwarvenHud { public static void update() { if (CLIENT.player == null || CLIENT.getNetworkHandler() == null - || !SkyblockerConfigManager.get().mining.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().mining.dwarvenHud.enabledPowder + || !SkyblockerConfigManager.get().mining.dwarvenHud.enabledCommissions + && !SkyblockerConfigManager.get().mining.dwarvenHud.enabledPowder + && SkyblockerConfigManager.get().mining.commissionWaypoints.mode == MiningConfig.CommissionWaypointMode.OFF || !Utils.isInCrystalHollows() && !Utils.isInDwarvenMines()) { return; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/GlaciteColdOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/GlaciteColdOverlay.java index 3839a712..f2911f9a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/GlaciteColdOverlay.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/GlaciteColdOverlay.java @@ -13,7 +13,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class GlaciteColdOverlay { - private static final Identifier POWDER_SNOW_OUTLINE = new Identifier("textures/misc/powder_snow_outline.png"); + private static final Identifier POWDER_SNOW_OUTLINE = Identifier.ofVanilla("textures/misc/powder_snow_outline.png"); private static final Pattern COLD_PATTERN = Pattern.compile("Cold: -(\\d+)❄"); private static int cold = 0; private static long resetTime = System.currentTimeMillis(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java index dd97e5d1..294b2c3d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java @@ -12,16 +12,15 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import net.minecraft.util.Util; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,56 +29,56 @@ public class MetalDetector { private static final float[] LIGHT_GRAY = { 192 / 255f, 192 / 255f, 192 / 255f }; private static final Pattern TREASURE_PATTERN = Pattern.compile("(§3§lTREASURE: §b)(\\d+\\.?\\d?)m"); private static final Pattern KEEPER_PATTERN = Pattern.compile("Keeper of (\\w+)"); - private static final HashMap<String, Vec3i> keeperOffsets = Util.make(new HashMap<>(), map -> { - map.put("Diamond", new Vec3i(33, 0, 3)); - map.put("Lapis", new Vec3i(-33, 0, -3)); - map.put("Emerald", new Vec3i(-3, 0, 33)); - map.put("Gold", new Vec3i(3, 0, -33)); - }); - private static final HashSet<Vec3i> knownChestOffsets = Util.make(new HashSet<>(), set -> { - set.add(new Vec3i(-38, -22, 26)); // -38, -22, 26 - set.add(new Vec3i(38, -22, -26)); // 38, -22, -26 - set.add(new Vec3i(-40, -22, 18)); // -40, -22, 18 - set.add(new Vec3i(-41, -20, 22)); // -41, -20, 22 - set.add(new Vec3i(-5, -21, 16)); // -5, -21, 16 - set.add(new Vec3i(40, -22, -30)); // 40, -22, -30 - set.add(new Vec3i(-42, -20, -28)); // -42, -20, -28 - set.add(new Vec3i(-43, -22, -40)); // -43, -22, -40 - set.add(new Vec3i(42, -19, -41)); // 42, -19, -41 - set.add(new Vec3i(43, -21, -16)); // 43, -21, -16 - set.add(new Vec3i(-1, -22, -20)); // -1, -22, -20 - set.add(new Vec3i(6, -21, 28)); // 6, -21, 28 - set.add(new Vec3i(7, -21, 11)); // 7, -21, 11 - set.add(new Vec3i(7, -21, 22)); // 7, -21, 22 - set.add(new Vec3i(-12, -21, -44)); // -12, -21, -44 - set.add(new Vec3i(12, -22, 31)); // 12, -22, 31 - set.add(new Vec3i(12, -22, -22)); // 12, -22, -22 - set.add(new Vec3i(12, -21, 7)); // 12, -21, 7 - set.add(new Vec3i(12, -21, -43)); // 12, -21, -43 - set.add(new Vec3i(-14, -21, 43)); // -14, -21, 43 - set.add(new Vec3i(-14, -21, 22)); // -14, -21, 22 - set.add(new Vec3i(-17, -21, 20)); // -17, -21, 20 - set.add(new Vec3i(-20, -22, 0)); // -20, -22, 0 - set.add(new Vec3i(1, -21, 20)); // 1, -21, 20 - set.add(new Vec3i(19, -22, 29)); // 19, -22, 29 - set.add(new Vec3i(20, -22, 0)); // 20, -22, 0 - set.add(new Vec3i(20, -21, -26)); // 20, -21, -26 - set.add(new Vec3i(-23, -22, 40)); // -23, -22, 40 - set.add(new Vec3i(22, -21, -14)); // 22, -21, -14 - set.add(new Vec3i(-24, -22, 12)); // -24, -22, 12 - set.add(new Vec3i(23, -22, 26)); // 23, -22, 26 - set.add(new Vec3i(23, -22, -39)); // 23, -22, -39 - set.add(new Vec3i(24, -22, 27)); // 24, -22, 27 - set.add(new Vec3i(25, -22, 17)); // 25, -22, 17 - set.add(new Vec3i(29, -21, -44)); // 29, -21, -44 - set.add(new Vec3i(-31, -21, -12)); // -31, -21, -12 - set.add(new Vec3i(-31, -21, -40)); // -31, -21, -40 - set.add(new Vec3i(30, -21, -25)); // 30, -21, -25 - set.add(new Vec3i(-32, -21, -40)); // -32, -21, -40 - set.add(new Vec3i(-36, -20, 42)); // -36, -20, 42 - set.add(new Vec3i(-37, -21, -14)); // -37, -21, -14 - set.add(new Vec3i(-37, -21, -22)); // -37, -21, -22 - }); + private static final Map<String, Vec3i> keeperOffsets = Map.of( + "Diamond", new Vec3i(33, 0, 3), + "Lapis", new Vec3i(-33, 0, -3), + "Emerald", new Vec3i(-3, 0, 33), + "Gold", new Vec3i(3, 0, -33) + ); + private static final Set<Vec3i> knownChestOffsets = Set.of( + new Vec3i(-38, -22, 26), // -38, -22, 26 + new Vec3i(38, -22, -26), // 38, -22, -26 + new Vec3i(-40, -22, 18), // -40, -22, 18 + new Vec3i(-41, -20, 22), // -41, -20, 22 + new Vec3i(-5, -21, 16), // -5, -21, 16 + new Vec3i(40, -22, -30), // 40, -22, -30 + new Vec3i(-42, -20, -28), // -42, -20, -28 + new Vec3i(-43, -22, -40), // -43, -22, -40 + new Vec3i(42, -19, -41), // 42, -19, -41 + new Vec3i(43, -21, -16), // 43, -21, -16 + new Vec3i(-1, -22, -20), // -1, -22, -20 + new Vec3i(6, -21, 28), // 6, -21, 28 + new Vec3i(7, -21, 11), // 7, -21, 11 + new Vec3i(7, -21, 22), // 7, -21, 22 + new Vec3i(-12, -21, -44), // -12, -21, -44 + new Vec3i(12, -22, 31), // 12, -22, 31 + new Vec3i(12, -22, -22), // 12, -22, -22 + new Vec3i(12, -21, 7), // 12, -21, 7 + new Vec3i(12, -21, -43), // 12, -21, -43 + new Vec3i(-14, -21, 43), // -14, -21, 43 + new Vec3i(-14, -21, 22), // -14, -21, 22 + new Vec3i(-17, -21, 20), // -17, -21, 20 + new Vec3i(-20, -22, 0), // -20, -22, 0 + new Vec3i(1, -21, 20), // 1, -21, 20 + new Vec3i(19, -22, 29), // 19, -22, 29 + new Vec3i(20, -22, 0), // 20, -22, 0 + new Vec3i(20, -21, -26), // 20, -21, -26 + new Vec3i(-23, -22, 40), // -23, -22, 40 + new Vec3i(22, -21, -14), // 22, -21, -14 + new Vec3i(-24, -22, 12), // -24, -22, 12 + new Vec3i(23, -22, 26), // 23, -22, 26 + new Vec3i(23, -22, -39), // 23, -22, -39 + new Vec3i(24, -22, 27), // 24, -22, 27 + new Vec3i(25, -22, 17), // 25, -22, 17 + new Vec3i(29, -21, -44), // 29, -21, -44 + new Vec3i(-31, -21, -12), // -31, -21, -12 + new Vec3i(-31, -21, -40), // -31, -21, -40 + new Vec3i(30, -21, -25), // 30, -21, -25 + new Vec3i(-32, -21, -40), // -32, -21, -40 + new Vec3i(-36, -20, 42), // -36, -20, 42 + new Vec3i(-37, -21, -14), // -37, -21, -14 + new Vec3i(-37, -21, -22) // -37, -21, -22 + ); protected static Vec3i minesCenter = null; private static double previousDistance; @@ -107,7 +106,7 @@ public class MetalDetector { } //in the mines of divan Matcher treasureDistanceMature = TREASURE_PATTERN.matcher(text.getString()); - if (!treasureDistanceMature.matches()) { + if (!treasureDistanceMature.find()) { return; } //find new values @@ -164,7 +163,7 @@ public class MetalDetector { * Works out the possible locations the treasure could be using the distance the treasure is from the player and * narrows down possible locations until there is one left. * - * @param distance the distance the treasure is from the player squared + * @param distance the distance the treasure is from the player squared * @param playerPos the position of the player */ protected static void updatePossibleBlocks(double distance, Vec3d playerPos) { @@ -232,6 +231,7 @@ public class MetalDetector { /** * Renders waypoints for the location of treasure or possible treasure. + * * @param context world render context */ private static void render(WorldRenderContext context) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java index 53c4336e..86e8d23e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java @@ -9,6 +9,8 @@ import net.minecraft.component.type.ProfileComponent; import net.minecraft.enchantment.Enchantments; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import net.minecraft.registry.BuiltinRegistries; +import net.minecraft.registry.RegistryKeys; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -35,7 +37,7 @@ public class EndHudWidget extends Widget { static { ENDERMAN_HEAD.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of("MHF_Enderman"), Optional.empty(), new PropertyMap())); - POPPY.addEnchantment(Enchantments.INFINITY, 1); + POPPY.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); INSTANCE.setX(SkyblockerConfigManager.get().otherLocations.end.x); INSTANCE.setY(SkyblockerConfigManager.get().otherLocations.end.y); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java index b0282a69..e5a3a9fa 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.end; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; import de.hysky.skyblocker.utils.waypoint.Waypoint; @@ -117,7 +118,7 @@ public class EnderNodes { private long lastConfirmed; private EnderNode(BlockPos pos) { - super(pos, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType, DyeColor.CYAN.getColorComponents(), false); + super(pos, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType, ColorUtils.getFloatComponents(DyeColor.CYAN), false); } private void updateParticles() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java index ac961dd8..872d2823 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java @@ -5,6 +5,7 @@ import com.google.gson.JsonObject; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.HudRenderEvents; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.waypoint.Waypoint; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents; @@ -73,7 +74,7 @@ public class TheEnd { }); - HudRenderEvents.AFTER_MAIN_HUD.register((drawContext, tickDelta) -> { + HudRenderEvents.AFTER_MAIN_HUD.register((drawContext, tickCounter) -> { if (!Utils.isInTheEnd()) return; if (!SkyblockerConfigManager.get().otherLocations.end.hudEnabled) return; @@ -268,7 +269,7 @@ public class TheEnd { public record ProtectorLocation(int x, int z, Text name, Waypoint waypoint) { public ProtectorLocation(int x, int z, Text name) { - this(x, z, name, new Waypoint(new BlockPos(x, 0, z), Waypoint.Type.WAYPOINT, DyeColor.MAGENTA.getColorComponents())); + this(x, z, name, new Waypoint(new BlockPos(x, 0, z), Waypoint.Type.WAYPOINT, ColorUtils.getFloatComponents(DyeColor.MAGENTA))); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java b/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java new file mode 100644 index 00000000..e846a88a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java @@ -0,0 +1,185 @@ +package de.hysky.skyblocker.skyblock.events; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.logging.LogUtils; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.ints.IntList; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.sound.SoundEvent; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +public class EventNotifications { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static long currentTime = System.currentTimeMillis() / 1000; + + public static final String JACOBS = "Jacob's Farming Contest"; + + public static final IntList DEFAULT_REMINDERS = IntList.of(60, 60 * 5); + + public static final Map<String, ItemStack> eventIcons = Map.ofEntries( + Map.entry("Dark Auction", new ItemStack(Items.NETHER_BRICK)), + Map.entry("Bonus Fishing Festival", new ItemStack(Items.FISHING_ROD)), + Map.entry("Bonus Mining Fiesta", new ItemStack(Items.IRON_PICKAXE)), + Map.entry(JACOBS, new ItemStack(Items.IRON_HOE)), + Map.entry("New Year Celebration", new ItemStack(Items.CAKE)), + Map.entry("Election Over!", new ItemStack(Items.JUKEBOX)), + Map.entry("Election Booth Opens", new ItemStack(Items.JUKEBOX)), + Map.entry("Spooky Festival", new ItemStack(Items.JACK_O_LANTERN)), + Map.entry("Season of Jerry", new ItemStack(Items.SNOWBALL)), + Map.entry("Jerry's Workshop Opens", new ItemStack(Items.SNOW_BLOCK)), + Map.entry("Traveling Zoo", new ItemStack(Items.HAY_BLOCK)) // change to the custom head one day + ); + + public static void init() { + Scheduler.INSTANCE.scheduleCyclic(EventNotifications::timeUpdate, 20); + + SkyblockEvents.JOIN.register(EventNotifications::refreshEvents); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( + ClientCommandManager.literal("skyblocker").then( + ClientCommandManager.literal("debug").then( + ClientCommandManager.literal("toasts").then( + ClientCommandManager.argument("time", IntegerArgumentType.integer(0)) + .then(ClientCommandManager.argument("jacob", BoolArgumentType.bool()).executes(context -> { + long time = System.currentTimeMillis() / 1000 + context.getArgument("time", int.class); + if (context.getArgument("jacob", Boolean.class)) { + MinecraftClient.getInstance().getToastManager().add( + new JacobEventToast(time, "Jacob's farming contest", new String[]{"Cactus", "Cocoa Beans", "Pumpkin"}) + ); + } else { + MinecraftClient.getInstance().getToastManager().add( + new EventToast(time, "Jacob's or something idk", new ItemStack(Items.PAPER)) + ); + } + return 0; + } + ) + ) + ) + ) + ) + )); + } + + private static final Map<String, LinkedList<SkyblockEvent>> events = new ConcurrentHashMap<>(); + + public static Map<String, LinkedList<SkyblockEvent>> getEvents() { + return events; + } + + public static void refreshEvents() { + CompletableFuture.supplyAsync(() -> { + try { + JsonArray jsonElements = SkyblockerMod.GSON.fromJson(Http.sendGetRequest("https://hysky.de/api/calendar"), JsonArray.class); + return jsonElements.asList().stream().map(JsonElement::getAsJsonObject).toList(); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to download events list", e); + } + return List.<JsonObject>of(); + }).thenAccept(eventsList -> { + events.clear(); + for (JsonObject object : eventsList) { + if (object.get("timestamp").getAsLong() + object.get("duration").getAsInt() < currentTime) continue; + SkyblockEvent skyblockEvent = SkyblockEvent.of(object); + events.computeIfAbsent(object.get("event").getAsString(), s -> new LinkedList<>()).add(skyblockEvent); + } + + for (Map.Entry<String, LinkedList<SkyblockEvent>> entry : events.entrySet()) { + entry.getValue().sort(Comparator.comparingLong(SkyblockEvent::start)); // Sort just in case it's not in order for some reason in API + //LOGGER.info("Next {} is at {}", entry.getKey(), entry.getValue().peekFirst()); + } + + for (String s : events.keySet()) { + SkyblockerConfigManager.get().eventNotifications.eventsReminderTimes.computeIfAbsent(s, s1 -> DEFAULT_REMINDERS); + } + }).exceptionally(EventNotifications::itBorked); + } + + private static Void itBorked(Throwable throwable) { + LOGGER.error("[Skyblocker] Event loading borked, sowwy :(", throwable); + return null; + } + + + private static void timeUpdate() { + + long newTime = System.currentTimeMillis() / 1000; + for (Map.Entry<String, LinkedList<SkyblockEvent>> entry : events.entrySet()) { + LinkedList<SkyblockEvent> nextEvents = entry.getValue(); + SkyblockEvent skyblockEvent = nextEvents.peekFirst(); + if (skyblockEvent == null) continue; + + // Remove finished event + if (newTime > skyblockEvent.start() + skyblockEvent.duration()) { + nextEvents.pollFirst(); + skyblockEvent = nextEvents.peekFirst(); + if (skyblockEvent == null) continue; + } + String eventName = entry.getKey(); + List<Integer> reminderTimes = SkyblockerConfigManager.get().eventNotifications.eventsReminderTimes.getOrDefault(eventName, DEFAULT_REMINDERS); + if (reminderTimes.isEmpty()) continue; + + for (Integer reminderTime : reminderTimes) { + if (criterionMet() && currentTime + reminderTime < skyblockEvent.start() && newTime + reminderTime >= skyblockEvent.start()) { + MinecraftClient instance = MinecraftClient.getInstance(); + if (eventName.equals(JACOBS)) { + instance.getToastManager().add( + new JacobEventToast(skyblockEvent.start(), eventName, skyblockEvent.extras()) + ); + } else { + instance.getToastManager().add( + new EventToast(skyblockEvent.start(), eventName, eventIcons.getOrDefault(eventName, new ItemStack(Items.PAPER))) + ); + } + SoundEvent soundEvent = SkyblockerConfigManager.get().eventNotifications.reminderSound.getSoundEvent(); + if (soundEvent != null) + instance.getSoundManager().play(PositionedSoundInstance.master(soundEvent, 1f, 1f)); + break; + } + } + } + currentTime = newTime; + } + + private static boolean criterionMet() { + return switch (SkyblockerConfigManager.get().eventNotifications.criterion) { + case NONE -> false; + case SKYBLOCK -> Utils.isOnSkyblock(); + case HYPIXEL -> Utils.isOnHypixel(); + case EVERYWHERE -> true; + }; + } + + public record SkyblockEvent(long start, int duration, String[] extras, @Nullable String warpCommand) { + public static SkyblockEvent of(JsonObject jsonObject) { + String location = jsonObject.get("location").getAsString(); + location = location.isBlank() ? null : location; + return new SkyblockEvent(jsonObject.get("timestamp").getAsLong(), + jsonObject.get("duration").getAsInt(), + jsonObject.get("extras").getAsJsonArray().asList().stream().map(JsonElement::getAsString).toArray(String[]::new), + location); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java b/src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java new file mode 100644 index 00000000..48ea05c4 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java @@ -0,0 +1,93 @@ +package de.hysky.skyblocker.skyblock.events; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.Utils; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.toast.Toast; +import net.minecraft.client.toast.ToastManager; +import net.minecraft.item.ItemStack; +import net.minecraft.text.MutableText; +import net.minecraft.text.OrderedText; +import net.minecraft.text.Text; +import net.minecraft.util.Colors; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.util.List; + +public class EventToast implements Toast { + protected static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "notification"); + + private final long eventStartTime; + + protected final List<OrderedText> message; + protected final List<OrderedText> messageNow; + protected final int messageWidth; + protected final int messageNowWidth; + protected final ItemStack icon; + + protected boolean started; + + public EventToast(long eventStartTime, String name, ItemStack icon) { + this.eventStartTime = eventStartTime; + MutableText formatted = Text.translatable("skyblocker.events.startsSoon", Text.literal(name).formatted(Formatting.YELLOW)).formatted(Formatting.WHITE); + TextRenderer renderer = MinecraftClient.getInstance().textRenderer; + message = renderer.wrapLines(formatted, 150); + messageWidth = message.stream().mapToInt(renderer::getWidth).max().orElse(150); + + MutableText formattedNow = Text.translatable("skyblocker.events.startsNow", Text.literal(name).formatted(Formatting.YELLOW)).formatted(Formatting.WHITE); + messageNow = renderer.wrapLines(formattedNow, 150); + messageNowWidth = messageNow.stream().mapToInt(renderer::getWidth).max().orElse(150); + this.icon = icon; + this.started = eventStartTime - System.currentTimeMillis() / 1000 < 0; + + } + @Override + public Visibility draw(DrawContext context, ToastManager manager, long startTime) { + context.drawGuiTexture(TEXTURE, 0, 0, getWidth(), getHeight()); + + int y = (getHeight() - getInnerContentsHeight())/2; + y = 2 + drawMessage(context, 30, y, Colors.WHITE); + drawTimer(context, 30, y); + + context.drawItemWithoutEntity(icon, 8, getHeight()/2 - 8); + return startTime > 5_000 ? Visibility.HIDE: Visibility.SHOW; + } + + protected int drawMessage(DrawContext context, int x, int y, int color) { + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + for (OrderedText orderedText : started ? messageNow: message) { + context.drawText(textRenderer, orderedText, x, y, color, false); + y += textRenderer.fontHeight; + } + return y; + } + + protected void drawTimer(DrawContext context, int x, int y) { + long currentTime = System.currentTimeMillis() / 1000; + int timeTillEvent = (int) (eventStartTime - currentTime); + started = timeTillEvent < 0; + if (started) return; + + Text time = Utils.getDurationText(timeTillEvent); + + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + context.drawText(textRenderer, time, x, y, Colors.LIGHT_YELLOW, false); + } + + @Override + public int getWidth() { + return (started ? messageNowWidth: messageWidth) + 30 + 6; + } + + protected int getInnerContentsHeight() { + return message.size() * 9 + (started ? 0 : 9); + } + + @Override + public int getHeight() { + return Math.max(getInnerContentsHeight() + 12 + 2, 32); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java b/src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java new file mode 100644 index 00000000..43ed7d12 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java @@ -0,0 +1,60 @@ +package de.hysky.skyblocker.skyblock.events; + +import de.hysky.skyblocker.skyblock.tabhud.widget.JacobsContestWidget; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.toast.ToastManager; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Colors; +import net.minecraft.util.math.MathHelper; + +public class JacobEventToast extends EventToast { + + private final String[] crops; + + private static final ItemStack DEFAULT_ITEM = new ItemStack(Items.IRON_HOE); + + public JacobEventToast(long eventStartTime, String name, String[] crops) { + super(eventStartTime, name, new ItemStack(Items.IRON_HOE)); + this.crops = crops; + } + + @Override + public Visibility draw(DrawContext context, ToastManager manager, long startTime) { + context.drawGuiTexture(TEXTURE, 0, 0, getWidth(), getHeight()); + + int y = (getHeight() - getInnerContentsHeight()) / 2; + TextRenderer textRenderer = manager.getClient().textRenderer; + MatrixStack matrices = context.getMatrices(); + if (startTime < 3_000) { + int k = MathHelper.floor(Math.clamp((3_000 - startTime) / 200.0f, 0.0f, 1.0f) * 255.0f) << 24 | 0x4000000; + y = 2 + drawMessage(context, 30, y, 0xFFFFFF | k); + } else { + int k = (~MathHelper.floor(Math.clamp((startTime - 3_000) / 200.0f, 0.0f, 1.0f) * 255.0f)) << 24 | 0x4000000; + + + String s = "Crops:"; + int x = 30 + textRenderer.getWidth(s) + 4; + context.drawText(textRenderer, s, 30, 7 + (16 - textRenderer.fontHeight) / 2, Colors.WHITE, false); + for (int i = 0; i < crops.length; i++) { + context.drawItem(JacobsContestWidget.FARM_DATA.getOrDefault(crops[i], DEFAULT_ITEM), x + i * (16 + 8), 7); + } + // IDK how to make the items transparent, so I just redraw the texture on top + matrices.push(); + matrices.translate(0, 0, 400f); + RenderHelper.renderNineSliceColored(context, TEXTURE, 0, 0, getWidth(), getHeight(), 1f, 1f, 1f, (k >> 24) / 255f); + matrices.pop(); + y += textRenderer.fontHeight * message.size(); + } + matrices.push(); + matrices.translate(0, 0, 400f); + drawTimer(context, 30, y); + + context.drawItemWithoutEntity(icon, 8, getHeight() / 2 - 8); + matrices.pop(); + return startTime > 5_000 ? Visibility.HIDE : Visibility.SHOW; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java index 72c1370c..c60fdd1a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java @@ -52,16 +52,16 @@ public class FancyStatusBars { } public static void init() { - statusBars.put("health", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/health"), + statusBars.put("health", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/health"), new Color[]{new Color(255, 0, 0), new Color(255, 220, 0)}, true, new Color(255, 85, 85), Text.translatable("skyblocker.bars.config.health"))); - statusBars.put("intelligence", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/intelligence"), + statusBars.put("intelligence", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/intelligence"), new Color[]{new Color(0, 255, 255), new Color(180, 0, 255)}, true, new Color(85, 255, 255), Text.translatable("skyblocker.bars.config.intelligence"))); - statusBars.put("defense", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/defense"), + statusBars.put("defense", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/defense"), new Color[]{new Color(255, 255, 255)}, false, new Color(185, 185, 185), Text.translatable("skyblocker.bars.config.defense"))); - statusBars.put("experience", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/experience"), + statusBars.put("experience", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/experience"), new Color[]{new Color(100, 230, 70)}, false, new Color(128, 255, 32), Text.translatable("skyblocker.bars.config.experience"))); @@ -292,7 +292,7 @@ public class FancyStatusBars { Collection<StatusBar> barCollection = statusBars.values(); for (StatusBar statusBar : barCollection) { - if (statusBar.anchor != null) statusBar.render(context, -1, -1, client.getLastFrameDuration()); + if (statusBar.anchor != null) statusBar.render(context, -1, -1, client.getRenderTickCounter().getLastFrameDuration()); } for (StatusBar statusBar : barCollection) { if (statusBar.anchor != null && statusBar.showText()) statusBar.renderText(context); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java index 467691fd..6392f08a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java @@ -21,8 +21,8 @@ import java.util.function.Consumer; public class StatusBar implements Widget, Drawable, Element, Selectable { - private static final Identifier BAR_FILL = new Identifier(SkyblockerMod.NAMESPACE, "bars/bar_fill"); - private static final Identifier BAR_BACK = new Identifier(SkyblockerMod.NAMESPACE, "bars/bar_back"); + private static final Identifier BAR_FILL = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_fill"); + private static final Identifier BAR_BACK = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_back"); /* public static final Codec<StatusBar> CODEC = RecordCodecBuilder.create(instance -> instance.group( diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java index df072710..a412c1c5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java @@ -23,7 +23,7 @@ import java.util.HashMap; import java.util.Map; public class StatusBarsConfigScreen extends Screen { - private static final Identifier HOTBAR_TEXTURE = new Identifier("hud/hotbar"); + private static final Identifier HOTBAR_TEXTURE = Identifier.ofVanilla("hud/hotbar"); public static final long RESIZE_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_HRESIZE_CURSOR); @@ -293,6 +293,7 @@ public class StatusBarsConfigScreen extends Screen { if (button == 0) { cursorBar = statusBar; cursorBar.inMouse = true; + currentInsertLocation = BarLocation.of(cursorBar); if (statusBar.anchor != null) FancyStatusBars.barPositioner.removeBar(statusBar.anchor, statusBar.gridY, statusBar); FancyStatusBars.updatePositions(); @@ -322,6 +323,7 @@ public class StatusBarsConfigScreen extends Screen { public boolean mouseReleased(double mouseX, double mouseY, int button) { if (cursorBar != null) { cursorBar.inMouse = false; + currentInsertLocation = BarLocation.of(cursorBar); cursorBar = null; FancyStatusBars.updatePositions(); checkNullAnchor(FancyStatusBars.statusBars.values()); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java index 201e6716..8bed581e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java @@ -16,6 +16,8 @@ import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,17 +35,18 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit public class FarmingHud { private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class); public static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); - private static final Pattern COUNTER = Pattern.compile("Counter: (?<count>[\\d,]+) .+"); private static final Pattern FARMING_XP = Pattern.compile("§3\\+(?<xp>\\d+.?\\d*) Farming \\((?<percent>[\\d,]+.?\\d*)%\\)"); + private static final MinecraftClient client = MinecraftClient.getInstance(); + private static CounterType counterType = CounterType.NONE; private static final Deque<IntLongPair> counter = new ArrayDeque<>(); private static final LongPriorityQueue blockBreaks = new LongArrayFIFOQueue(); private static final Queue<FloatLongPair> farmingXp = new ArrayDeque<>(); private static float farmingXpPercentProgress; public static void init() { - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickDelta) -> { + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { if (shouldRender()) { - if (!counter.isEmpty() && counter.peek().rightLong() + 10_000 < System.currentTimeMillis()) { + if (!counter.isEmpty() && counter.peek().rightLong() + 5000 < System.currentTimeMillis()) { counter.poll(); } if (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < System.currentTimeMillis()) { @@ -53,17 +56,9 @@ public class FarmingHud { farmingXp.poll(); } - ItemStack stack = MinecraftClient.getInstance().player.getMainHandStack(); - Matcher matcher = ItemUtils.getLoreLineIfMatch(stack, FarmingHud.COUNTER); - if (matcher != null) { - try { - int count = NUMBER_FORMAT.parse(matcher.group("count")).intValue(); - if (counter.isEmpty() || counter.peekLast().leftInt() != count) { - counter.offer(IntLongPair.of(count, System.currentTimeMillis())); - } - } catch (ParseException e) { - LOGGER.error("[Skyblocker Farming HUD] Failed to parse counter", e); - } + ItemStack stack = client.player.getMainHandStack(); + if (stack == null || !tryGetCounter(stack, CounterType.CULTIVATING) && !tryGetCounter(stack, CounterType.COUNTER)) { + counterType = CounterType.NONE; } FarmingHudWidget.INSTANCE.update(); @@ -92,8 +87,26 @@ public class FarmingHud { .executes(Scheduler.queueOpenScreenCommand(() -> new FarmingHudConfigScreen(null))))))); } + private static boolean tryGetCounter(ItemStack stack, CounterType counterType) { + NbtCompound customData = ItemUtils.getCustomData(stack); + if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return false; + int count = customData.getInt(counterType.nbtKey); + if (FarmingHud.counterType != counterType) { + counter.clear(); + FarmingHud.counterType = counterType; + } + if (counter.isEmpty() || counter.peekLast().leftInt() != count) { + counter.offer(IntLongPair.of(count, System.currentTimeMillis())); + } + return true; + } + private static boolean shouldRender() { - return SkyblockerConfigManager.get().farming.garden.farmingHud.enableHud && Utils.getLocation() == Location.GARDEN; + return SkyblockerConfigManager.get().farming.garden.farmingHud.enableHud && client.player != null && Utils.getLocation() == Location.GARDEN; + } + + public static String counterText() { + return counterType.text; } public static int counter() { @@ -120,4 +133,21 @@ public class FarmingHud { public static double farmingXpPerHour() { return farmingXp.stream().mapToDouble(FloatLongPair::leftFloat).sum() * blockBreaks() * 1800; // Hypixel only sends xp updates around every half a second } + + public enum CounterType { + NONE("", "No Counter"), + COUNTER("mined_crops", "Counter: "), + CULTIVATING("farmed_cultivating", "Cultivating Counter: "); + + private final String nbtKey; + private final String text; + + CounterType(String nbtKey, String text) { + this.nbtKey = nbtKey; + this.text = text; + } + public boolean matchesText(String textToMatch) { + return this.text.equals(textToMatch); + } + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java index f12f6fa8..84013f0b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java @@ -1,11 +1,15 @@ package de.hysky.skyblocker.skyblock.garden; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.widget.Widget; import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent; import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.item.ItemStack; @@ -18,31 +22,31 @@ import java.util.Map; public class FarmingHudWidget extends Widget { private static final MutableText TITLE = Text.literal("Farming").formatted(Formatting.YELLOW, Formatting.BOLD); - public static final Map<String, ItemStack> FARMING_TOOLS = Map.ofEntries( - Map.entry("THEORETICAL_HOE_WHEAT_1", Ico.WHEAT), - Map.entry("THEORETICAL_HOE_WHEAT_2", Ico.WHEAT), - Map.entry("THEORETICAL_HOE_WHEAT_3", Ico.WHEAT), - Map.entry("THEORETICAL_HOE_CARROT_1", Ico.CARROT), - Map.entry("THEORETICAL_HOE_CARROT_2", Ico.CARROT), - Map.entry("THEORETICAL_HOE_CARROT_3", Ico.CARROT), - Map.entry("THEORETICAL_HOE_POTATO_1", Ico.POTATO), - Map.entry("THEORETICAL_HOE_POTATO_2", Ico.POTATO), - Map.entry("THEORETICAL_HOE_POTATO_3", Ico.POTATO), - Map.entry("THEORETICAL_HOE_CANE_1", Ico.SUGAR_CANE), - Map.entry("THEORETICAL_HOE_CANE_2", Ico.SUGAR_CANE), - Map.entry("THEORETICAL_HOE_CANE_3", Ico.SUGAR_CANE), - Map.entry("THEORETICAL_HOE_WARTS_1", Ico.NETHER_WART), - Map.entry("THEORETICAL_HOE_WARTS_2", Ico.NETHER_WART), - Map.entry("THEORETICAL_HOE_WARTS_3", Ico.NETHER_WART), - Map.entry("FUNGI_CUTTER", Ico.MUSHROOM), - Map.entry("CACTUS_KNIFE", Ico.CACTUS), - Map.entry("MELON_DICER", Ico.MELON), - Map.entry("MELON_DICER_2", Ico.MELON), - Map.entry("MELON_DICER_3", Ico.MELON), - Map.entry("PUMPKIN_DICER", Ico.PUMPKIN), - Map.entry("PUMPKIN_DICER_2", Ico.PUMPKIN), - Map.entry("PUMPKIN_DICER_3", Ico.PUMPKIN), - Map.entry("COCO_CHOPPER", Ico.COCOA_BEANS) + public static final Map<String, String> FARMING_TOOLS = Map.ofEntries( + Map.entry("THEORETICAL_HOE_WHEAT_1", "WHEAT"), + Map.entry("THEORETICAL_HOE_WHEAT_2", "WHEAT"), + Map.entry("THEORETICAL_HOE_WHEAT_3", "WHEAT"), + Map.entry("THEORETICAL_HOE_CARROT_1", "CARROT_ITEM"), + Map.entry("THEORETICAL_HOE_CARROT_2", "CARROT_ITEM"), + Map.entry("THEORETICAL_HOE_CARROT_3", "CARROT_ITEM"), + Map.entry("THEORETICAL_HOE_POTATO_1", "POTATO_ITEM"), + Map.entry("THEORETICAL_HOE_POTATO_2", "POTATO_ITEM"), + Map.entry("THEORETICAL_HOE_POTATO_3", "POTATO_ITEM"), + Map.entry("THEORETICAL_HOE_CANE_1", "SUGAR_CANE"), + Map.entry("THEORETICAL_HOE_CANE_2", "SUGAR_CANE"), + Map.entry("THEORETICAL_HOE_CANE_3", "SUGAR_CANE"), + Map.entry("THEORETICAL_HOE_WARTS_1", "NETHER_STALK"), + Map.entry("THEORETICAL_HOE_WARTS_2", "NETHER_STALK"), + Map.entry("THEORETICAL_HOE_WARTS_3", "NETHER_STALK"), + Map.entry("FUNGI_CUTTER", "RED_MUSHROOM"), + Map.entry("CACTUS_KNIFE", "CACTUS"), + Map.entry("MELON_DICER", "MELON"), + Map.entry("MELON_DICER_2", "MELON"), + Map.entry("MELON_DICER_3", "MELON"), + Map.entry("PUMPKIN_DICER", "PUMPKIN"), + Map.entry("PUMPKIN_DICER_2", "PUMPKIN"), + Map.entry("PUMPKIN_DICER_3", "PUMPKIN"), + Map.entry("COCO_CHOPPER", "INK_SACK:3") ); public static final FarmingHudWidget INSTANCE = new FarmingHudWidget(); private final MinecraftClient client = MinecraftClient.getInstance(); @@ -56,21 +60,45 @@ public class FarmingHudWidget extends Widget { @Override public void updateContent() { - ItemStack icon = client.player == null ? Ico.HOE : FARMING_TOOLS.getOrDefault(ItemUtils.getItemId(client.player.getMainHandStack()), Ico.HOE); - addSimpleIcoText(icon, "Counter: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format(FarmingHud.counter())); - addSimpleIcoText(icon, "Crops/min: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) FarmingHud.cropsPerMinute() / 100 * 100)); - addSimpleIcoText(icon, "Blocks/s: ", Formatting.YELLOW, Integer.toString(FarmingHud.blockBreaks())); + if (client.player == null) return; + ItemStack farmingToolStack = client.player.getMainHandStack(); + if (farmingToolStack == null) return; + String cropItemId = FARMING_TOOLS.get(ItemUtils.getItemId(farmingToolStack)); + ItemStack cropStack = ItemRepository.getItemStack(ItemTooltip.getNeuName(cropItemId, cropItemId)); // The cropItemId is being used as the api id in the second parameter because the skyblock id and api id are the same for all crops. + + String counterText = FarmingHud.counterText(); + String counterNumber = FarmingHud.NUMBER_FORMAT.format(FarmingHud.counter()); + if (FarmingHud.CounterType.NONE.matchesText(counterText)) counterNumber = ""; + addSimpleIcoText(cropStack, counterText, Formatting.YELLOW, counterNumber); + float cropsPerMinute = FarmingHud.cropsPerMinute(); + addSimpleIcoText(cropStack, "Crops/min: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) cropsPerMinute / 10 * 10)); + addSimpleIcoText(Ico.GOLD, "Coins/h: ", Formatting.GOLD, getPriceText(cropItemId, cropsPerMinute)); + addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Integer.toString(FarmingHud.blockBreaks())); //noinspection DataFlowIssue addComponent(new ProgressComponent(Ico.LANTERN, Text.literal("Farming Level: "), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue())); addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) FarmingHud.farmingXpPerHour())); Entity cameraEntity = client.getCameraEntity(); - double yaw = cameraEntity == null ? 0.0d : cameraEntity.getYaw(); - double pitch = cameraEntity == null ? 0.0d : cameraEntity.getPitch(); - addComponent(new PlainTextComponent(Text.literal("Yaw: " + String.format("%.3f", MathHelper.wrapDegrees(yaw))).formatted(Formatting.YELLOW))); - addComponent(new PlainTextComponent(Text.literal("Pitch: " + String.format("%.3f", MathHelper.wrapDegrees(pitch))).formatted(Formatting.YELLOW))); + String yaw = cameraEntity == null ? "No Camera Entity" : String.format("%.2f", MathHelper.wrapDegrees(cameraEntity.getYaw())); + String pitch = cameraEntity == null ? "No Camera Entity" : String.format("%.2f", MathHelper.wrapDegrees(cameraEntity.getPitch())); + addComponent(new PlainTextComponent(Text.literal("Yaw: " + yaw).formatted(Formatting.GOLD))); + addComponent(new PlainTextComponent(Text.literal("Pitch: " + pitch).formatted(Formatting.GOLD))); if (LowerSensitivity.isSensitivityLowered()) { addComponent(new PlainTextComponent(Text.translatable("skyblocker.garden.hud.mouseLocked").formatted(Formatting.ITALIC))); } } + + /** + * Gets the price text of the given crop id, calculated with the given crops per minute and the npc price if it's higher than the bazaar sell price, or the bazaar sell price otherwise. + */ + private String getPriceText(String cropItemId, float cropsPerMinute) { + DoubleBooleanPair itemBazaarPrice = ItemUtils.getItemPrice(cropItemId); // Gets the bazaar sell price of the crop. + double itemNpcPrice = TooltipInfoType.NPC.hasOrNullWarning(cropItemId) ? TooltipInfoType.NPC.getData().get(cropItemId).getAsDouble() : Double.MIN_VALUE; // Gets the npc sell price of the crop or set to the min double value if it doesn't exist. + boolean shouldUseNpcPrice = itemNpcPrice > itemBazaarPrice.leftDouble(); // Use the npc price if it's more than the bazaar sell price. + double price = shouldUseNpcPrice ? itemNpcPrice : itemBazaarPrice.leftDouble(); // same as above + + // Return the formatted price if npc price is higher or bazaar price is present. + // Multiply by 60 to convert to hourly and divide by 100 for rounding is combined into multiplying by 0.6. + return shouldUseNpcPrice || itemBazaarPrice.rightBoolean() ? FarmingHud.NUMBER_FORMAT.format((int) (price * cropsPerMinute * 0.6) * 100) : "No Data"; + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/LowerSensitivity.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/LowerSensitivity.java index ee857618..2bfb7fb2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/LowerSensitivity.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/LowerSensitivity.java @@ -9,28 +9,23 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.item.ItemStack; public class LowerSensitivity { - private static boolean sensitivityLowered = false; public static void init() { ClientTickEvents.END_WORLD_TICK.register(world -> { - if (!Utils.isOnSkyblock() || Utils.getLocation() != Location.GARDEN || MinecraftClient.getInstance().player == null) { + if (Utils.getLocation() != Location.GARDEN || MinecraftClient.getInstance().player == null || !SkyblockerConfigManager.get().farming.garden.lockMouseTool) { if (sensitivityLowered) lowerSensitivity(false); return; } - if (SkyblockerConfigManager.get().farming.garden.lockMouseTool) { - ItemStack mainHandStack = MinecraftClient.getInstance().player.getMainHandStack(); - String itemId = ItemUtils.getItemId(mainHandStack); - boolean shouldLockMouse = FarmingHudWidget.FARMING_TOOLS.containsKey(itemId) && (!SkyblockerConfigManager.get().farming.garden.lockMouseGroundOnly || MinecraftClient.getInstance().player.isOnGround()); - if (shouldLockMouse && !sensitivityLowered) lowerSensitivity(true); - else if (!shouldLockMouse && sensitivityLowered) lowerSensitivity(false); - - } + ItemStack mainHandStack = MinecraftClient.getInstance().player.getMainHandStack(); + String itemId = ItemUtils.getItemId(mainHandStack); + boolean shouldLockMouse = FarmingHudWidget.FARMING_TOOLS.containsKey(itemId) && (!SkyblockerConfigManager.get().farming.garden.lockMouseGroundOnly || MinecraftClient.getInstance().player.isOnGround()); + if (shouldLockMouse && !sensitivityLowered) lowerSensitivity(true); + else if (!shouldLockMouse && sensitivityLowered) lowerSensitivity(false); }); } public static void lowerSensitivity(boolean lowerSensitivity) { - if (sensitivityLowered == lowerSensitivity) return; sensitivityLowered = lowerSensitivity; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java index 682933f4..d8f4dad7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.garden; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.NEURepoManager; import de.hysky.skyblocker.utils.Utils; @@ -12,6 +13,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.HandledScreen; @@ -40,7 +42,8 @@ public class VisitorHelper { private static final Map<String, ItemStack> itemCache = new HashMap<>(); private static final int TEXT_START_X = 4; private static final int TEXT_START_Y = 4; - private static final int TEXT_INDENT = 8; + private static final int ENTRY_INDENT = 8; + private static final int ITEM_INDENT = 20; private static final int LINE_SPACING = 3; public static void init() { @@ -59,22 +62,32 @@ public class VisitorHelper { public static void onMouseClicked(double mouseX, double mouseY, int mouseButton, TextRenderer textRenderer) { int yPosition = TEXT_START_Y; - for (Map.Entry<Pair<String, String>, Object2IntMap<String>> visitorEntry : itemMap.entrySet()) { - int textWidth; - int textHeight = textRenderer.fontHeight; - - yPosition += LINE_SPACING + textHeight; + yPosition += LINE_SPACING + textRenderer.fontHeight; for (Object2IntMap.Entry<String> itemEntry : visitorEntry.getValue().object2IntEntrySet()) { String itemText = itemEntry.getKey(); - textWidth = textRenderer.getWidth(itemText + " x" + itemEntry.getIntValue()); + int textWidth = textRenderer.getWidth(itemText + " x" + itemEntry.getIntValue()); - if (isMouseOverText(mouseX, mouseY, TEXT_START_X + TEXT_INDENT, yPosition, textWidth, textHeight)) { + // Check if the mouse is over the item text + // The text starts at `TEXT_START_X + ENTRY_INDENT + ITEM_INDENT` + if (isMouseOverText(mouseX, mouseY, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT, yPosition, textWidth, textRenderer.fontHeight)) { + // Send command to buy the item from the bazaar MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemText); return; } - yPosition += LINE_SPACING + textHeight; + + // Check if the mouse is over the copy amount text + // The copy amount text starts at `TEXT_START_X + ENTRY_INDENT + ITEM_INDENT + textWidth` + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player != null && isMouseOverText(mouseX, mouseY, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT + textWidth, yPosition, textRenderer.getWidth(" [Copy Amount]"), textRenderer.fontHeight)) { + // Copy the amount to the clipboard + client.keyboard.setClipboard(String.valueOf(itemEntry.getIntValue())); + client.player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); + return; + } + + yPosition += LINE_SPACING + textRenderer.fontHeight; } } } @@ -112,7 +125,7 @@ public class VisitorHelper { } } - private static void updateItemMap(String visitorName, @Nullable String visitorTexture, Text lore) { + private static void updateItemMap(String visitorName, @Nullable String visitorTexture, Text lore) { String[] splitItemText = lore.getString().split(" x"); String itemName = splitItemText[0].trim(); if (itemName.isEmpty()) return; @@ -168,12 +181,23 @@ public class VisitorHelper { return stack; } - private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, @Nullable ItemStack stack, String itemName, int amount, int index, int mouseX, int mousseY) { - Text text = stack != null ? stack.getName().copy().append(" x" + amount) : Text.literal(itemName + " x" + amount); - drawTextWithOptionalUnderline(context, textRenderer, text, TEXT_START_X + TEXT_INDENT, TEXT_START_Y + (index * (LINE_SPACING + textRenderer.fontHeight)), mouseX, mousseY); + /** + * Draws the item entry, amount, and copy amount text with optional underline and the item icon + */ + private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, @Nullable ItemStack stack, String itemName, int amount, int index, int mouseX, int mouseY) { + Text text = stack != null ? stack.getName().copy().append(" x" + amount) : Text.literal(itemName + " x" + amount); + Text copyAmount = Text.literal(" [Copy Amount]"); + + // Calculate the y position of the text with index as the line number + int y = TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight); + // Draw the item and amount text + drawTextWithOptionalUnderline(context, textRenderer, text, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT, y, mouseX, mouseY); + // Draw the copy amount text separately after the item and amount text + drawTextWithOptionalUnderline(context, textRenderer, copyAmount, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT + textRenderer.getWidth(text), y, mouseX, mouseY); + // drawItem adds 150 to the z, which puts our z at 350, above the item in the slot (250) and their text (300) and below the cursor stack (382) and their text (432) if (stack != null) { - context.drawItem(stack, TEXT_START_X + TEXT_INDENT + 2 + textRenderer.getWidth(text), TEXT_START_Y + (index * (LINE_SPACING + textRenderer.fontHeight)) - textRenderer.fontHeight + 5); + context.drawItem(stack, TEXT_START_X + ENTRY_INDENT, y - textRenderer.fontHeight + 5); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java index 65e1b138..6170eab1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java @@ -47,14 +47,14 @@ public class CustomArmorTrims { } private static void initializeTrimCache() { - ClientPlayerEntity player = MinecraftClient.getInstance().player; + MinecraftClient client = MinecraftClient.getInstance(); FabricLoader loader = FabricLoader.getInstance(); - if (trimsInitialized || (player == null && !Debug.debugEnabled())) { + if (trimsInitialized || (client == null && !Debug.debugEnabled())) { return; } try { TRIMS_CACHE.clear(); - RegistryWrapper.WrapperLookup wrapperLookup = getWrapperLookup(loader, player); + RegistryWrapper.WrapperLookup wrapperLookup = getWrapperLookup(loader, client); for (Reference<ArmorTrimMaterial> material : wrapperLookup.getWrapperOrThrow(RegistryKeys.TRIM_MATERIAL).streamEntries().toList()) { for (Reference<ArmorTrimPattern> pattern : wrapperLookup.getWrapperOrThrow(RegistryKeys.TRIM_PATTERN).streamEntries().toList()) { ArmorTrim trim = new ArmorTrim(material, pattern); @@ -70,8 +70,8 @@ public class CustomArmorTrims { } } - private static RegistryWrapper.WrapperLookup getWrapperLookup(FabricLoader loader, ClientPlayerEntity player) { - return !Debug.debugEnabled() ? player.networkHandler.getRegistryManager() : BuiltinRegistries.createWrapperLookup(); + private static RegistryWrapper.WrapperLookup getWrapperLookup(FabricLoader loader, MinecraftClient client) { + return client != null && client.getNetworkHandler() != null && client.getNetworkHandler().getRegistryManager() != null ? client.getNetworkHandler().getRegistryManager() : BuiltinRegistries.createWrapperLookup(); } private static void registerCommand(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { @@ -140,8 +140,8 @@ public class CustomArmorTrims { public record ArmorTrimId(@SerialEntry Identifier material, @SerialEntry Identifier pattern) implements Pair<Identifier, Identifier> { public static final Codec<ArmorTrimId> CODEC = RecordCodecBuilder.create(instance -> instance.group( - Identifier.CODEC.fieldOf("material").forGetter(ArmorTrimId::material), - Identifier.CODEC.fieldOf("pattern").forGetter(ArmorTrimId::pattern)) + Identifier.CODEC.fieldOf("material").forGetter(ArmorTrimId::material), + Identifier.CODEC.fieldOf("pattern").forGetter(ArmorTrimId::pattern)) .apply(instance, ArmorTrimId::new)); @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java index 96c21d22..93d29714 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java @@ -1,9 +1,9 @@ package de.hysky.skyblocker.skyblock.item; -import com.google.gson.JsonElement; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.PetCache; +import de.hysky.skyblocker.skyblock.PetCache.PetInfo; import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.ProfileUtils; import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.minecraft.block.BlockState; @@ -36,8 +36,8 @@ public class ItemCooldowns { 561700, 611700, 666700, 726700, 791700, 861700, 936700, 1016700, 1101700, 1191700, 1286700, 1386700, 1496700, 1616700, 1746700, 1886700 }; - public static int monkeyLevel = 1; - public static double monkeyExp = 0; + private static int monkeyLevel = 1; + private static double monkeyExp = 0; public static void init() { ClientPlayerBlockBreakEvents.AFTER.register(ItemCooldowns::afterBlockBreak); @@ -45,30 +45,24 @@ public class ItemCooldowns { } public static void updateCooldown() { - ProfileUtils.updateProfile().thenAccept(player -> { - for (JsonElement pet : player.getAsJsonObject("pets_data").getAsJsonArray("pets")) { - if (!pet.getAsJsonObject().get("type").getAsString().equals("MONKEY")) continue; - if (!pet.getAsJsonObject().get("active").getAsString().equals("true")) continue; - if (pet.getAsJsonObject().get("tier").getAsString().equals("LEGENDARY")) { - monkeyExp = Double.parseDouble(pet.getAsJsonObject().get("exp").getAsString()); - monkeyLevel = 0; - for (int xpLevel : EXPERIENCE_LEVELS) { - if (monkeyExp < xpLevel) { - break; - } else { - monkeyExp -= xpLevel; - monkeyLevel++; - } - } + PetInfo pet = PetCache.getCurrentPet(); + + if (pet != null && pet.tier().equals("LEGENDARY")) { + monkeyExp = pet.exp(); + + monkeyLevel = 0; + for (int xpLevel : EXPERIENCE_LEVELS) { + if (monkeyExp < xpLevel) { + break; + } else { + monkeyExp -= xpLevel; + monkeyLevel++; } } - }).exceptionally(e -> { - ProfileUtils.LOGGER.error("[Skyblocker Item Cooldown] Failed to get Player Pet Data, is the API Down/Limited?", e); - return null; - }); + } } - private static int getCooldown() { + private static int getCooldown4Foraging() { int baseCooldown = 2000; int monkeyPetCooldownReduction = baseCooldown * monkeyLevel / 200; return baseCooldown - monkeyPetCooldownReduction; @@ -82,7 +76,7 @@ public class ItemCooldowns { if (usedItemId.equals(JUNGLE_AXE_ID) || usedItemId.equals(TREECAPITATOR_ID)) { updateCooldown(); if (!isOnCooldown(JUNGLE_AXE_ID) || !isOnCooldown(TREECAPITATOR_ID)) { - ITEM_COOLDOWNS.put(usedItemId, new CooldownEntry(getCooldown())); + ITEM_COOLDOWNS.put(usedItemId, new CooldownEntry(getCooldown4Foraging())); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java index c78724ca..214ecc84 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java @@ -1,22 +1,35 @@ package de.hysky.skyblocker.skyblock.item; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; -import com.mojang.util.UndashedUuid; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Http.ApiResponse; +import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.item.ItemStack; import net.minecraft.nbt.*; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.collection.DefaultedList; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,17 +43,36 @@ import java.nio.file.Path; import java.util.Base64; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; public class MuseumItemCache { private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class); private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json"); private static final Map<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>(); private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}"; + public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation"; + private static final int CONFIRM_DONATION_BUTTON_SLOT = 20; private static CompletableFuture<Void> loaded; public static void init() { ClientLifecycleEvents.CLIENT_STARTED.register(MuseumItemCache::load); + ClientCommandRegistrationCallback.EVENT.register(MuseumItemCache::registerCommands); + } + + private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(literal(SkyblockerMod.NAMESPACE) + .then(literal("museum") + .then(literal("resync") + .executes(context -> { + if (tryResync(context.getSource())) { + context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.attemptingResync"))); + } else { + context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.cannotResync"))); + } + + return Command.SINGLE_SUCCESS; + })))); } private static void load(MinecraftClient client) { @@ -67,7 +99,35 @@ public class MuseumItemCache { }); } + public static void handleClick(Slot slot, int slotId, DefaultedList<Slot> slots) { + if (slotId == CONFIRM_DONATION_BUTTON_SLOT) { + //Slots 0 to 17 can have items, well not all but thats the general range + for (int i = 0; i < 17; i++) { + ItemStack stack = slots.get(i).getStack(); + + if (!stack.isEmpty()) { + String itemId = ItemUtils.getItemId(stack); + String profileId = Utils.getProfileId(); + + if (!itemId.isEmpty() && !profileId.isEmpty()) { + String uuid = Utils.getUndashedUuid(); + //Be safe about access to avoid NPEs + Map<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); + playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY.get()); + + playerData.get(profileId).collectedItemIds().add(itemId); + save(); + } + } + } + } + } + private static void updateData4ProfileMember(String uuid, String profileId) { + updateData4ProfileMember(uuid, profileId, null); + } + + private static void updateData4ProfileMember(String uuid, String profileId, FabricClientCommandSource source) { CompletableFuture.runAsync(() -> { try (ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId)) { //The request was successful @@ -103,58 +163,85 @@ public class MuseumItemCache { MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds)); save(); + if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncSuccess"))); + LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId); } else { //If the player's Museum API is disabled putEmpty(uuid, profileId); + if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure"))); + LOGGER.warn(ERROR_LOG_TEMPLATE + " because the Museum API is disabled!", profileId); } } else { //If the request returns a non 200 status code putEmpty(uuid, profileId); + if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure"))); + LOGGER.error(ERROR_LOG_TEMPLATE + " because a non 200 status code was encountered! Status Code: {}", profileId, response.statusCode()); } } catch (Exception e) { //If an exception was somehow thrown putEmpty(uuid, profileId); + if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure"))); + LOGGER.error(ERROR_LOG_TEMPLATE, profileId, e); } }); } private static void putEmpty(String uuid, String profileId) { - MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), ObjectOpenHashSet.of())); + //Only put new data if they didn't have any before + if (!MUSEUM_ITEM_CACHE.get(uuid).containsKey(profileId)) { + MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), ObjectOpenHashSet.of())); + } + save(); } + private static boolean tryResync(FabricClientCommandSource source) { + String uuid = Utils.getUndashedUuid(); + String profileId = Utils.getProfileId(); + + //Only allow resyncing if the data is actually present yet, otherwise the player needs to swap servers for the tick method to be called + if (loaded.isDone() && !profileId.isEmpty() && MUSEUM_ITEM_CACHE.containsKey(uuid) && MUSEUM_ITEM_CACHE.get(uuid).containsKey(profileId) && MUSEUM_ITEM_CACHE.get(uuid).get(profileId).canResync()) { + updateData4ProfileMember(uuid, profileId, source); + + return true; + } + + return false; + } + /** - * The cache is ticked upon switching skyblock servers + * The cache is ticked upon switching Skyblock servers. Only loads from the API if the profile wasn't cached yet. */ public static void tick(String profileId) { - if (loaded.isDone()) { - String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + String uuid = Utils.getUndashedUuid(); + + if (loaded.isDone() && (!MUSEUM_ITEM_CACHE.containsKey(uuid) || !MUSEUM_ITEM_CACHE.getOrDefault(uuid, new Object2ObjectOpenHashMap<>()).containsKey(profileId))) { Map<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); - playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY); + playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY.get()); - if (playerData.get(profileId).stale()) updateData4ProfileMember(uuid, profileId); + updateData4ProfileMember(uuid, profileId); } } public static boolean hasItemInMuseum(String id) { - String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + String uuid = Utils.getUndashedUuid(); ObjectOpenHashSet<String> collectedItemIds = (!MUSEUM_ITEM_CACHE.containsKey(uuid) || Utils.getProfileId().isBlank() || !MUSEUM_ITEM_CACHE.get(uuid).containsKey(Utils.getProfileId())) ? null : MUSEUM_ITEM_CACHE.get(uuid).get(Utils.getProfileId()).collectedItemIds(); return collectedItemIds != null && collectedItemIds.contains(id); } - private record ProfileMuseumData(long lastUpdated, ObjectOpenHashSet<String> collectedItemIds) { - private static final ProfileMuseumData EMPTY = new ProfileMuseumData(0L, null); - private static final long MAX_AGE = 86_400_000; + private record ProfileMuseumData(long lastResync, ObjectOpenHashSet<String> collectedItemIds) { + private static final Supplier<ProfileMuseumData> EMPTY = () -> new ProfileMuseumData(0L, new ObjectOpenHashSet<>()); + private static final long TIME_BETWEEN_RESYNCING_ALLOWED = 600_000L; private static final Codec<ProfileMuseumData> CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.LONG.fieldOf("lastUpdated").forGetter(ProfileMuseumData::lastUpdated), + Codec.LONG.fieldOf("lastResync").forGetter(ProfileMuseumData::lastResync), Codec.STRING.listOf() .xmap(ObjectOpenHashSet::new, ObjectArrayList::new) .fieldOf("collectedItemIds") @@ -165,8 +252,8 @@ public class MuseumItemCache { .xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new) ).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new); - private boolean stale() { - return System.currentTimeMillis() > lastUpdated + MAX_AGE; + private boolean canResync() { + return this.lastResync + TIME_BETWEEN_RESYNCING_ALLOWED < System.currentTimeMillis(); } } -}
\ No newline at end of file +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java index a09b260a..e7cc61ae 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java @@ -15,10 +15,12 @@ import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import net.minecraft.recipe.CraftingRecipe; import net.minecraft.recipe.Recipe; import net.minecraft.recipe.RecipeEntry; import net.minecraft.recipe.RecipeMatcher; import net.minecraft.recipe.book.RecipeBookCategory; +import net.minecraft.recipe.input.CraftingRecipeInput; import net.minecraft.screen.AbstractRecipeScreenHandler; import net.minecraft.screen.ScreenHandlerType; import net.minecraft.screen.slot.Slot; @@ -28,14 +30,14 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; public class SkyblockCraftingTableScreen extends HandledScreen<SkyblockCraftingTableScreenHandler> { - private static final Identifier TEXTURE = new Identifier("textures/gui/container/crafting_table.png"); + private static final Identifier TEXTURE = Identifier.ofVanilla("textures/gui/container/crafting_table.png"); protected static final ButtonTextures MORE_CRAFTS_TEXTURES = new ButtonTextures( - new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/more_button"), - new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/more_button_disabled"), - new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/more_button_highlighted") + Identifier.of(SkyblockerMod.NAMESPACE, "quick_craft/more_button"), + Identifier.of(SkyblockerMod.NAMESPACE, "quick_craft/more_button_disabled"), + Identifier.of(SkyblockerMod.NAMESPACE, "quick_craft/more_button_highlighted") ); - protected static final Identifier QUICK_CRAFT = new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/quick_craft_overlay"); + protected static final Identifier QUICK_CRAFT = Identifier.of(SkyblockerMod.NAMESPACE, "quick_craft/quick_craft_overlay"); private final ItemListWidget recipeBook = new ItemListWidget(); private boolean narrow; private TexturedButtonWidget moreCraftsButton; @@ -139,7 +141,7 @@ public class SkyblockCraftingTableScreen extends HandledScreen<SkyblockCraftingT } - static class DummyRecipeScreenHandler extends AbstractRecipeScreenHandler<SimpleInventory> { + static class DummyRecipeScreenHandler extends AbstractRecipeScreenHandler<CraftingRecipeInput, CraftingRecipe> { public DummyRecipeScreenHandler() { super(ScreenHandlerType.GENERIC_9X6, -69); @@ -152,7 +154,7 @@ public class SkyblockCraftingTableScreen extends HandledScreen<SkyblockCraftingT public void clearCraftingSlots() {} @Override - public boolean matches(RecipeEntry<? extends Recipe<SimpleInventory>> recipe) { + public boolean matches(RecipeEntry<CraftingRecipe> recipe) { return false; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java new file mode 100644 index 00000000..66c02ca1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java @@ -0,0 +1,21 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +import net.minecraft.text.Text; + +public record SlotText(Text text, TextPosition position) { + public static SlotText bottomLeft(Text text) { + return new SlotText(text, TextPosition.BOTTOM_LEFT); + } + + public static SlotText bottomRight(Text text) { + return new SlotText(text, TextPosition.BOTTOM_RIGHT); + } + + public static SlotText topLeft(Text text) { + return new SlotText(text, TextPosition.TOP_LEFT); + } + + public static SlotText topRight(Text text) { + return new SlotText(text, TextPosition.TOP_RIGHT); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java new file mode 100644 index 00000000..71d9aa30 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java @@ -0,0 +1,58 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.render.gui.AbstractContainerMatcher; +import net.minecraft.screen.slot.Slot; +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * Extend this class and add it to {@link SlotTextManager#adders} to add text to any arbitrary slot. + */ +public abstract class SlotTextAdder extends AbstractContainerMatcher { + /** + * Utility constructor that will compile the given string into a pattern. + * + * @see #SlotTextAdder(Pattern) + */ + protected SlotTextAdder(@NotNull @Language("RegExp") String titlePattern) { + super(titlePattern); + } + + /** + * Creates a SlotTextAdder that will be applied to screens with titles that match the given pattern. + * + * @param titlePattern The pattern to match the screen title against. + */ + protected SlotTextAdder(@NotNull Pattern titlePattern) { + super(titlePattern); + } + + /** + * Creates a SlotTextAdder that will be applied to all screens. + */ + protected SlotTextAdder() { + super(); + } + + /** + * This method will be called for each rendered slot. Consider using a switch statement on {@link Slot#id} if you wish to add different text to different slots. + * + * @return A list of positioned text to be rendered. Return {@link List#of()} if no text should be rendered. + * @implNote By minecraft's design, scaled text inexplicably moves around. + * So, limit your text to 3 characters (or roughly less than 20 width) if you want it to not look horrible. + */ + public abstract @NotNull List<SlotText> getText(Slot slot); + + /** + * Override this method to add conditions to enable or disable this adder. + * @return Whether this adder is enabled. + * @implNote The slot text adders only work while in skyblock, so no need to check for that again. + */ + public boolean isEnabled() { + return SkyblockerConfigManager.get().general.itemInfoDisplay.slotText; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java new file mode 100644 index 00000000..d3941d77 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java @@ -0,0 +1,75 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +import de.hysky.skyblocker.skyblock.item.slottext.adders.*; +import de.hysky.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.screen.slot.Slot; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class SlotTextManager { + private static final SlotTextAdder[] adders = new SlotTextAdder[]{ + new EssenceShopAdder(), + new EnchantmentLevelAdder(), + new MinionLevelAdder(), + new PetLevelAdder(), + new SkyblockLevelAdder(), + new SkillLevelAdder(), + new CatacombsLevelAdder.Dungeoneering(), + new CatacombsLevelAdder.DungeonClasses(), + new CatacombsLevelAdder.ReadyUp(), + new RancherBootsSpeedAdder(), + new AttributeShardAdder(), + new PrehistoricEggAdder(), + new PotionLevelAdder(), + new CollectionAdder(), + new CommunityShopAdder(), + new YourEssenceAdder(), + new PowerStonesGuideAdder(), + new StatsTuningAdder() + }; + private static final ArrayList<SlotTextAdder> currentScreenAdders = new ArrayList<>(); + + private SlotTextManager() { + } + + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { + if (screen instanceof HandledScreen<?> && Utils.isOnSkyblock()) { + onScreenChange(screen); + ScreenEvents.remove(screen).register(ignored -> currentScreenAdders.clear()); + } + }); + } + + private static void onScreenChange(Screen screen) { + final String title = screen.getTitle().getString(); + for (SlotTextAdder adder : adders) { + if (!adder.isEnabled()) continue; + if (adder.titlePattern == null || adder.titlePattern.matcher(title).find()) { + currentScreenAdders.add(adder); + } + } + } + + /** + * The returned text is rendered on top of the slot. The text will be scaled if it doesn't fit in the slot, + * but 3 characters should be seen as the maximum to keep it readable and in place as it tends to move around when scaled. + * + * @implNote Only the first adder that returns a non-null text will be used. + * The order of the adders remains the same as they were added to the {@link SlotTextManager#adders} array. + */ + @NotNull + public static List<SlotText> getText(Slot slot) { + if (currentScreenAdders.isEmpty()) return List.of(); + for (SlotTextAdder adder : currentScreenAdders) { + List<SlotText> text = adder.getText(slot); + if (!text.isEmpty()) return text; + } + return List.of(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java new file mode 100644 index 00000000..052b228d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java @@ -0,0 +1,8 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +public enum TextPosition { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_RIGHT +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java index ed650e26..81f543f8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java @@ -1,9 +1,22 @@ -package de.hysky.skyblocker.skyblock.item; +package de.hysky.skyblocker.skyblock.item.slottext.adders; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; -public class AttributeShards { - private static final Object2ObjectOpenHashMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>(); +import java.util.List; + +public class AttributeShardAdder extends SlotTextAdder { + private static final Object2ObjectMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>(); static { //Weapons @@ -50,10 +63,35 @@ public class AttributeShards { ID_2_SHORT_NAME.put("fishing_speed", "FS"); ID_2_SHORT_NAME.put("hunter", "H"); ID_2_SHORT_NAME.put("trophy_hunter", "TH"); + } + + public AttributeShardAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + NbtCompound customData = ItemUtils.getCustomData(stack); + + if (!ItemUtils.getItemId(stack).equals("ATTRIBUTE_SHARD")) return List.of(); + + NbtCompound attributesTag = customData.getCompound("attributes"); + String[] attributes = attributesTag.getKeys().toArray(String[]::new); + + if (attributes.length != 1) return List.of(); + String attributeId = attributes[0]; + int attributeLevel = attributesTag.getInt(attributeId); + String attributeInitials = ID_2_SHORT_NAME.getOrDefault(attributeId, ""); + return List.of( + SlotText.bottomRight(Text.literal(String.valueOf(attributeLevel)).withColor(0xFFDDC1)), + SlotText.topLeft(Text.literal(attributeInitials).withColor(0xCFF8F8)) + ); } - public static String getShortName(String id) { - return ID_2_SHORT_NAME.getOrDefault(id, ""); + @Override + public boolean isEnabled() { + return SkyblockerConfigManager.get().general.itemInfoDisplay.attributeShardInfo; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java new file mode 100644 index 00000000..6c99ebf9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java @@ -0,0 +1,105 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +//This class is split into 3 inner classes as there are multiple screens for showing catacombs levels, each with different slot ids or different style of showing the level. +//It's still kept in 1 main class for organization purposes. +public class CatacombsLevelAdder { + private CatacombsLevelAdder() { + } + + public static class Dungeoneering extends SlotTextAdder { + private static final Pattern LEVEL_PATTERN = Pattern.compile(".*?(?:(?<arabic>\\d+)|(?<roman>\\S+))? ?✯?"); + public Dungeoneering() { + super("^Dungeoneering"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 12, 29, 30, 31, 32, 33 -> { + Matcher matcher = LEVEL_PATTERN.matcher(slot.getStack().getName().getString()); + if (!matcher.matches()) return List.of(); + String arabic = matcher.group("arabic"); + String roman = matcher.group("roman"); + if (arabic == null && roman == null) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.RED))); + String level; + if (arabic != null) { + if (!NumberUtils.isDigits(arabic)) return List.of(); //Sanity check + level = arabic; + } else { // roman != null + if (!RomanNumerals.isValidRomanNumeral(roman)) return List.of(); //Sanity check + level = String.valueOf(RomanNumerals.romanToDecimal(roman)); + } + + return List.of(SlotText.bottomLeft(Text.literal(level).withColor(0xFFDDC1))); + } + default -> { + return List.of(); + } + } + } + } + + public static class DungeonClasses extends SlotTextAdder { + + public DungeonClasses() { + super("^Dungeon Classes"); //Applies to both screens as they are same in both the placement and the style of the level text. + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 11, 12, 13, 14, 15 -> { + String level = getBracketedLevelFromName(slot.getStack()); + if (!NumberUtils.isDigits(level)) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(level).withColor(0xFFDDC1))); + } + default -> { + return List.of(); + } + } + } + } + + public static class ReadyUp extends SlotTextAdder { + + public ReadyUp() { + super("^Ready Up"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 29, 30, 31, 32, 33 -> { + String level = getBracketedLevelFromName(slot.getStack()); + if (!NumberUtils.isDigits(level)) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(level).withColor(0xFFDDC1))); + } + default -> { + return List.of(); + } + } + } + } + + public static String getBracketedLevelFromName(ItemStack itemStack) { + String name = itemStack.getName().getString(); + if (!name.startsWith("[Lvl ")) return null; + int index = name.indexOf(']'); + if (index == -1) return null; + return name.substring(5, index); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java new file mode 100644 index 00000000..207190c2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java @@ -0,0 +1,32 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CollectionAdder extends SlotTextAdder { + private static final Pattern COLLECTION = Pattern.compile("^[\\w ]+ (?<level>[IVXLCDM]+)$"); + + public CollectionAdder() { + super("^\\w+ Collections"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + Matcher matcher = COLLECTION.matcher(stack.getName().getString()); + if (matcher.matches()) { + int level = RomanNumerals.romanToDecimal(matcher.group("level")); + return List.of(SlotText.bottomRight(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } + return List.of(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java new file mode 100644 index 00000000..c7ea17dc --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java @@ -0,0 +1,60 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class CommunityShopAdder extends SlotTextAdder { + private static final byte CATEGORIES_START = 10; + private static final byte CATEGORIES_END = 14; //Inclusive + + public CommunityShopAdder() { + super("^Community Shop"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + for (byte i = CATEGORIES_START; i <= CATEGORIES_END; i++) { + if (slot.inventory.getStack(i).isOf(Items.LIME_STAINED_GLASS_PANE)) { //Only the selected category has a lime stained glass pane, the others have a gray one. + return switch (i) { //This is a switch to allow adding more categories easily in the future, if we ever add more. + case 11 -> getTextForUpgradesScreen(slot); + default -> List.of(); + }; + } + } + return List.of(); + } + + private static List<SlotText> getTextForUpgradesScreen(Slot slot) { + final ItemStack stack = slot.getStack(); + switch (slot.id) { + case 30, 31, 32, 33, 34, 38, 39, 40, 41, 42, 43, 44 -> { + String name = stack.getName().getString(); + int lastIndex = name.lastIndexOf(' '); + String roman = name.substring(lastIndex + 1); // + 1 as we don't want the space + if (!RomanNumerals.isValidRomanNumeral(roman)) return List.of(); + + List<Text> lore = ItemUtils.getLore(stack); + if (lore.isEmpty()) return List.of(); + String lastLine = lore.getLast().getString(); + return List.of(SlotText.bottomLeft(switch (lastLine) { + case "Maxed out!" -> Text.literal("Max").withColor(0xfab387); + case "Currently upgrading!" -> Text.literal("⏰").withColor(0xf9e2af).formatted(Formatting.BOLD); + case "Click to claim!" -> Text.literal("✅").withColor(0xa6e3a1).formatted(Formatting.BOLD); + default -> Text.literal(String.valueOf(RomanNumerals.romanToDecimal(roman))).withColor(0xcba6f7); + })); + + } + } + return List.of(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java new file mode 100644 index 00000000..5530035d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class EnchantmentLevelAdder extends SlotTextAdder { + public EnchantmentLevelAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack itemStack = slot.getStack(); + if (!itemStack.isOf(Items.ENCHANTED_BOOK)) return List.of(); + String name = itemStack.getName().getString(); + if (name.equals("Enchanted Book")) { + NbtCompound nbt = ItemUtils.getCustomData(itemStack); + if (nbt.isEmpty() || !nbt.contains("enchantments", NbtElement.COMPOUND_TYPE)) return List.of(); + NbtCompound enchantments = nbt.getCompound("enchantments"); + if (enchantments.getSize() != 1) return List.of(); //Only makes sense to display the level when there's one enchant. + int level = enchantments.getInt(enchantments.getKeys().iterator().next()); + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } else { //In bazaar, the books have the enchantment level in the name + int level = getEnchantLevelFromString(name); + if (level == 0) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } + } + + private static int getEnchantLevelFromString(String str) { + String romanNumeral = str.substring(str.lastIndexOf(' ') + 1); //+1 because we don't need the space itself + return RomanNumerals.romanToDecimal(romanNumeral); //Temporary line. The method will be moved out later. + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EssenceShopAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EssenceShopAdder.java new file mode 100644 index 00000000..65574a5a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EssenceShopAdder.java @@ -0,0 +1,51 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EssenceShopAdder extends SlotTextAdder { + private static final Pattern ESSENCELEVEL = Pattern.compile("^[\\w ]+ (?<level>[IVXLCDM]+)$"); + private static final Pattern UNLOCKED = Pattern.compile("UNLOCKED"); + private static final Pattern ESSENCE = Pattern.compile("Your \\w+ Essence: (?<essence>[\\d,]+)"); + + public EssenceShopAdder() { + super("Essence Shop"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + + Matcher essenceLevelMatcher = ESSENCELEVEL.matcher(stack.getName().getString()); + Matcher essenceAmountMatcher = ItemUtils.getLoreLineIfMatch(stack, ESSENCE); + + if (essenceLevelMatcher.matches()) { + int level = RomanNumerals.romanToDecimal(essenceLevelMatcher.group("level")); + Matcher unlockedMatcher = ItemUtils.getLoreLineIfMatch(stack, UNLOCKED); + if (unlockedMatcher == null) { + level -= 1; + } + return List.of(SlotText.bottomRight(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } + if (essenceAmountMatcher == null) return List.of(); + String essenceAmount = essenceAmountMatcher.group("essence").replace(",", ""); + if (!essenceAmount.matches("-?\\d+")) return List.of(); + NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT); + NUMBER_FORMATTER_S.setMinimumFractionDigits(1); + int amount = Integer.parseInt(essenceAmount); + + return List.of(SlotText.bottomRight(Text.literal(NUMBER_FORMATTER_S.format(amount)).withColor(0xFFDDC1))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java new file mode 100644 index 00000000..fc46f153 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MinionLevelAdder extends SlotTextAdder { + private static final Pattern MINION_PATTERN = Pattern.compile(".* Minion ([IVXLCDM]+)"); + public MinionLevelAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + ItemStack itemStack = slot.getStack(); + if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of(); + Matcher matcher = MINION_PATTERN.matcher(itemStack.getName().getString()); + if (!matcher.matches()) return List.of(); + String romanNumeral = matcher.group(1); + if (!RomanNumerals.isValidRomanNumeral(romanNumeral)) return List.of(); + int level = RomanNumerals.romanToDecimal(romanNumeral); + return List.of(SlotText.topRight(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java new file mode 100644 index 00000000..88d48fbf --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java @@ -0,0 +1,29 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class PetLevelAdder extends SlotTextAdder { + public PetLevelAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + ItemStack itemStack = slot.getStack(); + if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of(); + String level = CatacombsLevelAdder.getBracketedLevelFromName(itemStack); + if (!NumberUtils.isDigits(level)) return List.of(); + if ("100".equals(level) || "200".equals(level)) return List.of(); + return List.of(SlotText.topLeft(Text.literal(level).withColor(0xFFDDC1))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java new file mode 100644 index 00000000..1c3ef4bc --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java @@ -0,0 +1,32 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class PotionLevelAdder extends SlotTextAdder { + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + NbtCompound customData = ItemUtils.getCustomData(stack); + String title = stack.getName().getString(); + if (customData.contains("potion_level", NbtElement.INT_TYPE) && !title.contains("Healer Class") && !title.contains("Class Passives")) { + if (title.contains("Healer Level ")){ + String level = title.replaceAll("[^0-9]", ""); + return List.of(SlotText.bottomRight(Text.literal(level).withColor(0xFFFFFF))); + } else { + int level = customData.getInt("potion_level"); + return List.of(SlotText.bottomRight(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } + } + return List.of(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java new file mode 100644 index 00000000..0bb37165 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java @@ -0,0 +1,38 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PowerStonesGuideAdder extends SlotTextAdder { + private static final Pattern LEARNED = Pattern.compile("Learned: (Yes|Not Yet) (?<symbol>[✖✔])"); + + public PowerStonesGuideAdder() { + super("^Power Stones Guide"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + + Matcher match = ItemUtils.getLoreLineIfMatch(stack, LEARNED); + if (match == null) return List.of(); + String symbol = match.group("symbol"); + Text text; + if (symbol.equals("✖")) { + text = Text.literal("✖").withColor(0xFF7276); + } else { + text = Text.literal("✔").withColor(0x90ee90); + } + + return List.of(SlotText.bottomRight(text)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java new file mode 100644 index 00000000..ce29297e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class PrehistoricEggAdder extends SlotTextAdder { + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + if (!stack.isOf(Items.PLAYER_HEAD) || !StringUtils.equals(stack.getSkyblockId(), "PREHISTORIC_EGG")) return List.of(); + NbtCompound nbt = ItemUtils.getCustomData(stack); + if (!nbt.contains("blocks_walked", NbtElement.INT_TYPE)) return List.of(); + int walked = nbt.getInt("blocks_walked"); + + String walkedstr; + if (walked < 1000) walkedstr = String.valueOf(walked); + else if (walked < 10000) walkedstr = String.format("%.1fk", walked/1000.0f); + else walkedstr = walked / 1000 + "k"; + + return List.of(SlotText.bottomLeft(Text.literal(walkedstr).withColor(0xFFDDC1))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java new file mode 100644 index 00000000..7de5a5be --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java @@ -0,0 +1,36 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RancherBootsSpeedAdder extends SlotTextAdder { + private static final Pattern SPEED_PATTERN = Pattern.compile("Current Speed Cap: (\\d+) ?(\\d+)?"); + + public RancherBootsSpeedAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack itemStack = slot.getStack(); + // V null-safe equals. + if (!itemStack.isOf(Items.LEATHER_BOOTS) && !StringUtils.equals(itemStack.getSkyblockId(), "RANCHERS_BOOTS")) return List.of(); + Matcher matcher = ItemUtils.getLoreLineIfMatch(slot.getStack(), SPEED_PATTERN); + if (matcher == null) return List.of(); + String speed = matcher.group(2); + if (speed == null) speed = matcher.group(1); //2nd group only matches when the speed cap is set to a number beyond the player's actual speed cap. + return List.of(SlotText.bottomLeft(Text.literal(speed).withColor(0xFFDDC1))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java new file mode 100644 index 00000000..07b8dd9b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SkillLevelAdder extends SlotTextAdder { + public SkillLevelAdder() { + super("^Your Skills"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 19, 20, 21, 22, 23, 24, 25, 29, 30, 31, 32 -> { //These are the slots that contain the skill items. Note that they aren't continuous, as there are 2 rows. + String name = slot.getStack().getName().getString(); + int lastIndex = name.lastIndexOf(' '); + if (lastIndex == -1) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.LIGHT_PURPLE))); //Skills without any levels don't display any roman numerals. Probably because 0 doesn't exist. + String romanNumeral = name.substring(lastIndex + 1); //+1 because we don't need the space itself + //The "romanNumeral" might be a latin numeral, too. There's a skyblock setting for this, so we have to do it this way V + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(RomanNumerals.isValidRomanNumeral(romanNumeral) ? RomanNumerals.romanToDecimal(romanNumeral) : Integer.parseInt(romanNumeral))).withColor(0xFFDDC1))); + } + default -> { + return List.of(); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java new file mode 100644 index 00000000..0fc07922 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java @@ -0,0 +1,29 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SkyblockLevelAdder extends SlotTextAdder { + public SkyblockLevelAdder() { + super("^SkyBlock Menu"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + if (slot.getIndex() != 22) return List.of(); + List<Text> lore = ItemUtils.getLore(slot.getStack()); + if (lore.isEmpty()) return List.of(); + List<Text> siblings = lore.getFirst().getSiblings(); + if (siblings.size() < 3) return List.of(); + String levelText = siblings.get(2).getString(); //The 3rd child is the level text itself + if (!NumberUtils.isDigits(levelText)) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(levelText).withColor(0xFFDDC1))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java new file mode 100644 index 00000000..3079d8f2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java @@ -0,0 +1,42 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StatsTuningAdder extends SlotTextAdder { + private static final Pattern STATHAS = Pattern.compile("Stat has: (?<points>\\d+) (points|point)"); + private static final Pattern UNASSIGNEDPOINTS = Pattern.compile("Unassigned Points: (?<points>\\d+)!!!"); + + public StatsTuningAdder() { + super("^Stats Tuning"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + + Matcher statMatcher = ItemUtils.getLoreLineIfMatch(stack, STATHAS); + Matcher unassignedMatcher = ItemUtils.getLoreLineIfMatch(stack, UNASSIGNEDPOINTS); + + if (stack.getName().getString().equals("Stats Tuning")) { + if (unassignedMatcher == null) return List.of(); + String unassignedPoints = unassignedMatcher.group("points"); + return List.of(SlotText.bottomRight(Text.literal(unassignedPoints).withColor(0xFFDDC1))); + } + + if (statMatcher == null) return List.of(); + String assignedPoints = statMatcher.group("points"); + if (assignedPoints.equals("0")) return List.of(); + return List.of(SlotText.bottomRight(Text.literal(assignedPoints).withColor(0xFFDDC1))); + + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/YourEssenceAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/YourEssenceAdder.java new file mode 100644 index 00000000..ec1bd561 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/YourEssenceAdder.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class YourEssenceAdder extends SlotTextAdder { + private static final Pattern ESSENCE = Pattern.compile("You currently own (?<essence>[\\d,]+)"); + + public YourEssenceAdder() { + super("^Your Essence"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + String name = stack.getName().getString(); + if (name.contains("Essence")) { + List<Text> lore = ItemUtils.getLore(stack); + if (lore.isEmpty()) return List.of(); + String essenceAmountText = lore.getFirst().getString(); + + Matcher essenceAmountMatcher = ESSENCE.matcher(essenceAmountText); + if (essenceAmountMatcher.find()) { + String essenceAmount = essenceAmountMatcher.group("essence").replace(",", ""); + if (!essenceAmount.matches("-?\\d+")) return List.of(); + NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT); + NUMBER_FORMATTER_S.setMinimumFractionDigits(1); + int amount = Integer.parseInt(essenceAmount); + + return List.of(SlotText.bottomRight(Text.literal(NUMBER_FORMATTER_S.format(amount)).withColor(0xFFDDC1))); + } + } + return List.of(); + } +}
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java index 8798a139..992206ad 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -108,7 +108,7 @@ public class AccessoriesHelper { .put(page, new ObjectOpenHashSet<>(accessoryIds)); } - static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) { + public static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) { if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return Pair.of(AccessoryReport.INELIGIBLE, null); Accessory accessory = ACCESSORY_DATA.get(accessoryId); @@ -208,7 +208,7 @@ public class AccessoriesHelper { } } - enum AccessoryReport { + public enum AccessoryReport { HAS_HIGHEST_TIER, //You've collected the highest tier - Collected IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade -- Shows you what tier this accessory is in its family HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable -- Shows you the highest tier accessory you've collected in that family diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java index 50772789..cad1eaab 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java @@ -15,13 +15,17 @@ import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.inventory.Inventory; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringNbtReader; import net.minecraft.nbt.visitor.StringNbtWriter; +import net.minecraft.registry.BuiltinRegistries; +import net.minecraft.registry.RegistryOps; import net.minecraft.util.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,8 +42,8 @@ import java.util.stream.Collectors; public class BackpackPreview { private static final Logger LOGGER = LoggerFactory.getLogger(BackpackPreview.class); - private static final Identifier ITEM_PROTECTION = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); - private static final Identifier TEXTURE = new Identifier("textures/gui/container/generic_54.png"); + private static final Identifier ITEM_PROTECTION = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); + private static final Identifier TEXTURE = Identifier.ofVanilla("textures/gui/container/generic_54.png"); private static final Pattern ECHEST_PATTERN = Pattern.compile("Ender Chest.*\\((\\d+)/\\d+\\)"); private static final Pattern BACKPACK_PATTERN = Pattern.compile("Backpack.*\\(Slot #(\\d+)\\)"); private static final int STORAGE_SIZE = 27; @@ -88,7 +92,7 @@ public class BackpackPreview { Path storageFile = saveDir.resolve(index + ".nbt"); if (Files.isRegularFile(storageFile)) { try (BufferedReader reader = Files.newBufferedReader(storageFile)) { - storages[index] = Storage.CODEC.parse(NbtOps.INSTANCE, StringNbtReader.parse(reader.lines().collect(Collectors.joining()))).getOrThrow(); + storages[index] = Storage.CODEC.parse(getOps(), StringNbtReader.parse(reader.lines().collect(Collectors.joining()))).getOrThrow(); } catch (Exception e) { LOGGER.error("Failed to load backpack preview file: {}", storageFile.getFileName().toString(), e); } @@ -96,6 +100,11 @@ public class BackpackPreview { } } + private static RegistryOps<NbtElement> getOps() { + MinecraftClient client = MinecraftClient.getInstance(); + return client != null && client.getNetworkHandler() != null && client.getNetworkHandler().getRegistryManager() != null ? client.getNetworkHandler().getRegistryManager().getOps(NbtOps.INSTANCE) : BuiltinRegistries.createWrapperLookup().getOps(NbtOps.INSTANCE); + } + private static void saveStorages() { for (int index = 0; index < STORAGE_SIZE; ++index) { if (storages[index] != null && storages[index].dirty) { @@ -107,7 +116,7 @@ public class BackpackPreview { private static void saveStorage(int index) { Path storageFile = saveDir.resolve(index + ".nbt"); try (BufferedWriter writer = Files.newBufferedWriter(storageFile)) { - writer.write(new StringNbtWriter().apply(Storage.CODEC.encodeStart(NbtOps.INSTANCE, storages[index]).getOrThrow())); + writer.write(new StringNbtWriter().apply(Storage.CODEC.encodeStart(getOps(), storages[index]).getOrThrow())); storages[index].markClean(); } catch (Exception e) { LOGGER.error("Failed to save backpack preview file: {}", storageFile.getFileName().toString(), e); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java index c5279c61..b93ca77a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java @@ -7,7 +7,6 @@ import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntObjectPair; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.HoveredTooltipPositioner; import net.minecraft.client.gui.tooltip.TooltipComponent; import net.minecraft.item.ItemStack; @@ -34,8 +33,7 @@ public class CompactorDeletorPreview { public static final Pattern NAME = Pattern.compile("PERSONAL_(?<type>COMPACTOR|DELETOR)_(?<size>\\d+)"); private static final MinecraftClient client = MinecraftClient.getInstance(); - public static boolean drawPreview(DrawContext context, ItemStack stack, String type, String size, int x, int y) { - List<Text> tooltips = Screen.getTooltipFromItem(client, stack); + public static boolean drawPreview(DrawContext context, ItemStack stack, List<Text> tooltips, String type, String size, int x, int y) { int targetIndex = getTargetIndex(tooltips); if (targetIndex == -1) return false; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java index 8f2c0901..b7ac2856 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java @@ -10,7 +10,7 @@ import net.minecraft.item.Items; import net.minecraft.util.Identifier; public class CompactorPreviewTooltipComponent implements TooltipComponent { - private static final Identifier TEXTURE = new Identifier("textures/gui/container/generic_54.png"); + private static final Identifier TEXTURE = Identifier.ofVanilla("textures/gui/container/generic_54.png"); private static final ItemStack BLACK_STAINED_GLASS_PANE = new ItemStack(Items.BLACK_STAINED_GLASS_PANE); private final Iterable<IntObjectPair<ItemStack>> items; private final IntIntPair dimensions; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java deleted file mode 100644 index 46babc8b..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java +++ /dev/null @@ -1,96 +0,0 @@ -package de.hysky.skyblocker.skyblock.item.tooltip; - -import de.hysky.skyblocker.utils.Constants; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import net.minecraft.util.StringIdentifiable; - -public class ExoticTooltip { - public static String getExpectedHex(String id) { - String color = TooltipInfoType.COLOR.getData().get(id).getAsString(); - if (color != null) { - String[] RGBValues = color.split(","); - return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2])); - } else { - ItemTooltip.LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id); - return null; - } - } - - public static boolean isException(String id, String hex) { - if (id.startsWith("LEATHER") || id.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(id)) { - return true; - } - if (id.startsWith("RANCHER")) { - return Constants.RANCHERS.contains(hex); - } - if (id.contains("ADAPTIVE_CHESTPLATE")) { - return Constants.ADAPTIVE_CHEST.contains(hex); - } else if (id.contains("ADAPTIVE")) { - return Constants.ADAPTIVE.contains(hex); - } - if (id.startsWith("REAPER")) { - return Constants.REAPER.contains(hex); - } - if (id.startsWith("FAIRY")) { - return Constants.FAIRY_HEXES.contains(hex); - } - if (id.startsWith("CRYSTAL")) { - return Constants.CRYSTAL_HEXES.contains(hex); - } - if (id.contains("SPOOK")) { - return Constants.SPOOK.contains(hex); - } - return false; - } - - public static DyeType checkDyeType(String hex) { - if (Constants.CRYSTAL_HEXES.contains(hex)) { - return DyeType.CRYSTAL; - } - if (Constants.FAIRY_HEXES.contains(hex)) { - return DyeType.FAIRY; - } - if (Constants.OG_FAIRY_HEXES.contains(hex)) { - return DyeType.OG_FAIRY; - } - if (Constants.SPOOK.contains(hex)) { - return DyeType.SPOOK; - } - if (Constants.GLITCHED.contains(hex)) { - return DyeType.GLITCHED; - } - return DyeType.EXOTIC; - } - - public static boolean intendedDyed(NbtCompound customData) { - return customData.contains("dye_item"); - } - - public enum DyeType implements StringIdentifiable { - CRYSTAL("crystal", Formatting.AQUA), - FAIRY("fairy", Formatting.LIGHT_PURPLE), - OG_FAIRY("og_fairy", Formatting.DARK_PURPLE), - SPOOK("spook", Formatting.RED), - GLITCHED("glitched", Formatting.BLUE), - EXOTIC("exotic", Formatting.GOLD); - private final String name; - private final Formatting formatting; - - DyeType(String name, Formatting formatting) { - this.name = name; - this.formatting = formatting; - } - - @Override - public String asString() { - return name; - } - - public MutableText getTranslatedText() { - return Text.translatable("skyblocker.exotic." + name).formatted(formatting); - } - } -} 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 8c083e25..49d170b9 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 @@ -1,269 +1,46 @@ package de.hysky.skyblocker.skyblock.item.tooltip; -import com.google.gson.JsonObject; -import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; -import de.hysky.skyblocker.skyblock.item.MuseumItemCache; -import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper.AccessoryReport; import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; -import it.unimi.dsi.fastutil.Pair; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.item.TooltipType; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.DyedColorComponent; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtElement; -import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; - import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import java.util.concurrent.CompletableFuture; public class ItemTooltip { protected static final Logger LOGGER = LoggerFactory.getLogger(ItemTooltip.class.getName()); private static final MinecraftClient client = MinecraftClient.getInstance(); - protected static final GeneralConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip; + public static final GeneralConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip; private static volatile boolean sentNullWarning = false; - public static void getTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType tooltipType, List<Text> lines) { - if (!Utils.isOnSkyblock() || client.player == null) return; - - smoothenLines(lines); - - String name = getInternalNameFromNBT(stack, false); - String internalID = getInternalNameFromNBT(stack, true); - String neuName = name; - if (name == null || internalID == null) return; - - if (name.startsWith("ISSHINY_")) { - name = "SHINY_" + internalID; - neuName = internalID; - } - - if (lines.isEmpty()) { - return; - } - - int count = stack.getCount(); - boolean bazaarOpened = lines.stream().anyMatch(each -> each.getString().contains("Buy price:") || each.getString().contains("Sell price:")); - - if (TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) { - lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) - .formatted(Formatting.YELLOW) - .append(getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), count))); - } - - boolean bazaarExist = false; - - if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened) { - JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name); - lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) - .formatted(Formatting.GOLD) - .append(getItem.get("buyPrice").isJsonNull() - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count))); - lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) - .formatted(Formatting.GOLD) - .append(getItem.get("sellPrice").isJsonNull() - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count))); - bazaarExist = true; - } - - // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api - boolean lbinExist = false; - if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened && !bazaarExist) { - lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) - .formatted(Formatting.GOLD) - .append(getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), count))); - lbinExist = true; - } - - if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) { - if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { - nullWarning(); - } else { - /* - We are skipping check average prices for potions, runes - and enchanted books because there is no data for their in API. - */ - neuName = getNeuName(internalID, neuName); - - if (!neuName.isEmpty() && lbinExist) { - GeneralConfig.Average type = config.avg; - - // "No data" line because of API not keeping old data, it causes NullPointerException - if (type == GeneralConfig.Average.ONE_DAY || type == GeneralConfig.Average.BOTH) { - lines.add( - Text.literal(String.format("%-19s", "1 Day Avg. Price:")) - .formatted(Formatting.GOLD) - .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count) - ) - ); - } - if (type == GeneralConfig.Average.THREE_DAY || type == GeneralConfig.Average.BOTH) { - lines.add( - Text.literal(String.format("%-19s", "3 Day Avg. Price:")) - .formatted(Formatting.GOLD) - .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count) - ) - ); - } - } - } - } - - final Map<Integer, String> itemTierFloors = Map.of( - 1, "F1", - 2, "F2", - 3, "F3", - 4, "F4/M1", - 5, "F5/M2", - 6, "F6/M3", - 7, "F7/M4", - 8, "M5", - 9, "M6", - 10, "M7" - ); - - if (SkyblockerConfigManager.get().general.itemTooltip.dungeonQuality) { - NbtCompound customData = ItemUtils.getCustomData(stack); - if (customData != null && customData.contains("baseStatBoostPercentage")) { - int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage"); - boolean maxQuality = baseStatBoostPercentage == 50; - if (maxQuality) { - lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); - } else { - lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); - } - if (customData.contains("item_tier")) { // sometimes it just isn't here? - int itemTier = customData.getInt("item_tier"); - if (maxQuality) { - lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD)); - } else { - lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.BLUE)); - } - } - } - } - - if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) { - lines.add(Text.literal(String.format("%-20s", "Motes Price:")) - .formatted(Formatting.LIGHT_PURPLE) - .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), count))); - } - - if (TooltipInfoType.OBTAINED.isTooltipEnabled()) { - String timestamp = ItemUtils.getTimestamp(stack); - - if (!timestamp.isEmpty()) { - lines.add(Text.literal(String.format("%-21s", "Obtained: ")) - .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(timestamp).formatted(Formatting.RED))); - } - } - - if (TooltipInfoType.MUSEUM.isTooltipEnabledAndHasOrNullWarning(internalID) && !bazaarOpened) { - String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString(); - String format = switch (itemCategory) { - case "Weapons" -> "%-18s"; - case "Armor" -> "%-19s"; - default -> "%-20s"; - }; - - //Special case the special category so that it doesn't always display not donated - if (itemCategory.equals("Special")) { - lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) - .formatted(Formatting.LIGHT_PURPLE)); - } else { - NbtCompound customData = ItemUtils.getCustomData(stack); - boolean isInMuseum = (customData.contains("donated_museum") && customData.getBoolean("donated_museum")) || MuseumItemCache.hasItemInMuseum(internalID); - - Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED; - - lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):")) - .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD)) - .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting))); - } - } - - if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.contains(DataComponentTypes.DYED_COLOR)) { - String uuid = ItemUtils.getItemUuid(stack); - boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(uuid) || SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(uuid); - //DyedColorComponent#getColor returns ARGB so we mask out the alpha bits - int dyeColor = DyedColorComponent.getColor(stack, 0); - - // dyeColor will have alpha = 255 if it's dyed, and alpha = 0 if it's not dyed, - if (!hasCustomDye && dyeColor != 0) { - dyeColor = dyeColor & 0x00FFFFFF; - String colorHex = String.format("%06X", dyeColor); - String expectedHex = ExoticTooltip.getExpectedHex(internalID); - - boolean correctLine = false; - for (Text text : lines) { - String existingTooltip = text.getString() + " "; - if (existingTooltip.startsWith("Color: ")) { - correctLine = true; - - addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, existingTooltip); - break; - } - } - - if (!correctLine) { - addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, ""); - } - } - } - - if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) { - Pair<AccessoryReport, String> report = AccessoriesHelper.calculateReport4Accessory(internalID); - - if (report.left() != AccessoryReport.INELIGIBLE) { - MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); - - Text stateText = switch (report.left()) { - case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); - case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff)); - - //Should never be the case - default -> Text.literal("? Unknown").formatted(Formatting.GRAY); - }; - - lines.add(title.append(stateText)); - } - } - } - + /** + * Gets the NEU id from an id and an api id. + * @param id the id of the skyblock item, gotten from {@link de.hysky.skyblocker.utils.ItemUtils#getItemId(net.minecraft.item.ItemStack) ItemUtils#getItemId(ItemStack)} or {@link net.minecraft.item.ItemStack#getSkyblockId() ItemStack#getSkyblockId()} + * @param apiId the api id of the skyblock item, matching the id of the item on the Skyblocker api, gotten from {@link net.minecraft.item.ItemStack#getSkyblockApiId() ItemStack#getSkyblockApiId()} + * @return the NEU id of the skyblock item, matching the id of the item gotten from {@link io.github.moulberry.repo.data.NEUItem#getSkyblockItemId() NEUItem#getSkyblockItemId()} or {@link net.minecraft.item.ItemStack#getNeuName() ItemStack#getNeuName()}, + * or an empty string if either id or apiId is null + */ @NotNull - public static String getNeuName(String internalID, String neuName) { - switch (internalID) { + public static String getNeuName(String id, String apiId) { + if (id == null || apiId == null) return ""; + switch (id) { case "PET" -> { - neuName = neuName.replaceAll("LVL_\\d*_", ""); - String[] parts = neuName.split("_"); + apiId = apiId.replaceAll("LVL_\\d*_", ""); + String[] parts = apiId.split("_"); String type = parts[0]; - neuName = neuName.replaceAll(type + "_", ""); - neuName = neuName + "-" + type; - neuName = neuName.replace("UNCOMMON", "1") + apiId = apiId.replaceAll(type + "_", ""); + apiId = apiId + "-" + type; + apiId = apiId.replace("UNCOMMON", "1") .replace("COMMON", "0") .replace("RARE", "2") .replace("EPIC", "3") @@ -271,20 +48,19 @@ public class ItemTooltip { .replace("MYTHIC", "5") .replace("-", ";"); } - case "RUNE" -> neuName = neuName.replaceAll("_(?!.*_)", ";"); - case "POTION" -> neuName = ""; + case "RUNE" -> apiId = apiId.replaceAll("_(?!.*_)", ";"); + case "POTION" -> apiId = ""; case "ATTRIBUTE_SHARD" -> - neuName = internalID + "+" + neuName.replace("SHARD-", "").replaceAll("_(?!.*_)", ";"); - default -> neuName = neuName.replace(":", "-"); - } - return neuName; - } - - private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound customData, String colorHex, String expectedHex, String existingTooltip) { - if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !ExoticTooltip.isException(internalID, colorHex) && !ExoticTooltip.intendedDyed(customData)) { - final ExoticTooltip.DyeType type = ExoticTooltip.checkDyeType(colorHex); - lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")")); - } + apiId = id + "+" + apiId.replace("SHARD-", "").replaceAll("_(?!.*_)", ";"); + case "NEW_YEAR_CAKE" -> apiId = id + "+" + apiId.replace("NEW_YEAR_CAKE_", ""); + case "PARTY_HAT_CRAB_ANIMATED" -> apiId = "PARTY_HAT_CRAB_" + apiId.replace("PARTY_HAT_CRAB_ANIMATED_", "") + "_ANIMATED"; + case "CRIMSON_HELMET", "CRIMSON_CHESTPLATE", "CRIMSON_LEGGINGS", "CRIMSON_BOOTS", + "AURORA_HELMET", "AURORA_CHESTPLATE", "AURORA_LEGGINGS", "AURORA_BOOTS", + "TERROR_HELMET", "TERROR_CHESTPLATE", "TERROR_LEGGINGS", "TERROR_BOOTS" -> apiId = id; + case "MIDAS_SWORD", "MIDAS_STAFF" -> apiId = id; + default -> apiId = apiId.replace(":", "-"); + } + return apiId; } public static void nullWarning() { @@ -294,69 +70,7 @@ public class ItemTooltip { } } - // TODO What in the world is this? - public static String getInternalNameFromNBT(ItemStack stack, boolean internalIDOnly) { - NbtCompound customData = ItemUtils.getCustomData(stack); - - if (customData == null || !customData.contains(ItemUtils.ID, NbtElement.STRING_TYPE)) { - return null; - } - String internalName = customData.getString(ItemUtils.ID); - - if (internalIDOnly) { - return internalName; - } - - // Transformation to API format. - if (customData.contains("is_shiny")) { - return "ISSHINY_" + internalName; - } - - switch (internalName) { - case "ENCHANTED_BOOK" -> { - if (customData.contains("enchantments")) { - NbtCompound enchants = customData.getCompound("enchantments"); - Optional<String> firstEnchant = enchants.getKeys().stream().findFirst(); - String enchant = firstEnchant.orElse(""); - return "ENCHANTMENT_" + enchant.toUpperCase(Locale.ENGLISH) + "_" + enchants.getInt(enchant); - } - } - case "PET" -> { - if (customData.contains("petInfo")) { - JsonObject petInfo = SkyblockerMod.GSON.fromJson(customData.getString("petInfo"), JsonObject.class); - return "LVL_1_" + petInfo.get("tier").getAsString() + "_" + petInfo.get("type").getAsString(); - } - } - case "POTION" -> { - String enhanced = customData.contains("enhanced") ? "_ENHANCED" : ""; - String extended = customData.contains("extended") ? "_EXTENDED" : ""; - String splash = customData.contains("splash") ? "_SPLASH" : ""; - if (customData.contains("potion") && customData.contains("potion_level")) { - return (customData.getString("potion") + "_" + internalName + "_" + customData.getInt("potion_level") - + enhanced + extended + splash).toUpperCase(Locale.ENGLISH); - } - } - case "RUNE" -> { - if (customData.contains("runes")) { - NbtCompound runes = customData.getCompound("runes"); - Optional<String> firstRunes = runes.getKeys().stream().findFirst(); - String rune = firstRunes.orElse(""); - return rune.toUpperCase(Locale.ENGLISH) + "_RUNE_" + runes.getInt(rune); - } - } - case "ATTRIBUTE_SHARD" -> { - if (customData.contains("attributes")) { - NbtCompound shards = customData.getCompound("attributes"); - Optional<String> firstShards = shards.getKeys().stream().findFirst(); - String shard = firstShards.orElse(""); - return internalName + "-" + shard.toUpperCase(Locale.ENGLISH) + "_" + shards.getInt(shard); - } - } - } - return internalName; - } - - private static Text getCoinsMessage(double price, int count) { + public static Text getCoinsMessage(double price, int count) { // Format the price string once String priceString = String.format(Locale.ENGLISH, "%1$,.1f", price); @@ -367,47 +81,9 @@ public class ItemTooltip { // If count is greater than 1, include the "each" information String priceStringTotal = String.format(Locale.ENGLISH, "%1$,.1f", price * count); - MutableText message = Text.literal(priceStringTotal + " Coins ").formatted(Formatting.DARK_AQUA); - message.append(Text.literal("(" + priceString + " each)").formatted(Formatting.GRAY)); - - return message; - } - - private static Text getMotesMessage(int price, int count) { - float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1; - - // Calculate the total price - int totalPrice = price * count; - String totalPriceString = String.format(Locale.ENGLISH, "%1$,.1f", totalPrice * motesMultiplier); - - // If count is 1, return a simple message - if (count == 1) { - return Text.literal(totalPriceString.replace(".0", "") + " Motes").formatted(Formatting.DARK_AQUA); - } - - // If count is greater than 1, include the "each" information - String eachPriceString = String.format(Locale.ENGLISH, "%1$,.1f", price * motesMultiplier); - MutableText message = Text.literal(totalPriceString.replace(".0", "") + " Motes ").formatted(Formatting.DARK_AQUA); - message.append(Text.literal("(" + eachPriceString.replace(".0", "") + " each)").formatted(Formatting.GRAY)); - - 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++) { - 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); + return Text.literal(priceStringTotal + " Coins ").formatted(Formatting.DARK_AQUA) + .append(Text.literal("(" + priceString + " each)").formatted(Formatting.GRAY)); } // If these options is true beforehand, the client will get first data of these options while loading. diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java new file mode 100644 index 00000000..9bd63adc --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java @@ -0,0 +1,45 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import de.hysky.skyblocker.utils.render.gui.AbstractContainerMatcher; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * Extend this class and add it to {@link TooltipManager#adders} to add additional text to tooltips. + */ +public abstract class TooltipAdder extends AbstractContainerMatcher { + /** + * The priority of this adder. Lower priority means it will be applied first. + * @apiNote Consider taking this value on your class' constructor and setting it from {@link TooltipManager#adders} to make it easy to read and maintain. + */ + public final int priority; + + protected TooltipAdder(String titlePattern, int priority) { + super(titlePattern); + this.priority = priority; + } + + protected TooltipAdder(Pattern titlePattern, int priority) { + super(titlePattern); + this.priority = priority; + } + + /** + * Creates a TooltipAdder that will be applied to all screens. + */ + protected TooltipAdder(int priority) { + super(); + this.priority = priority; + } + + /** + * @implNote The first element of the lines list holds the item's display name, + * as it's a list of all lines that will be displayed in the tooltip. + */ + public abstract void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines); +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java index 88f09496..d82b2682 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -1,6 +1,9 @@ package de.hysky.skyblocker.skyblock.item.tooltip; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.stream.JsonReader; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; @@ -8,6 +11,7 @@ import de.hysky.skyblocker.config.configs.GeneralConfig; import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Utils; +import java.io.StringReader; import java.net.http.HttpHeaders; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -160,7 +164,12 @@ public enum TooltipInfoType implements Runnable { if (this.hash == hash) return; else this.hash = hash; } - data = SkyblockerMod.GSON.fromJson(Http.sendGetRequest(address), JsonObject.class); + String response = Http.sendGetRequest(address); + if (response.trim().startsWith("<!DOCTYPE") || response.trim().startsWith("<html")) { + ItemTooltip.LOGGER.warn("[Skyblocker] Received HTML content for " + this.name() + ". Expected JSON."); + return; + } + data = SkyblockerMod.GSON.fromJson(response, JsonObject.class); if (callback != null) callback.accept(data); } catch (Exception e) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java new file mode 100644 index 00000000..e3a2ef04 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java @@ -0,0 +1,87 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; +import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.*; +import de.hysky.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class TooltipManager { + private static final TooltipAdder[] adders = new TooltipAdder[]{ + new LineSmoothener(), // Applies before anything else + new SupercraftReminder(), + new ChocolateFactorySolver.Tooltip(), + new NpcPriceTooltip(1), + new BazaarPriceTooltip(2), + new LBinTooltip(3), + new AvgBinTooltip(4), + new DungeonQualityTooltip(5), + new MotesTooltip(6), + new ObtainedDateTooltip(7), + new MuseumTooltip(8), + new ColorTooltip(9), + new AccessoryTooltip(10), + }; + private static final ArrayList<TooltipAdder> currentScreenAdders = new ArrayList<>(); + + private TooltipManager() { + } + + public static void init() { + ItemTooltipCallback.EVENT.register((stack, tooltipContext, tooltipType, lines) -> { + if (MinecraftClient.getInstance().currentScreen instanceof HandledScreen<?> handledScreen) { + addToTooltip(((HandledScreenAccessor) handledScreen).getFocusedSlot(), stack, lines); + } else { + addToTooltip(null, stack, lines); + } + }); + ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { + onScreenChange(screen); + ScreenEvents.remove(screen).register(ignored -> currentScreenAdders.clear()); + }); + } + + private static void onScreenChange(Screen screen) { + final String title = screen.getTitle().getString(); + currentScreenAdders.clear(); + for (TooltipAdder adder : adders) { + if (adder.titlePattern == null || adder.titlePattern.matcher(title).find()) { + currentScreenAdders.add(adder); + } + } + currentScreenAdders.sort(Comparator.comparingInt(adder -> adder.priority)); + } + + /** + * <p>Adds additional text from all adders that are applicable to the current screen. + * This method is run on each tooltip render, so don't do any heavy calculations here.</p> + * + * <p>If you want to add info to the tooltips of multiple items, consider using a switch statement with {@code focusedSlot.getIndex()}</p> + * + * @param focusedSlot The slot that is currently focused by the cursor. + * @param stack The stack to render the tooltip for. + * @param lines The tooltip lines of the focused item. This includes the display name, as it's a part of the tooltip (at index 0). + * @return The lines list itself after all adders have added their text. + * @deprecated This method is public only for the sake of the mixin. Don't call directly, not that there is any point to it. + */ + @Deprecated + public static List<Text> addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + if (!Utils.isOnSkyblock()) return lines; + for (TooltipAdder adder : currentScreenAdders) { + adder.addToTooltip(focusedSlot, stack, lines); + } + return lines; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java new file mode 100644 index 00000000..caed0e0e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java @@ -0,0 +1,45 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AccessoryTooltip extends TooltipAdder { + public AccessoryTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + final String internalID = stack.getSkyblockId(); + if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) { + Pair<AccessoriesHelper.AccessoryReport, String> report = AccessoriesHelper.calculateReport4Accessory(internalID); + + if (report.left() != AccessoriesHelper.AccessoryReport.INELIGIBLE) { + MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); + + Text stateText = switch (report.left()) { + case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); + case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff)); + + //Should never be the case + default -> Text.literal("? Unknown").formatted(Formatting.GRAY); + }; + + lines.add(title.append(stateText)); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java new file mode 100644 index 00000000..d7a56b95 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java @@ -0,0 +1,63 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AvgBinTooltip extends TooltipAdder { + public AvgBinTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + String neuName = stack.getNeuName(); + String internalID = stack.getSkyblockId(); + if (neuName == null || internalID == null) return; + + if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) { + if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { + ItemTooltip.nullWarning(); + } else { + /* + We are skipping check average prices for potions, runes + and enchanted books because there is no data for their in API. + */ + if (!neuName.isEmpty() && LBinTooltip.lbinExist) { + GeneralConfig.Average type = ItemTooltip.config.avg; + + // "No data" line because of API not keeping old data, it causes NullPointerException + if (type == GeneralConfig.Average.ONE_DAY || type == GeneralConfig.Average.BOTH) { + lines.add( + Text.literal(String.format("%-19s", "1 Day Avg. Price:")) + .formatted(Formatting.GOLD) + .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), stack.getCount()) + ) + ); + } + if (type == GeneralConfig.Average.THREE_DAY || type == GeneralConfig.Average.BOTH) { + lines.add( + Text.literal(String.format("%-19s", "3 Day Avg. Price:")) + .formatted(Formatting.GOLD) + .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), stack.getCount()) + ) + ); + } + } + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java new file mode 100644 index 00000000..d2fa563b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java @@ -0,0 +1,57 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class BazaarPriceTooltip extends TooltipAdder { + public static boolean bazaarExist = false; + + public BazaarPriceTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + bazaarExist = false; + final String internalID = stack.getSkyblockId(); + if (internalID == null) return; + String name = stack.getSkyblockApiId(); + if (name == null) return; + + if (name.startsWith("ISSHINY_")) name = "SHINY_" + internalID; + + if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name)) { + int amount; + if (lines.get(1).getString().endsWith("Sack")) { + //The amount is in the 2nd sibling of the 3rd line of the lore. here V + //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]] + String line = lines.get(3).getSiblings().get(1).getString().replace(",", ""); + amount = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount(); + } else { + amount = stack.getCount(); + } + JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name); + lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) + .formatted(Formatting.GOLD) + .append(getItem.get("buyPrice").isJsonNull() + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(getItem.get("buyPrice").getAsDouble(), amount))); + lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) + .formatted(Formatting.GOLD) + .append(getItem.get("sellPrice").isJsonNull() + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(getItem.get("sellPrice").getAsDouble(), amount))); + bazaarExist = true; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java new file mode 100644 index 00000000..7546d37a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java @@ -0,0 +1,126 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringIdentifiable; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class ColorTooltip extends TooltipAdder { + private static final Logger LOGGER = LoggerFactory.getLogger(ColorTooltip.class); + + public ColorTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + final String internalID = stack.getSkyblockId(); + if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.contains(DataComponentTypes.DYED_COLOR)) { + //DyedColorComponent#getColor can be ARGB so we mask out the alpha bits + int dyeColor = stack.get(DataComponentTypes.DYED_COLOR).rgb() & 0x00FFFFFF; + String colorHex = String.format("%06X", dyeColor); + String expectedHex = getExpectedHex(internalID); + + boolean correctLine = false; + for (Text text : lines) { + String existingTooltip = text.getString() + " "; + if (existingTooltip.startsWith("Color: ")) { + correctLine = true; + + addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, existingTooltip); + break; + } + } + + if (!correctLine) { + addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, ""); + } + } + } + + private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound customData, String colorHex, String expectedHex, String existingTooltip) { + if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !isException(internalID, colorHex) && !intendedDyed(customData)) { + final DyeType type = checkDyeType(colorHex); + lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")")); + } + } + + public static String getExpectedHex(String id) { + String color = TooltipInfoType.COLOR.getData().get(id).getAsString(); + if (color != null) { + String[] RGBValues = color.split(","); + return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2])); + } else { + LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id); + return null; + } + } + + public static boolean isException(String id, String hex) { + return switch (id) { + case String it when it.startsWith("LEATHER") || it.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(it) -> true; + case String it when it.startsWith("RANCHER") -> Constants.RANCHERS.contains(hex); + case String it when it.contains("ADAPTIVE_CHESTPLATE") -> Constants.ADAPTIVE_CHEST.contains(hex); + case String it when it.contains("ADAPTIVE") -> Constants.ADAPTIVE.contains(hex); + case String it when it.contains("REAPER") -> Constants.REAPER.contains(hex); + case String it when it.contains("FAIRY") -> Constants.FAIRY_HEXES.contains(hex); + case String it when it.contains("CRYSTAL") -> Constants.CRYSTAL_HEXES.contains(hex); + case String it when it.contains("SPOOK") -> Constants.SPOOK.contains(hex); + default -> false; + }; + } + + public static DyeType checkDyeType(String hex) { + return switch (hex) { + case String it when Constants.CRYSTAL_HEXES.contains(it) -> DyeType.CRYSTAL; + case String it when Constants.FAIRY_HEXES.contains(it) -> DyeType.FAIRY; + case String it when Constants.OG_FAIRY_HEXES.contains(it) -> DyeType.OG_FAIRY; + case String it when Constants.SPOOK.contains(it) -> DyeType.SPOOK; + case String it when Constants.GLITCHED.contains(it) -> DyeType.GLITCHED; + default -> DyeType.EXOTIC; + }; + } + + public static boolean intendedDyed(NbtCompound customData) { + return customData.contains("dye_item"); + } + + public enum DyeType implements StringIdentifiable { + CRYSTAL("crystal", Formatting.AQUA), + FAIRY("fairy", Formatting.LIGHT_PURPLE), + OG_FAIRY("og_fairy", Formatting.DARK_PURPLE), + SPOOK("spook", Formatting.RED), + GLITCHED("glitched", Formatting.BLUE), + EXOTIC("exotic", Formatting.GOLD); + private final String name; + private final Formatting formatting; + + DyeType(String name, Formatting formatting) { + this.name = name; + this.formatting = formatting; + } + + @Override + public String asString() { + return name; + } + + public MutableText getTranslatedText() { + return Text.translatable("skyblocker.exotic." + name).formatted(formatting); + } + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java new file mode 100644 index 00000000..0b1d993d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java @@ -0,0 +1,59 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class DungeonQualityTooltip extends TooltipAdder { + public DungeonQualityTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + if (!SkyblockerConfigManager.get().general.itemTooltip.dungeonQuality) return; + NbtCompound customData = ItemUtils.getCustomData(stack); + if (customData == null || !customData.contains("baseStatBoostPercentage")) return; + int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage"); + boolean maxQuality = baseStatBoostPercentage == 50; + if (maxQuality) { + lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); + } else { + lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); + } + + if (customData.contains("item_tier")) { // sometimes it just isn't here? + int itemTier = customData.getInt("item_tier"); + if (maxQuality) { + lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD)); + } else { + lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.BLUE)); + } + } + } + + final String getItemTierFloor(int tier) { + return switch (tier) { + case 0 -> "E"; + case 1 -> "F1"; + case 2 -> "F2"; + case 3 -> "F3"; + case 4 -> "F4/M1"; + case 5 -> "F5/M2"; + case 6 -> "F6/M3"; + case 7 -> "F7/M4"; + case 8 -> "M5"; + case 9 -> "M6"; + case 10 -> "M7"; + default -> "Unknown"; + }; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java new file mode 100644 index 00000000..e6930c32 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java @@ -0,0 +1,40 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class LBinTooltip extends TooltipAdder { + public static boolean lbinExist = false; + + public LBinTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + lbinExist = false; + final String internalID = stack.getSkyblockId(); + if (internalID == null) return; + String name = stack.getSkyblockApiId(); + if (name == null) return; + + if (name.startsWith("ISSHINY_")) name = "SHINY_" + internalID; + + // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api + + if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !BazaarPriceTooltip.bazaarExist) { + lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) + .formatted(Formatting.GOLD) + .append(ItemTooltip.getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), stack.getCount()))); + lbinExist = true; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java new file mode 100644 index 00000000..0e997834 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class LineSmoothener extends TooltipAdder { + //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); + + public static Text createSmoothLine() { + return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD); + } + + public LineSmoothener() { + super(Integer.MIN_VALUE); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + for (int i = 0; i < lines.size(); i++) { + 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()); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java new file mode 100644 index 00000000..a0aa8d94 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java @@ -0,0 +1,50 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; + +public class MotesTooltip extends TooltipAdder { + public MotesTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + final String internalID = stack.getSkyblockId(); + if (internalID != null && TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) { + lines.add(Text.literal(String.format("%-20s", "Motes Price:")) + .formatted(Formatting.LIGHT_PURPLE) + .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), stack.getCount()))); + } + } + + private static Text getMotesMessage(int price, int count) { + float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1; + + // Calculate the total price + int totalPrice = price * count; + String totalPriceString = String.format(Locale.ENGLISH, "%1$,.1f", totalPrice * motesMultiplier); + + // If count is 1, return a simple message + if (count == 1) { + return Text.literal(totalPriceString.replace(".0", "") + " Motes").formatted(Formatting.DARK_AQUA); + } + + // If count is greater than 1, include the "each" information + String eachPriceString = String.format(Locale.ENGLISH, "%1$,.1f", price * motesMultiplier); + MutableText message = Text.literal(totalPriceString.replace(".0", "") + " Motes ").formatted(Formatting.DARK_AQUA); + message.append(Text.literal("(" + eachPriceString.replace(".0", "") + " each)").formatted(Formatting.GRAY)); + + return message; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java new file mode 100644 index 00000000..5c21d2df --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java @@ -0,0 +1,49 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class MuseumTooltip extends TooltipAdder { + public MuseumTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + final String internalID = stack.getSkyblockId(); + if (TooltipInfoType.MUSEUM.isTooltipEnabledAndHasOrNullWarning(internalID)) { + String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString(); + String format = switch (itemCategory) { + case "Weapons" -> "%-18s"; + case "Armor" -> "%-19s"; + default -> "%-20s"; + }; + + //Special case the special category so that it doesn't always display not donated + if (itemCategory.equals("Special")) { + lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) + .formatted(Formatting.LIGHT_PURPLE)); + } else { + NbtCompound customData = ItemUtils.getCustomData(stack); + boolean isInMuseum = (customData.contains("donated_museum") && customData.getBoolean("donated_museum")) || MuseumItemCache.hasItemInMuseum(internalID); + + Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED; + + lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):")) + .formatted(Formatting.LIGHT_PURPLE) + .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD)) + .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting))); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java new file mode 100644 index 00000000..672201d5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java @@ -0,0 +1,28 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class NpcPriceTooltip extends TooltipAdder { + public NpcPriceTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + final String internalID = stack.getSkyblockId(); + if (internalID != null && TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) { + lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) + .formatted(Formatting.YELLOW) + .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), stack.getCount()))); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java new file mode 100644 index 00000000..9f405c58 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java @@ -0,0 +1,73 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.List; +import java.util.Locale; + +public class ObtainedDateTooltip extends TooltipAdder { + private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH); + private static final DateTimeFormatter OLD_OBTAINED_DATE_FORMAT = DateTimeFormatter.ofPattern("M/d/yy h:m a").withZone(ZoneId.of("UTC")).localizedBy(Locale.ENGLISH); + + public ObtainedDateTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + if (TooltipInfoType.OBTAINED.isTooltipEnabled()) { + String timestamp = getTimestamp(stack); + + if (!timestamp.isEmpty()) { + lines.add(Text.empty() + .append(Text.literal(String.format("%-21s", "Obtained: ")).formatted(Formatting.LIGHT_PURPLE)) + .append(Text.literal(timestamp).formatted(Formatting.RED))); + } + } + } + + /** + * This method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum. + * Currently, there are two types of string timestamps the legacy which is built like this + * "dd/MM/yy hh:mm" ("25/04/20 16:38") and the current which is built like this + * "MM/dd/yy hh:mm aa" ("12/24/20 11:08 PM"). Since Hypixel transforms the two formats into one format without + * taking into account of their formats, we do the same. The final result looks like this + * "MMMM dd, yyyy" (December 24, 2020). + * Since the legacy format has a 25 as "month" SimpleDateFormat converts the 25 into 2 years and 1 month and makes + * "25/04/20 16:38" -> "January 04, 2022" instead of "April 25, 2020". + * This causes the museum rank to be much worse than it should be. + * <p> + * This also handles the long timestamp format introduced in January 2024 where the timestamp is in epoch milliseconds. + * + * @param stack the item under the pointer + * @return if the item have a "Timestamp" it will be shown formated on the tooltip + */ + public static String getTimestamp(ItemStack stack) { + NbtCompound customData = ItemUtils.getCustomData(stack); + + if (customData != null && customData.contains("timestamp", NbtElement.LONG_TYPE)) { + Instant date = Instant.ofEpochMilli(customData.getLong("timestamp")); + return OBTAINED_DATE_FORMATTER.format(date); + } + + if (customData != null && customData.contains("timestamp", NbtElement.STRING_TYPE)) { + TemporalAccessor date = OLD_OBTAINED_DATE_FORMAT.parse(customData.getString("timestamp")); + return OBTAINED_DATE_FORMATTER.format(date); + } + + return ""; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java new file mode 100644 index 00000000..47d2bd48 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java @@ -0,0 +1,32 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.regex.Pattern; + +public class SupercraftReminder extends TooltipAdder { + private static final byte SUPERCRAFT_SLOT = 32; + private static final byte RECIPE_RESULT_SLOT = 25; + + public SupercraftReminder() { + super(Pattern.compile("^.+ Recipe$"), Integer.MIN_VALUE); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + if (focusedSlot == null || focusedSlot.id != SUPERCRAFT_SLOT || !stack.isOf(Items.GOLDEN_PICKAXE)) return; + String uuid = ItemUtils.getItemUuid(focusedSlot.inventory.getStack(RECIPE_RESULT_SLOT)); + if (!uuid.isEmpty()) return; //Items with UUID can't be stacked, and therefore the shift-click feature doesn't matter + int index = lines.size() - 1; + if (lines.get(lines.size() - 2).getString().equals("Recipe not unlocked!")) index--; //Place it right below the "Right-Click to set amount" line + lines.add(index, Text.literal("Shift-Click to maximize the amount!").formatted(Formatting.GOLD)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java new file mode 100644 index 00000000..10e12ace --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java @@ -0,0 +1,92 @@ +package de.hysky.skyblocker.skyblock.itemlist; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class ItemListTab extends ItemListWidget.TabContainerWidget { + + private SearchResultsWidget results; + private final MinecraftClient client; + private TextFieldWidget searchField; + + public ItemListTab(int x, int y, MinecraftClient client, TextFieldWidget searchField) { + super(x, y, Text.literal("Item List Tab")); + this.client = client; + this.searchField = searchField; + if (ItemRepository.filesImported()) { + this.results = new SearchResultsWidget(this.client, x - 9, y - 9 ); + this.results.updateSearchResult(searchField == null ? "": this.searchField.getText()); + } + } + + @Override + public List<? extends Element> children() { + return List.of(results, searchField); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + MatrixStack matrices = context.getMatrices(); + matrices.push(); + matrices.translate(0.0D, 0.0D, 100.0D); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + int x = getX(); + int y = getY(); + + // all coordinates offseted -9 + if (!ItemRepository.filesImported() && !this.searchField.isFocused() && this.searchField.getText().isEmpty()) { + Text hintText = (Text.literal("Loading...")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY); + context.drawTextWithShadow(this.client.textRenderer, hintText, x + 16, y + 7, -1); + } else if (!this.searchField.isFocused() && this.searchField.getText().isEmpty()) { + Text hintText = (Text.translatable("gui.recipebook.search_hint")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY); + context.drawTextWithShadow(this.client.textRenderer, hintText, x + 16, y + 7, -1); + } else { + this.searchField.render(context, mouseX, mouseY, delta); + } + if (ItemRepository.filesImported()) { + if (results == null) { + this.results = new SearchResultsWidget(this.client, x - 9, y - 9); + } + this.results.updateSearchResult(this.searchField.getText()); + this.results.render(context, mouseX, mouseY, delta); + } + matrices.pop(); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} + + public void setSearchField(TextFieldWidget searchField) { + this.searchField = searchField; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!visible) return false; + if (this.searchField.mouseClicked(mouseX, mouseY, button) && this.results != null) { + this.results.closeRecipeView(); + this.searchField.setFocused(true); + return true; + } else if (results != null) { + this.searchField.setFocused(false); + + return this.results.mouseClicked(mouseX, mouseY, button); + } + + return false; + } + + @Override + public void drawTooltip(DrawContext context, int mouseX, int mouseY) { + if (this.results != null) this.results.drawTooltip(context, mouseX, mouseY); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java index 6120528c..be1f4b98 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java @@ -1,103 +1,135 @@ package de.hysky.skyblocker.skyblock.itemlist; -import com.mojang.blaze3d.systems.RenderSystem; - import de.hysky.skyblocker.mixins.accessors.RecipeBookWidgetAccessor; +import de.hysky.skyblocker.utils.render.gui.SideTabButtonWidget; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ContainerWidget; import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.screen.AbstractRecipeScreenHandler; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; + +import java.util.ArrayList; +import java.util.List; @Environment(value = EnvType.CLIENT) public class ItemListWidget extends RecipeBookWidget { private int parentWidth; private int parentHeight; private int leftOffset; - private TextFieldWidget searchField; - private SearchResultsWidget results; + + private TabContainerWidget currentTabContent; + private final List<Pair<SideTabButtonWidget, TabContainerWidget>> tabs = new ArrayList<>(2); + private ItemListTab itemListTab; + + private static int currentTab = 0; public ItemListWidget() { super(); } - public void updateSearchResult() { - this.results.updateSearchResult(((RecipeBookWidgetAccessor) this).getSearchText()); - } - @Override - public void initialize(int parentWidth, int parentHeight, MinecraftClient client, boolean narrow, AbstractRecipeScreenHandler<?> craftingScreenHandler) { + public void initialize(int parentWidth, int parentHeight, MinecraftClient client, boolean narrow, AbstractRecipeScreenHandler<?, ?> craftingScreenHandler) { super.initialize(parentWidth, parentHeight, client, narrow, craftingScreenHandler); this.parentWidth = parentWidth; this.parentHeight = parentHeight; this.leftOffset = narrow ? 0 : 86; - this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField(); - int x = (this.parentWidth - 147) / 2 - this.leftOffset; - int y = (this.parentHeight - 166) / 2; - if (ItemRepository.filesImported()) { - this.results = new SearchResultsWidget(this.client, x, y); - this.updateSearchResult(); - } + TextFieldWidget searchField = ((RecipeBookWidgetAccessor) this).getSearchField(); + int x = (parentWidth - 147) / 2 - leftOffset; + int y = (parentHeight - 166) / 2; + + // Init all the tabs, content and the tab button on the left + tabs.clear(); + + // Item List + itemListTab = new ItemListTab(x + 9, y + 9, this.client, searchField); + SideTabButtonWidget itemListTabButton = new SideTabButtonWidget(x - 30, y + 3, currentTab == 0, new ItemStack(Items.CRAFTING_TABLE)); + itemListTabButton.setTooltip(Tooltip.of(Text.literal("Item List"))); + if (currentTab == 0) currentTabContent = itemListTab; + tabs.add(new ObjectObjectImmutablePair<>( + itemListTabButton, + this.itemListTab)); + + // Upcoming Events + UpcomingEventsTab upcomingEventsTab = new UpcomingEventsTab(x + 9, y + 9, this.client); + SideTabButtonWidget eventsTabButtonWidget = new SideTabButtonWidget(x - 30, y + 3 + 27, currentTab == 1, new ItemStack(Items.CLOCK)); + eventsTabButtonWidget.setTooltip(Tooltip.of(Text.literal("Upcoming Events"))); + if (currentTab == 1) currentTabContent = upcomingEventsTab; + tabs.add(new ObjectObjectImmutablePair<>( + eventsTabButtonWidget, + upcomingEventsTab + )); + + } + + @Override + public void reset() { + super.reset(); + if (itemListTab != null) itemListTab.setSearchField(((RecipeBookWidgetAccessor) this).getSearchField()); } @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { if (this.isOpen()) { - MatrixStack matrices = context.getMatrices(); - matrices.push(); - matrices.translate(0.0D, 0.0D, 100.0D); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField(); int i = (this.parentWidth - 147) / 2 - this.leftOffset; int j = (this.parentHeight - 166) / 2; + // Draw the texture context.drawTexture(TEXTURE, i, j, 1, 1, 147, 166); - this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField(); - - if (!ItemRepository.filesImported() && !this.searchField.isFocused() && this.searchField.getText().isEmpty()) { - Text hintText = (Text.literal("Loading...")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY); - context.drawTextWithShadow(this.client.textRenderer, hintText, i + 25, j + 14, -1); - } else if (!this.searchField.isFocused() && this.searchField.getText().isEmpty()) { - Text hintText = (Text.translatable("gui.recipebook.search_hint")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY); - context.drawTextWithShadow(this.client.textRenderer, hintText, i + 25, j + 14, -1); - } else { - this.searchField.render(context, mouseX, mouseY, delta); - } - if (ItemRepository.filesImported()) { - if (results == null) { - int x = (this.parentWidth - 147) / 2 - this.leftOffset; - int y = (this.parentHeight - 166) / 2; - this.results = new SearchResultsWidget(this.client, x, y); - } - this.updateSearchResult(); - this.results.render(context, mouseX, mouseY, delta); + // Draw the tab's content + if (currentTabContent != null) currentTabContent.render(context, mouseX, mouseY, delta); + // Draw the tab buttons + for (Pair<SideTabButtonWidget, TabContainerWidget> tab : tabs) { + tab.left().render(context, mouseX, mouseY, delta); } - matrices.pop(); + } } @Override public void drawTooltip(DrawContext context, int x, int y, int mouseX, int mouseY) { - if (this.isOpen() && ItemRepository.filesImported() && results != null) { - this.results.drawTooltip(context, mouseX, mouseY); + if (this.isOpen() && currentTabContent != null) { + this.currentTabContent.drawTooltip(context, mouseX, mouseY); } } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.isOpen() && this.client.player != null && !this.client.player.isSpectator() && ItemRepository.filesImported() && this.searchField != null && results != null) { - if (this.searchField.mouseClicked(mouseX, mouseY, button)) { - this.results.closeRecipeView(); - this.searchField.setFocused(true); - return true; - } else { - this.searchField.setFocused(false); - return this.results.mouseClicked(mouseX, mouseY, button); + if (this.isOpen() && this.client.player != null && !this.client.player.isSpectator()) { + // check if a tab is clicked + for (Pair<SideTabButtonWidget, TabContainerWidget> tab : tabs) { + if (tab.first().mouseClicked(mouseX, mouseY, button) && currentTabContent != tab.right()) { + for (Pair<SideTabButtonWidget, TabContainerWidget> tab2 : tabs) { + tab2.first().setToggled(false); + } + tab.first().setToggled(true); + currentTabContent = tab.right(); + currentTab = tabs.indexOf(tab); + return true; + } } + // click the tab content + if (currentTabContent != null) return currentTabContent.mouseClicked(mouseX, mouseY, button); + else return false; } else return false; } + + /** + * A container widget but with a fixed width and height and a drawTooltip method to implement + */ + public abstract static class TabContainerWidget extends ContainerWidget { + + public TabContainerWidget(int x, int y, Text text) { + super(x, y, 131, 150, text); + } + + public abstract void drawTooltip(DrawContext context, int mouseX, int mouseY); + } }
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java index 5ac0ba8d..4dca91d6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java @@ -51,9 +51,13 @@ public class ItemRepository { } private static void loadItem(NEUItem item) { - ItemStack stack = ItemStackBuilder.fromNEUItem(item); - items.add(stack); - itemsMap.put(item.getSkyblockItemId(), stack); + try { + ItemStack stack = ItemStackBuilder.fromNEUItem(item); + items.add(stack); + itemsMap.put(item.getSkyblockItemId(), stack); + } catch (Exception e) { + LOGGER.error("[Skyblocker Item Repo Loader] Failed to load item, please report this! Skyblock Id: {}", item.getSkyblockItemId(), e); + } } private static void loadRecipes(NEUItem item) { @@ -114,9 +118,12 @@ public class ItemRepository { return items.stream(); } + /** + * @param neuId the NEU item id gotten through {@link NEUItem#getSkyblockItemId()}, {@link ItemStack#getNeuName()}, or {@link de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip#getNeuName(String, String) ItemTooltip#getNeuName(String, String)} + */ @Nullable - public static ItemStack getItemStack(String internalName) { - return itemsMap.get(internalName); + public static ItemStack getItemStack(String neuId) { + return itemsMap.get(neuId); } public static Stream<SkyblockCraftingRecipe> getRecipesStream() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java index 6ee75161..01ffc144 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java @@ -21,8 +21,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class ItemStackBuilder { - private static final Pattern SKULL_UUID_PATTERN = Pattern.compile("(?<=SkullOwner:\\{)Id:\"(.{36})\""); - private static final Pattern SKULL_TEXTURE_PATTERN = Pattern.compile("(?<=Properties:\\{textures:\\[0:\\{Value:)\"(.+?)\""); + public static final Pattern SKULL_UUID_PATTERN = Pattern.compile("(?<=SkullOwner:\\{)Id:\"(.{36})\""); + public static final Pattern SKULL_TEXTURE_PATTERN = Pattern.compile("(?<=Properties:\\{textures:\\[0:\\{Value:)\"(.+?)\""); private static final Pattern COLOR_PATTERN = Pattern.compile("color:(\\d+)"); private static final Pattern EXPLOSION_COLOR_PATTERN = Pattern.compile("\\{Explosion:\\{(?:Type:[0-9a-z]+,)?Colors:\\[(?<color>[0-9]+)]\\}"); private static Map<String, Map<Rarity, PetNumbers>> petNums; @@ -41,7 +41,7 @@ public class ItemStackBuilder { List<Pair<String, String>> injectors = new ArrayList<>(petData(internalName)); String legacyId = item.getMinecraftItemId(); - Identifier itemId = new Identifier(ItemFixerUpper.convertItemId(legacyId, item.getDamage())); + Identifier itemId = Identifier.of(ItemFixerUpper.convertItemId(legacyId, item.getDamage())); ItemStack stack = new ItemStack(Registries.ITEM.get(itemId)); @@ -138,7 +138,7 @@ public class ItemStackBuilder { return list; } - private static String injectData(String string, List<Pair<String, String>> injectors) { + public static String injectData(String string, List<Pair<String, String>> injectors) { for (Pair<String, String> injector : injectors) { string = string.replaceAll(injector.getLeft(), injector.getRight()); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java index d2d463c7..6c7d1cbc 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java @@ -1,30 +1,27 @@ package de.hysky.skyblocker.skyblock.itemlist; -import java.util.List; -import java.util.ArrayList; - import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.text.OrderedText; import net.minecraft.text.Text; import net.minecraft.util.Identifier; +import java.util.List; + public class ResultButtonWidget extends ClickableWidget { - private static final Identifier BACKGROUND_TEXTURE = new Identifier("recipe_book/slot_craftable"); + private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("recipe_book/slot_craftable"); protected ItemStack itemStack = null; public ResultButtonWidget(int x, int y) { - super(x, y, 25, 25, Text.of("")); + super(x, y, 25, 25, Text.literal("")); } protected void setItemStack(ItemStack itemStack) { - this.active = !itemStack.getItem().equals(Items.AIR); + this.active = !itemStack.isEmpty(); this.visible = true; this.itemStack = itemStack; } @@ -37,29 +34,18 @@ public class ResultButtonWidget extends ClickableWidget { @Override public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { MinecraftClient client = MinecraftClient.getInstance(); - // this.drawTexture(matrices, this.x, this.y, 29, 206, this.width, this.height); context.drawGuiTexture(BACKGROUND_TEXTURE, this.getX(), this.getY(), this.getWidth(), this.getHeight()); - // client.getItemRenderer().renderInGui(this.itemStack, this.x + 4, this.y + 4); context.drawItem(this.itemStack, this.getX() + 4, this.getY() + 4); - // client.getItemRenderer().renderGuiItemOverlay(client.textRenderer, itemStack, this.x + 4, this.y + 4); context.drawItemInSlot(client.textRenderer, itemStack, this.getX() + 4, this.getY() + 4); } public void renderTooltip(DrawContext context, int mouseX, int mouseY) { MinecraftClient client = MinecraftClient.getInstance(); + if (client.currentScreen == null) return; List<Text> tooltip = Screen.getTooltipFromItem(client, this.itemStack); - List<OrderedText> orderedTooltip = new ArrayList<>(); - - for(int i = 0; i < tooltip.size(); i++) { - orderedTooltip.add(tooltip.get(i).asOrderedText()); - } - - client.currentScreen.setTooltip(orderedTooltip); + client.currentScreen.setTooltip(tooltip.stream().map(Text::asOrderedText).toList()); } - @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - // TODO Auto-generated method stub - - } + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java index 1ef352e3..dfb53628 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java @@ -6,6 +6,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.Element; import net.minecraft.client.gui.screen.ButtonTextures; import net.minecraft.client.gui.widget.ToggleButtonWidget; import net.minecraft.component.DataComponentTypes; @@ -23,9 +24,9 @@ import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class SearchResultsWidget implements Drawable { - private static final ButtonTextures PAGE_FORWARD_TEXTURES = new ButtonTextures(new Identifier("recipe_book/page_forward"), new Identifier("recipe_book/page_forward_highlighted")); - private static final ButtonTextures PAGE_BACKWARD_TEXTURES = new ButtonTextures(new Identifier("recipe_book/page_backward"), new Identifier("recipe_book/page_backward_highlighted")); +public class SearchResultsWidget implements Drawable, Element { + private static final ButtonTextures PAGE_FORWARD_TEXTURES = new ButtonTextures(Identifier.ofVanilla("recipe_book/page_forward"), Identifier.ofVanilla("recipe_book/page_forward_highlighted")); + private static final ButtonTextures PAGE_BACKWARD_TEXTURES = new ButtonTextures(Identifier.ofVanilla("recipe_book/page_backward"), Identifier.ofVanilla("recipe_book/page_backward_highlighted")); private static final int COLS = 5; private static final int MAX_TEXT_WIDTH = 124; private static final String ELLIPSIS = "..."; @@ -225,4 +226,16 @@ public class SearchResultsWidget implements Drawable { return false; } + private boolean focused = false; + + @Override + public void setFocused(boolean focused) { + this.focused = focused; + } + + @Override + public boolean isFocused() { + return focused; + } + } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java new file mode 100644 index 00000000..9552ae87 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java @@ -0,0 +1,168 @@ +package de.hysky.skyblocker.skyblock.itemlist; + +import de.hysky.skyblocker.mixins.accessors.DrawContextInvoker; +import de.hysky.skyblocker.skyblock.events.EventNotifications; +import de.hysky.skyblocker.skyblock.tabhud.widget.JacobsContestWidget; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.tooltip.HoveredTooltipPositioner; +import net.minecraft.client.gui.tooltip.TooltipComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Colors; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +public class UpcomingEventsTab extends ItemListWidget.TabContainerWidget { + private static final ItemStack CLOCK = new ItemStack(Items.CLOCK); + private final MinecraftClient client; + private final List<EventRenderer> events; + + public UpcomingEventsTab(int x, int y, MinecraftClient client) { + super(x, y, Text.literal("Upcoming Events Tab")); + this.client = client; + events = EventNotifications.getEvents().entrySet() + .stream() + .sorted(Comparator.comparingLong(a -> a.getValue().isEmpty() ? Long.MAX_VALUE : a.getValue().peekFirst().start())) + .map(stringLinkedListEntry -> new EventRenderer(stringLinkedListEntry.getKey(), stringLinkedListEntry.getValue())) + .toList(); + } + + @Override + public void drawTooltip(DrawContext context, int mouseX, int mouseY) { + if (hovered != null) { + ((DrawContextInvoker) context).invokeDrawTooltip(this.client.textRenderer, hovered.getTooltip(), mouseX, mouseY, HoveredTooltipPositioner.INSTANCE); + } + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + + private EventRenderer hovered = null; + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + int x = getX(); + int y = getY(); + context.enableScissor(x, y, getRight(), getBottom()); + context.drawItem(CLOCK, x, y + 4); + context.drawText(this.client.textRenderer, "Upcoming Events", x + 17, y + 7, -1, true); + + int eventsY = y + 7 + 24; + hovered = null; + for (EventRenderer eventRenderer : events) { + eventRenderer.render(context, x + 1, eventsY, mouseX, mouseY); + if (isMouseOver(mouseX, mouseY) && eventRenderer.isMouseOver(mouseX, mouseY, x+1, eventsY)) hovered = eventRenderer; + eventsY += eventRenderer.getHeight(); + + } + context.disableScissor(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (hovered != null && hovered.getWarpCommand() != null) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown(hovered.getWarpCommand()); + return true; + } + return false; + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + + } + + public static class EventRenderer { + + private final LinkedList<EventNotifications.SkyblockEvent> events; + private final String eventName; + + public EventRenderer(String eventName, LinkedList<EventNotifications.SkyblockEvent> events) { + this.events = events; + this.eventName = eventName; + } + + public void render(DrawContext context, int x, int y, int mouseX, int mouseY) { + long time = System.currentTimeMillis() / 1000; + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + context.drawText(textRenderer, Text.literal(eventName).fillStyle(Style.EMPTY.withUnderline(isMouseOver(mouseX, mouseY, x, y))), x, y, -1, true); + if (events.isEmpty()) { + context.drawText(textRenderer, Text.literal(" ").append(Text.translatable("skyblocker.events.tab.noMore")), x, y + textRenderer.fontHeight, Colors.GRAY, false); + } else if (events.peekFirst().start() > time) { + MutableText formatted = Text.literal(" ").append(Text.translatable("skyblocker.events.tab.startsIn", Utils.getDurationText((int) (events.peekFirst().start() - time)))).formatted(Formatting.YELLOW); + context.drawText(textRenderer, formatted, x, y + textRenderer.fontHeight, -1, true); + } else { + MutableText formatted = Text.literal(" ").append(Text.translatable( "skyblocker.events.tab.endsIn", Utils.getDurationText((int) (events.peekFirst().start() + events.peekFirst().duration() - time)))).formatted(Formatting.GREEN); + context.drawText(textRenderer, formatted, x, y + textRenderer.fontHeight, -1, true); + } + + } + + public int getHeight() { + return 20; + } + + public boolean isMouseOver(int mouseX, int mouseY, int x, int y) { + return mouseX >= x && mouseX <= x + 131 && mouseY >= y && mouseY <= y+getHeight(); + } + + public List<TooltipComponent> getTooltip() { + List<TooltipComponent> components = new ArrayList<>(); + if (events.peekFirst() == null) return components; + if (eventName.equals(EventNotifications.JACOBS)) { + components.add(new JacobsTooltip(events.peekFirst().extras())); + } + //noinspection DataFlowIssue + if (events.peekFirst().warpCommand() != null) { + components.add(TooltipComponent.of(Text.translatable("skyblocker.events.tab.clickToWarp").formatted(Formatting.ITALIC).asOrderedText())); + } + + return components; + } + + public @Nullable String getWarpCommand() { + if (events.isEmpty()) return null; + return events.peek().warpCommand(); + } + } + + private record JacobsTooltip(String[] crops) implements TooltipComponent { + + private static final ItemStack BARRIER = new ItemStack(Items.BARRIER); + + @Override + public int getHeight() { + return 20; + } + + @Override + public int getWidth(TextRenderer textRenderer) { + return 16 * 3 + 4; + } + + @Override + public void drawItems(TextRenderer textRenderer, int x, int y, DrawContext context) { + for (int i = 0; i < crops.length; i++) { + String crop = crops[i]; + context.drawItem(JacobsContestWidget.FARM_DATA.getOrDefault(crop, BARRIER), x + 18 * i, y + 2); + } + } + + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java new file mode 100644 index 00000000..d867a0e6 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java @@ -0,0 +1,65 @@ +package de.hysky.skyblocker.skyblock.profileviewer; + +import com.mojang.blaze3d.systems.RenderSystem; +import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.util.Map; + +public class ProfileViewerNavButton extends ClickableWidget { + private final static Identifier BUTTON_TEXTURES_TOGGLED = Identifier.of("container/creative_inventory/tab_top_selected_2"); + private final static Identifier BUTTON_TEXTURES = Identifier.of("container/creative_inventory/tab_top_unselected_2"); + private boolean toggled; + private final int index; + private final ProfileViewerScreen screen; + private final ItemStack icon; + + private static final Map<String, ItemStack> HEAD_ICON = Map.ofEntries( + Map.entry("Skills", Ico.IRON_SWORD), + Map.entry("Slayers", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")), + Map.entry("Pets", Ico.BONE), + Map.entry("Dungeons", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")), + Map.entry("Inventories", Ico.E_CHEST), + Map.entry("Collections", Ico.PAINTING) + ); + + public ProfileViewerNavButton(ProfileViewerScreen screen, String tabName, int index, boolean toggled) { + super(-100, -100, 28, 32, Text.empty()); + this.screen = screen; + this.toggled = toggled; + this.index = index; + this.icon = HEAD_ICON.getOrDefault(tabName, Ico.BARRIER); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + RenderSystem.disableDepthTest(); + + context.drawGuiTexture(toggled ? BUTTON_TEXTURES_TOGGLED : BUTTON_TEXTURES, this.getX(), this.getY(), this.width, this.height - ((this.toggled) ? 0 : 4)); + context.drawItem(this.icon, this.getX() + 6, this.getY() + (this.toggled ? 7 : 9)); + + RenderSystem.enableDepthTest(); + } + + @Override + public void onClick(double mouseX, double mouseY) { + screen.onNavButtonClick(this); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} + + public void setToggled(boolean toggled) { + this.toggled = toggled; + } + + public int getIndex() { + return index; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java new file mode 100644 index 00000000..f5a5ec40 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java @@ -0,0 +1,19 @@ +package de.hysky.skyblocker.skyblock.profileviewer; + +import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ClickableWidget; + +import java.util.List; + +public interface ProfileViewerPage { + void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY); + default List<ClickableWidget> getButtons() { + return null; + } + default void onNavButtonClick(SubPageSelectButton selectButton) {} + default void markWidgetsAsVisible() {} + default void markWidgetsAsInvisible() {} + default void nextPage() {} + default void previousPage() {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java new file mode 100644 index 00000000..1d0b21ca --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java @@ -0,0 +1,230 @@ +package de.hysky.skyblocker.skyblock.profileviewer; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.mixins.accessors.SkullBlockEntityAccessor; +import de.hysky.skyblocker.skyblock.profileviewer.collections.CollectionsPage; +import de.hysky.skyblocker.skyblock.profileviewer.dungeons.DungeonsPage; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.InventoryPage; +import de.hysky.skyblocker.skyblock.profileviewer.skills.SkillsPage; +import de.hysky.skyblocker.skyblock.profileviewer.slayers.SlayersPage; +import de.hysky.skyblocker.utils.ApiUtils; +import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.utils.ProfileUtils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.network.OtherClientPlayerEntity; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.client.util.SkinTextures; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerModelPart; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.io.IOException; +import java.util.List; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static net.minecraft.client.gui.screen.ingame.InventoryScreen.drawEntity; + +public class ProfileViewerScreen extends Screen { + public static final Logger LOGGER = LoggerFactory.getLogger(ProfileViewerScreen.class); + private static final Text TITLE = Text.of("Skyblocker Profile Viewer"); + private static final String HYPIXEL_COLLECTIONS = "https://api.hypixel.net/v2/resources/skyblock/collections"; + private static final Object2ObjectOpenHashMap<String, Map<String, ?>> COLLECTIONS_CACHE = new Object2ObjectOpenHashMap<>(); + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/base_plate.png"); + private static final int GUI_WIDTH = 322; + private static final int GUI_HEIGHT = 180; + + private String playerName; + private JsonObject hypixelProfile; + private JsonObject playerProfile; + + private int activePage = 0; + private static final String[] PAGE_NAMES = {"Skills", "Slayers", "Dungeons", "Inventories", "Collections"}; + private final ProfileViewerPage[] profileViewerPages = new ProfileViewerPage[PAGE_NAMES.length]; + private final List<ProfileViewerNavButton> profileViewerNavButtons = new ArrayList<>(); + private OtherClientPlayerEntity entity; + private ProfileViewerTextWidget textWidget; + + public ProfileViewerScreen(String username) { + super(TITLE); + fetchPlayerData(username).thenRun(this::initialisePagesAndWidgets); + + for (int i = 0; i < PAGE_NAMES.length; i++) { + profileViewerNavButtons.add(new ProfileViewerNavButton(this, PAGE_NAMES[i], i, i == 0)); + } + } + + private void initialisePagesAndWidgets() { + textWidget = new ProfileViewerTextWidget(hypixelProfile, playerProfile); + + CompletableFuture<Void> skillsFuture = CompletableFuture.runAsync(() -> profileViewerPages[0] = new SkillsPage(hypixelProfile, playerProfile)); + CompletableFuture<Void> slayersFuture = CompletableFuture.runAsync(() -> profileViewerPages[1] = new SlayersPage(playerProfile)); + CompletableFuture<Void> dungeonsFuture = CompletableFuture.runAsync(() -> profileViewerPages[2] = new DungeonsPage(playerProfile)); + CompletableFuture<Void> inventoriesFuture = CompletableFuture.runAsync(() -> profileViewerPages[3] = new InventoryPage(playerProfile)); + CompletableFuture<Void> collectionsFuture = CompletableFuture.runAsync(() -> profileViewerPages[4] = new CollectionsPage(hypixelProfile, playerProfile)); + + CompletableFuture.allOf(skillsFuture, slayersFuture, dungeonsFuture, inventoriesFuture, collectionsFuture) + .thenRun(() -> { + synchronized (this) { + clearAndInit(); + } + }); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + synchronized (this) { + super.render(context, mouseX, mouseY, delta); + } + + int rootX = width / 2 - GUI_WIDTH / 2; + int rootY = height / 2 - GUI_HEIGHT / 2 + 5; + + context.drawTexture(TEXTURE, rootX, rootY, 0, 0, GUI_WIDTH, GUI_HEIGHT, GUI_WIDTH, GUI_HEIGHT); + for (ProfileViewerNavButton button : profileViewerNavButtons) { + button.setX(rootX + button.getIndex() * 28 + 4); + button.setY(rootY - 28); + button.render(context, mouseX, mouseY, delta); + } + + if (textWidget != null) textWidget.render(context, textRenderer, rootX + 8, rootY + 120); + drawPlayerEntity(context, playerName != null ? playerName : "Loading...", rootX, rootY, mouseX, mouseY); + + if (profileViewerPages[activePage] != null) { + profileViewerPages[activePage].markWidgetsAsVisible(); + profileViewerPages[activePage].render(context, mouseX, mouseY, delta, rootX + 93, rootY + 7); + } else { + context.drawText(textRenderer, "Loading...", rootX + 180, rootY + 80, Color.WHITE.getRGB(), true); + } + } + + private void drawPlayerEntity(DrawContext context, String username, int rootX, int rootY, int mouseX, int mouseY) { + if (entity != null) + drawEntity(context, rootX + 9, rootY + 16, rootX + 89, rootY + 124, 42, 0.0625F, mouseX, mouseY, entity); + context.drawCenteredTextWithShadow(textRenderer, username.length() > 15 ? username.substring(0, 15) : username, rootX + 47, rootY + 14, Color.WHITE.getRGB()); + + } + + private CompletableFuture<Void> fetchPlayerData(String username) { + CompletableFuture<Void> profileFuture = ProfileUtils.fetchFullProfile(username).thenAccept(profiles -> { + this.hypixelProfile = profiles.getAsJsonArray("profiles").asList().stream() + .map(JsonElement::getAsJsonObject) + .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean()) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No selected profile found!")); + + this.playerProfile = hypixelProfile.getAsJsonObject("members").get(ApiUtils.name2Uuid(username)).getAsJsonObject(); + }); + + CompletableFuture<Void> minecraftProfileFuture = SkullBlockEntityAccessor.invokeFetchProfileByName(username).thenAccept(profile -> { + this.playerName = profile.get().getName(); + entity = new OtherClientPlayerEntity(MinecraftClient.getInstance().world, profile.get()) { + @Override + public SkinTextures getSkinTextures() { + PlayerListEntry playerListEntry = new PlayerListEntry(profile.get(), false); + return playerListEntry.getSkinTextures(); + } + + @Override + public boolean isPartVisible(PlayerModelPart modelPart) { + return !(modelPart.getName().equals(PlayerModelPart.CAPE.getName())); + } + + @Override + public boolean isInvisibleTo(PlayerEntity player) { + return true; + } + }; + entity.setCustomNameVisible(false); + }).exceptionally(ex -> { + this.playerName = "User not found"; + return null; + }); + + return CompletableFuture.allOf(profileFuture, minecraftProfileFuture); + } + + public void onNavButtonClick(ProfileViewerNavButton clickedButton) { + if (profileViewerPages[activePage] != null) profileViewerPages[activePage].markWidgetsAsInvisible(); + for (ProfileViewerNavButton button : profileViewerNavButtons) { + button.setToggled(false); + } + activePage = clickedButton.getIndex(); + clickedButton.setToggled(true); + } + + @Override + public void init() { + profileViewerNavButtons.forEach(this::addDrawableChild); + for (ProfileViewerPage profileViewerPage : profileViewerPages) { + if (profileViewerPage != null && profileViewerPage.getButtons() != null) { + for (ClickableWidget button : profileViewerPage.getButtons()) { + if (button != null) addDrawableChild(button); + } + } + } + } + + public static void initClass() { + fetchCollectionsData(); // caching on launch + + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { + LiteralArgumentBuilder<FabricClientCommandSource> literalArgumentBuilder = ClientCommandManager.literal("pv") + .then(ClientCommandManager.argument("username", StringArgumentType.string()) + .executes(Scheduler.queueOpenScreenFactoryCommand(context -> new ProfileViewerScreen(StringArgumentType.getString(context, "username")))) + ) + .executes(Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(MinecraftClient.getInstance().getSession().getUsername()))); + dispatcher.register(literalArgumentBuilder); + dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE).then(literalArgumentBuilder)); + }); + } + + @NotNull + public static Map<String, Map<String, ?>> fetchCollectionsData() { + if (!COLLECTIONS_CACHE.isEmpty()) return COLLECTIONS_CACHE; + try { + JsonObject jsonObject = JsonParser.parseString(Http.sendGetRequest(HYPIXEL_COLLECTIONS)).getAsJsonObject(); + if (jsonObject.get("success").getAsBoolean()) { + Map<String, String[]> collectionsMap = new HashMap<>(); + Map<String, List<Integer>> tierRequirementsMap = new HashMap<>(); + JsonObject collections = jsonObject.getAsJsonObject("collections"); + collections.entrySet().forEach(entry -> { + String category = entry.getKey(); + JsonObject itemsObject = entry.getValue().getAsJsonObject().getAsJsonObject("items"); + String[] items = itemsObject.keySet().toArray(new String[0]); + collectionsMap.put(category, items); + itemsObject.entrySet().forEach(itemEntry -> { + List<Integer> tierReqs = new ArrayList<>(); + itemEntry.getValue().getAsJsonObject().getAsJsonArray("tiers").forEach(req -> + tierReqs.add(req.getAsJsonObject().get("amountRequired").getAsInt())); + tierRequirementsMap.put(itemEntry.getKey(), tierReqs); + }); + }); + COLLECTIONS_CACHE.put("COLLECTIONS", collectionsMap); + COLLECTIONS_CACHE.put("TIER_REQS", tierRequirementsMap); + return COLLECTIONS_CACHE; + } + } catch (IOException | InterruptedException e) { + LOGGER.error("[Skyblocker Profile Viewer] Failed to fetch collections data", e); + } + return Collections.emptyMap(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java new file mode 100644 index 00000000..4ee2dbba --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java @@ -0,0 +1,55 @@ +package de.hysky.skyblocker.skyblock.profileviewer; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Colors; + +public class ProfileViewerTextWidget { + private static final int ROW_GAP = 9; + + private String PROFILE_NAME = "UNKNOWN"; + private int SKYBLOCK_LEVEL = 0; + private double PURSE = 0; + private double BANK = 0; + + public ProfileViewerTextWidget(JsonObject hypixelProfile, JsonObject playerProfile){ + try { + this.PROFILE_NAME = hypixelProfile.get("cute_name").getAsString(); + this.SKYBLOCK_LEVEL = playerProfile.getAsJsonObject("leveling").get("experience").getAsInt() / 100; + this.PURSE = playerProfile.getAsJsonObject("currencies").get("coin_purse").getAsDouble(); + this.BANK = hypixelProfile.getAsJsonObject("banking").get("balance").getAsDouble(); + } catch (Exception ignored) {} + } + + public void render(DrawContext context, TextRenderer textRenderer, int root_x, int root_y){ + // Profile Icon + MatrixStack matrices = context.getMatrices(); + matrices.push(); + matrices.scale(0.75f, 0.75f, 1); + int rootAdjustedX = (int) ((root_x) / 0.75f); + int rootAdjustedY = (int) ((root_y) / 0.75f); + context.drawItem(Ico.PAINTING, rootAdjustedX, rootAdjustedY); + matrices.pop(); + + context.drawText(textRenderer, "§n"+PROFILE_NAME, root_x + 14, root_y + 3, Colors.WHITE, true); + context.drawText(textRenderer, "§aLevel:§r " + SKYBLOCK_LEVEL, root_x + 2, root_y + 6 + ROW_GAP, Colors.WHITE, true); + context.drawText(textRenderer, "§6Purse:§r " + formatCoins(PURSE), root_x + 2, root_y + 6 + ROW_GAP * 2, Colors.WHITE, true); + context.drawText(textRenderer, "§6Bank:§r " + formatCoins(BANK), root_x + 2, root_y + 6 + ROW_GAP * 3, Colors.WHITE, true); + context.drawText(textRenderer, "§6NW:§r " + "Soon™", root_x + 2, root_y + 6 + ROW_GAP * 4, Colors.WHITE, true ); + } + + private String formatCoins(double amount) { + if (amount >= 1_000_000_000) { + return String.format("%.4gB", amount / 1_000_000_000); + } else if (amount >= 1_000_000) { + return String.format("%.4gM", amount / 1_000_000); + } else if (amount >= 1_000) { + return String.format("%.4gK", amount / 1_000); + } else { + return String.valueOf((int)(amount)); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java new file mode 100644 index 00000000..b77c3e7a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java @@ -0,0 +1,100 @@ +package de.hysky.skyblocker.skyblock.profileviewer.collections; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.item.ItemStack; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class CollectionsPage implements ProfileViewerPage { + private static final String[] COLLECTION_CATEGORIES = {"MINING", "FARMING", "COMBAT", "FISHING", "FORAGING", "RIFT"}; + private static final int TOTAL_HEIGHT = 165; + private static final Map<String, ItemStack> ICON_MAP = Map.ofEntries( + Map.entry("MINING", Ico.STONE_PICKAXE), + Map.entry("FARMING", Ico.GOLDEN_HOE), + Map.entry("COMBAT", Ico.STONE_SWORD), + Map.entry("FISHING", Ico.FISH_ROD), + Map.entry("FORAGING", Ico.JUNGLE_SAPLING), + // Map.entry("BOSS", Ico.WITHER), Not currently part of Collections API so skipping for now + Map.entry("RIFT", Ico.MYCELIUM) + ); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + + private final GenericCategory[] collections = new GenericCategory[COLLECTION_CATEGORIES.length]; + private final List<SubPageSelectButton> collectionSelectButtons = new ArrayList<>(); + private int activePage = 0; + + + public CollectionsPage(JsonObject hProfile, JsonObject pProfile) { + for (int i = 0; i < COLLECTION_CATEGORIES.length; i++) { + try { + collectionSelectButtons.add(new SubPageSelectButton(this, -100, 0, i, ICON_MAP.getOrDefault(COLLECTION_CATEGORIES[i], Ico.BARRIER))); + collections[i] = new GenericCategory(hProfile, pProfile, COLLECTION_CATEGORIES[i]); + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error creating Collections Page", e); + } + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + int startingY = rootY + (TOTAL_HEIGHT - collectionSelectButtons.size() * 21) / 2; + for (int i = 0; i < collectionSelectButtons.size(); i++) { + collectionSelectButtons.get(i).setX(rootX); + collectionSelectButtons.get(i).setY(startingY + i * 21); + collectionSelectButtons.get(i).render(context, mouseX, mouseY, delta); + } + + if (collections[activePage] == null) { + context.drawText(textRenderer, "No data...", rootX + 92, rootY + 72, Color.DARK_GRAY.getRGB(), false); + return; + } + + collections[activePage].markWidgetsAsVisible(); + collections[activePage].render(context, mouseX, mouseY, delta, rootX + 35, rootY + 6); + } + + public void onNavButtonClick(SubPageSelectButton selectButton) { + if (collections[activePage] != null) collections[activePage].markWidgetsAsInvisible(); + for (SubPageSelectButton button : collectionSelectButtons) { + button.setToggled(false); + } + activePage = selectButton.getIndex(); + selectButton.setToggled(true); + } + + @Override + public List<ClickableWidget> getButtons() { + List<ClickableWidget> clickableWidgets = new ArrayList<>(collectionSelectButtons); + for (ProfileViewerPage page : collections) { + if (page != null && page.getButtons() != null) clickableWidgets.addAll(page.getButtons()); + } + return clickableWidgets; + } + + @Override + public void markWidgetsAsVisible() { + for (SubPageSelectButton button : collectionSelectButtons) { + button.visible = true; + button.active = true; + } + } + + @Override + public void markWidgetsAsInvisible() { + for (SubPageSelectButton button : collectionSelectButtons) { + button.visible = false; + button.active = false; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java new file mode 100644 index 00000000..ef26332e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java @@ -0,0 +1,136 @@ +package de.hysky.skyblocker.skyblock.profileviewer.collections; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.NEURepoManager; +import io.github.moulberry.repo.data.NEUItem; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.LoreComponent; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.text.NumberFormat; +import java.util.List; +import java.util.*; + +import static de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen.fetchCollectionsData; + +public class GenericCategory implements ProfileViewerPage { + private final String category; + private final LinkedList<ItemStack> collections = new LinkedList<>(); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final NumberFormat FORMATTER = NumberFormat.getInstance(Locale.US); + private static final Identifier BUTTON_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled.png"); + private static final int COLUMN_GAP = 26; + private static final int ROW_GAP = 34; + private static final int COLUMNS = 7; + + private final Map<String, String[]> collectionsMap; + private final Map<String, List<Integer>> tierRequirementsMap; + private final Map<String, String> ICON_TRANSLATION = Map.ofEntries( + Map.entry("MUSHROOM_COLLECTION", "RED_MUSHROOM")); + private final String[] ROMAN_NUMERALS = {"-", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX"}; + + public GenericCategory(JsonObject hProfile, JsonObject pProfile, String collection) { + Map<String, Map<String, ?>> fetchedData = fetchCollectionsData(); + //noinspection unchecked + collectionsMap = (Map<String, String[]>) fetchedData.get("COLLECTIONS"); + //noinspection unchecked + tierRequirementsMap = (Map<String, List<Integer>>) fetchedData.get("TIER_REQS"); + this.category = collection; + setupItemStacks(hProfile, pProfile); + } + + private int calculateTier(int achieved, List<Integer> requirements) { + return (int) requirements.stream().filter(req -> achieved >= req).count(); + } + + private void setupItemStacks(JsonObject hProfile, JsonObject pProfile) { + JsonObject playerCollection = pProfile.getAsJsonObject("collection"); + + for (String collection : collectionsMap.get(this.category)) { + Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems(); + ItemStack itemStack = items.values().stream() + .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(ICON_TRANSLATION.getOrDefault(collection, collection).replace(':', '-'))) + .findFirst() + .map(NEUItem::getSkyblockItemId) + .map(ItemRepository::getItemStack) + .map(ItemStack::copy) + .orElse(Ico.BARRIER.copy()); + + if (itemStack.getItem().getName().getString().equals("Barrier")) itemStack.set(DataComponentTypes.ITEM_NAME, Text.of(collection)); + + int personalColl = playerCollection != null && playerCollection.has(collection) ? playerCollection.get(collection).getAsInt() : 0; + + int coopColl = 0; + for (String member : hProfile.get("members").getAsJsonObject().keySet()) { + if (!hProfile.getAsJsonObject("members").getAsJsonObject(member).has("collection")) continue; + JsonObject memberColl = hProfile.getAsJsonObject("members").getAsJsonObject(member).getAsJsonObject("collection"); + coopColl += memberColl.has(collection) ? memberColl.get(collection).getAsInt() : 0; + } + + int collectionTier = calculateTier(coopColl, tierRequirementsMap.get(collection)); + List<Integer> tierRequirements = tierRequirementsMap.get(collection); + + List<Text> lore = new ArrayList<>(); + Style style = Style.EMPTY.withItalic(false); + lore.add(Text.literal("Collection: " + FORMATTER.format(personalColl)).setStyle(style).formatted(Formatting.YELLOW)); + if (hProfile.get("members").getAsJsonObject().keySet().size() > 1) { + lore.add(Text.literal("Co-op Collection: " + FORMATTER.format(coopColl)).setStyle(style).formatted(Formatting.AQUA)); + } + lore.add(Text.literal("Collection Tier: " + collectionTier).setStyle(style).formatted(Formatting.LIGHT_PURPLE)); + itemStack.set(DataComponentTypes.LORE, new LoreComponent(lore)); + + if (collectionTier == tierRequirements.size()) itemStack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); + + collections.add(itemStack); + } + } + + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + Text categoryTitle = Text.literal(category.charAt(0) + category.substring(1).toLowerCase() + " Collections").formatted(Formatting.BOLD); + context.drawText(textRenderer, categoryTitle, rootX + 88 - (textRenderer.getWidth(categoryTitle) / 2), rootY, Color.DARK_GRAY.getRGB(), false); + + for (int i = 0; i < collections.size(); i++) { + int x = rootX + 2 + (i % COLUMNS) * COLUMN_GAP; + int y = rootY + 19 + (i / COLUMNS) * ROW_GAP; + + context.fill(x - 3, y - 3, x + 19, y + 19, Color.BLACK.getRGB()); + context.drawTexture(BUTTON_TEXTURE, x - 2, y - 2, 0, 0, 20, 20, 20, 20); + context.drawItem(collections.get(i), x, y); + + ItemStack itemStack = collections.get(i); + List<Text> lore = itemStack.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).lines(); + for (Text text : lore) { + if (!text.getString().startsWith("Collection Tier: ")) continue; + int cTier = Integer.parseInt(text.getString().substring("Collection Tier: ".length())); + Color colour = Boolean.TRUE.equals(itemStack.get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE)) ? Color.MAGENTA : Color.darkGray; + context.drawText(textRenderer, Text.literal(toRomanNumerals(cTier)), x + 9 - (textRenderer.getWidth(toRomanNumerals(cTier)) / 2), y + 21, colour.getRGB(), false); + break; + } + + if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) { + List<Text> tooltip = collections.get(i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC); + context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); + } + } + } + + private String toRomanNumerals(int number) { + return number <= ROMAN_NUMERALS.length ? ROMAN_NUMERALS[number] : "Err"; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java new file mode 100644 index 00000000..3b847b1b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java @@ -0,0 +1,62 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.Map; + +public class DungeonClassWidget { + private final String className; + private LevelFinder.LevelInfo classLevel; + private static final int CLASS_CAP = 50; + private JsonObject classData; + private final ItemStack stack; + private boolean active = false; + + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png"); + private static final Identifier ACTIVE_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); + private static final Identifier BAR_FILL = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_fill"); + private static final Identifier BAR_BACK = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_back"); + + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final Map<String, ItemStack> CLASS_ICON = Map.ofEntries( + Map.entry("Healer", Ico.S_POTION), + Map.entry("Mage", Ico.B_ROD), + Map.entry("Berserk", Ico.IRON_SWORD), + Map.entry("Archer", Ico.BOW), + Map.entry("Tank", Ico.CHESTPLATE) + ); + + public DungeonClassWidget(String className, JsonObject playerProfile) { + this.className = className; + stack = CLASS_ICON.getOrDefault(className, Ico.BARRIER); + try { + classData = playerProfile.getAsJsonObject("dungeons").getAsJsonObject("player_classes").getAsJsonObject(this.className.toLowerCase()); + classLevel = LevelFinder.getLevelInfo("Catacombs", classData.get("experience").getAsLong()); + active = playerProfile.getAsJsonObject("dungeons").get("selected_dungeon_class").getAsString().equals(className.toLowerCase()); + } catch (Exception ignored) { + classLevel = LevelFinder.getLevelInfo("", 0); + } + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + context.drawItem(stack, x + 3, y + 5); + if (active) context.drawTexture(ACTIVE_TEXTURE, x + 3, y + 5, 0, 0, 16, 16, 16, 16); + + context.drawText(textRenderer, className + " " + classLevel.level, x + 31, y + 5, Color.WHITE.getRGB(), false); + Color fillColor = classLevel.level >= CLASS_CAP ? Color.MAGENTA : Color.GREEN; + context.drawGuiTexture(BAR_BACK, x + 30, y + 15, 75, 6); + RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 15, (int) (75 * classLevel.fill), 6, fillColor); + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java new file mode 100644 index 00000000..7c9206c0 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java @@ -0,0 +1,55 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.Map; + +public class DungeonFloorRunsWidget { + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/dungeons_body.png"); + + private static final String[] DUNGEONS = {"catacombs", "master_catacombs"}; + private JsonObject dungeonsStats; + + public DungeonFloorRunsWidget(JsonObject pProfile) { + try { + dungeonsStats = pProfile.getAsJsonObject("dungeons").getAsJsonObject("dungeon_types"); + } catch (Exception ignored) {} + } + + // TODO: Hovering on each floor should probably showcase best run times in a tooltip + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 110, 109, 110); + context.drawText(textRenderer, Text.literal("Floor Runs").formatted(Formatting.BOLD), x + 6, y + 4, Color.WHITE.getRGB(), true); + + int columnX = x + 4; + int elementY = y + 15; + for (String dungeon : DUNGEONS) { + JsonObject dungeonData; + try { + dungeonData = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions"); + for (Map.Entry<String, JsonElement> entry : dungeonData.entrySet()) { + if (entry.getKey().equals("total")) continue; + + String textToRender = String.format((dungeon.equals("catacombs") ? "§aF" : "§cM") + "%s§r %s", entry.getKey(), entry.getValue().getAsInt()); + context.drawText(textRenderer, textToRender, columnX + 2, elementY + 2, Color.WHITE.getRGB(), true); + + elementY += 11; + } + columnX += 52; + elementY = y + 26; + } catch (Exception e) { + return; + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java new file mode 100644 index 00000000..1a62a47b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.text.DecimalFormat; + +public class DungeonHeaderWidget { + private LevelFinder.LevelInfo classLevel; + private float classAvg; + + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final DecimalFormat DF = new DecimalFormat("#.##"); + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/dungeons_header.png"); + + public DungeonHeaderWidget(JsonObject playerProfile, String[] classes) { + try { + JsonObject DUNGEONS_PROFILE = playerProfile.getAsJsonObject("dungeons").getAsJsonObject("dungeon_types").getAsJsonObject("catacombs"); + this.classLevel = LevelFinder.getLevelInfo("Catacombs", DUNGEONS_PROFILE.get("experience").getAsLong()); + + float avg = 0; + JsonObject CLASS_DATA = playerProfile.getAsJsonObject("dungeons").getAsJsonObject("player_classes"); + for (String element : classes) { + avg += LevelFinder.getLevelInfo("Catacombs", CLASS_DATA.getAsJsonObject(element.toLowerCase()).get("experience").getAsLong()).level; + } + classAvg = avg/classes.length; + } catch (Exception ignored) { + this.classLevel = LevelFinder.getLevelInfo("", 0); + classAvg = 0; + } + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + + context.drawText(textRenderer, "§i§6§lCatacombs §r" + this.classLevel.level, x + 3, y + 4, Color.WHITE.getRGB(), true); + + context.drawText(textRenderer, "§eClass Average §r" + DF.format(this.classAvg), x + 3, y + 14, Color.WHITE.getRGB(), true); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java new file mode 100644 index 00000000..679cc575 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java @@ -0,0 +1,61 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +public class DungeonMiscStatsWidgets { + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png"); + private static final Identifier RUN_ICON = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/run_icon.png"); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final DecimalFormat DF = new DecimalFormat("#.##"); + private static final String[] DUNGEONS = {"catacombs", "master_catacombs"}; + + private final Map<String, Integer> dungeonRuns = new HashMap<>(); + private int secrets = 0; + private int totalRuns = 0; + + public DungeonMiscStatsWidgets(JsonObject pProfile) { + JsonObject DUNGEONS_DATA = pProfile.getAsJsonObject("dungeons"); + try { + secrets = DUNGEONS_DATA.get("secrets").getAsInt(); + + for (String dungeon : DUNGEONS) { + JsonObject dungeonData = DUNGEONS_DATA.getAsJsonObject("dungeon_types").getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions"); + int runs = 0; + for (Map.Entry<String, JsonElement> entry : dungeonData.entrySet()) { + String key = entry.getKey(); + if (key.equals("total")) continue; + runs += entry.getValue().getAsInt(); + } + dungeonRuns.put(dungeon, runs); + totalRuns += runs; + } + + } catch (Exception ignored) {} + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + context.drawItem(Ico.FEATHER, x + 2, y + 4); + + context.drawText(textRenderer, "Secrets " + secrets, x + 30, y + 4, Color.WHITE.getRGB(), true); + context.drawText(textRenderer, "Avg " + (totalRuns > 0 ? DF.format(secrets / (float) totalRuns) : 0) + "/Run", x + 30, y + 14, Color.WHITE.getRGB(), true); + + context.drawTexture(TEXTURE, x, y + 28, 0, 0, 109, 26, 109, 26); + context.drawTexture(RUN_ICON, x + 4, y + 33, 0, 0, 14, 16, 14, 16); + + context.drawText(textRenderer, "§aNormal §r" + dungeonRuns.getOrDefault("catacombs", 0), x + 30, y + 32, Color.WHITE.getRGB(), true); + context.drawText(textRenderer, "§cMaster §r" + dungeonRuns.getOrDefault("master_catacombs", 0), x + 30, y + 42, Color.WHITE.getRGB(), true); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java new file mode 100644 index 00000000..b1398661 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java @@ -0,0 +1,39 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.utils.ProfileUtils; +import net.minecraft.client.gui.DrawContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class DungeonsPage implements ProfileViewerPage { + public static final Logger LOGGER = LoggerFactory.getLogger(ProfileUtils.class); + private static final String[] CLASSES = {"Healer", "Mage", "Berserk", "Archer", "Tank"}; + + private final DungeonHeaderWidget dungeonHeaderWidget; + private final List<DungeonClassWidget> dungeonClassWidgetsList = new ArrayList<>(); + private final DungeonFloorRunsWidget dungeonFloorRunsWidget; + private final DungeonMiscStatsWidgets dungeonMiscStatsWidgets; + + public DungeonsPage(JsonObject pProfile) { + dungeonHeaderWidget = new DungeonHeaderWidget(pProfile, CLASSES); + dungeonFloorRunsWidget = new DungeonFloorRunsWidget(pProfile); + dungeonMiscStatsWidgets = new DungeonMiscStatsWidgets(pProfile); + for (String element : CLASSES) { + dungeonClassWidgetsList.add(new DungeonClassWidget(element, pProfile)); + } + } + + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + dungeonHeaderWidget.render(context, rootX, rootY); + dungeonFloorRunsWidget.render(context, rootX + 113, rootY + 56); + dungeonMiscStatsWidgets.render(context, rootX + 113, rootY); + for (int i = 0; i < dungeonClassWidgetsList.size(); i++) { + dungeonClassWidgetsList.get(i).render(context, rootX, rootY + 28 + i * 28); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java new file mode 100644 index 00000000..a2f7d9d6 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java @@ -0,0 +1,120 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.ItemLoader; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class Inventory implements ProfileViewerPage { + private static final Identifier TEXTURE = Identifier.of("textures/gui/container/generic_54.png"); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private final IntIntPair dimensions; + private final int itemsPerPage; + private final List<ItemStack> containerList; + private final String containerName; + private int activePage = 0; + private int totalPages = 1; + private final PaginationButton previousPage = new PaginationButton(this, -1000, 0, false); + private final PaginationButton nextPage = new PaginationButton(this, -1000, 0, true); + + public Inventory(String name, IntIntPair dimensions, JsonObject inventory) { + this(name, dimensions, inventory, new ItemLoader()); + } + + public Inventory(String name, IntIntPair dimensions, JsonObject inventory, ItemLoader itemLoader) { + containerName = name; + this.dimensions = dimensions; + itemsPerPage = dimensions.rightInt() * dimensions.leftInt(); + this.containerList = itemLoader.loadItems(inventory); + this.totalPages = (int) Math.ceil((double) containerList.size() / itemsPerPage); + } + + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + int rootYAdjusted = rootY + (26 - dimensions.leftInt() * 3); + context.drawTexture(TEXTURE, rootX, rootYAdjusted, 0, 0, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17); + context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootYAdjusted, 169, 0, 7, dimensions.leftInt() * 18 + 17); + context.drawTexture(TEXTURE, rootX, rootYAdjusted + dimensions.leftInt() * 18 + 17, 0, 215, dimensions.rightInt() * 18 + 7, 7); + context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootYAdjusted + dimensions.leftInt() * 18 + 17, 169, 215, 7, 7); + + context.drawText(textRenderer, containerName, rootX + 7, rootYAdjusted + 7, Color.DARK_GRAY.getRGB(), false); + + if (containerList.size() > itemsPerPage) { + previousPage.setX(rootX + 44); + previousPage.setY(rootY + 136); + previousPage.render(context, mouseX, mouseY, delta); + + context.drawCenteredTextWithShadow(textRenderer, "Page: " + (activePage + 1) + "/" + totalPages, rootX + 88, rootY + 140, Color.WHITE.getRGB()); + + nextPage.setX(rootX + 121); + nextPage.setY(rootY + 136); + nextPage.render(context, mouseX, mouseY, delta); + } + + int startIndex = activePage * itemsPerPage; + int endIndex = Math.min(startIndex + itemsPerPage, containerList.size()); + for (int i = 0; i < endIndex - startIndex; i++) { + if (containerList.get(startIndex + i) == ItemStack.EMPTY) continue; + int column = i % dimensions.rightInt(); + int row = i / dimensions.rightInt(); + + int x = rootX + 8 + column * 18; + int y = rootYAdjusted + 18 + row * 18; + context.drawItem(containerList.get(startIndex + i), x, y); + context.drawItemInSlot(textRenderer, containerList.get(startIndex + i), x, y); + + if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) { + List<Text> tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC); + context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); + } + } + } + + public void nextPage() { + if (activePage < totalPages - 1) { + activePage++; + } + } + + public void previousPage() { + if (activePage > 0) { + activePage--; + } + } + + @Override + public void markWidgetsAsVisible() { + nextPage.visible = true; + previousPage.visible = true; + nextPage.active = true; + previousPage.active = true; + } + + @Override + public void markWidgetsAsInvisible() { + nextPage.visible = false; + previousPage.visible = false; + nextPage.active = false; + previousPage.active = false; + } + + @Override + public List<ClickableWidget> getButtons() { + List<ClickableWidget> buttons = new ArrayList<>(); + buttons.add(nextPage); + buttons.add(previousPage); + return buttons; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java new file mode 100644 index 00000000..8b0cbefc --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java @@ -0,0 +1,113 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.BackpackItemLoader; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.PetsInventoryItemLoader; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.WardrobeInventoryItemLoader; +import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator; +import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.item.ItemStack; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class InventoryPage implements ProfileViewerPage { + private static final String[] INVENTORY_PAGES = {"Inventory", "Enderchest", "Backpack", "Wardrobe", "Pets", "Accessory Bag"}; + private static final int TOTAL_HEIGHT = 165; + private static final Map<String, ItemStack> ICON_MAP = Map.ofEntries( + Map.entry("Wardrobe", Ico.L_CHESTPLATE), + Map.entry("Inventory", Ico.CHEST), + Map.entry("Backpack", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzYyZjNiM2EwNTQ4MWNkZTc3MjQwMDA1YzBkZGNlZTFjMDY5ZTU1MDRhNjJjZTA5Nzc4NzlmNTVhMzkzOTYxNDYifX19")), + Map.entry("Pets", Ico.BONE), + Map.entry("Enderchest", Ico.E_CHEST), + Map.entry("Accessory Bag", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYxYTkxOGMwYzQ5YmE4ZDA1M2U1MjJjYjkxYWJjNzQ2ODkzNjdiNGQ4YWEwNmJmYzFiYTkxNTQ3MzA5ODVmZiJ9fX0=")) + ); + + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private final ProfileViewerPage[] inventorySubPages = new ProfileViewerPage[6]; + private final List<SubPageSelectButton> inventorySelectButtons = new ArrayList<>(); + private int activePage = 0; + + public InventoryPage(JsonObject pProfile) { + for (int i = 0; i < INVENTORY_PAGES.length; i++) { + inventorySelectButtons.add(new SubPageSelectButton(this, -100, 0, i, ICON_MAP.getOrDefault(INVENTORY_PAGES[i], Ico.BARRIER))); + } + + try { + JsonObject inventoryData = pProfile.getAsJsonObject("inventory"); + if (inventoryData == null) return; + inventorySubPages[0] = new PlayerInventory(inventoryData); + inventorySubPages[1] = new Inventory(INVENTORY_PAGES[1], IntIntPair.of(5, 9), inventoryData.getAsJsonObject("ender_chest_contents")); + inventorySubPages[2] = new Inventory(INVENTORY_PAGES[2], IntIntPair.of(5, 9), inventoryData.getAsJsonObject("backpack_contents"), new BackpackItemLoader()); + inventorySubPages[3] = new Inventory(INVENTORY_PAGES[3], IntIntPair.of(4, 9), inventoryData.getAsJsonObject("wardrobe_contents"), new WardrobeInventoryItemLoader(inventoryData)); + inventorySubPages[4] = new Inventory(INVENTORY_PAGES[4], IntIntPair.of(4, 9), pProfile, new PetsInventoryItemLoader()); + inventorySubPages[5] = new Inventory(INVENTORY_PAGES[5], IntIntPair.of(5, 9), inventoryData.getAsJsonObject("bag_contents").getAsJsonObject("talisman_bag")); + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error while loading inventory data: ", e); + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + int startingY = rootY + (TOTAL_HEIGHT - inventorySelectButtons.size() * 21) / 2; + for (int i = 0; i < inventorySelectButtons.size(); i++) { + inventorySelectButtons.get(i).setX(rootX); + inventorySelectButtons.get(i).setY(startingY + i * 21); + inventorySelectButtons.get(i).render(context, mouseX, mouseY, delta); + } + + if (inventorySubPages[activePage] == null) { + context.drawText(textRenderer, "No data...", rootX + 92, rootY + 72, Color.DARK_GRAY.getRGB(), false); + return; + } + + inventorySubPages[activePage].markWidgetsAsVisible(); + inventorySubPages[activePage].render(context, mouseX, mouseY, delta, rootX + 35, rootY + 6); + } + + public void onNavButtonClick(SubPageSelectButton clickedButton) { + if (inventorySubPages[activePage] != null) inventorySubPages[activePage].markWidgetsAsInvisible(); + for (SubPageSelectButton button : inventorySelectButtons) { + button.setToggled(false); + } + activePage = clickedButton.getIndex(); + clickedButton.setToggled(true); + } + + @Override + public List<ClickableWidget> getButtons() { + List<ClickableWidget> clickableWidgets = new ArrayList<>(inventorySelectButtons); + for (ProfileViewerPage page : inventorySubPages) { + if (page != null && page.getButtons() != null) clickableWidgets.addAll(page.getButtons()); + } + return clickableWidgets; + } + + @Override + public void markWidgetsAsVisible() { + if (inventorySubPages[activePage] != null) inventorySubPages[activePage].markWidgetsAsVisible(); + for (SubPageSelectButton button : inventorySelectButtons) { + button.visible = true; + button.active = true; + } + } + + @Override + public void markWidgetsAsInvisible() { + if (inventorySubPages[activePage] != null) inventorySubPages[activePage].markWidgetsAsInvisible(); + for (SubPageSelectButton button : inventorySelectButtons) { + button.visible = false; + button.active = false; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java new file mode 100644 index 00000000..1a725e1a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory; + +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class PaginationButton extends ClickableWidget { + private final ProfileViewerPage screen; + private final boolean isNextButton; + private final Identifier TEXTURE; + private final Identifier HIGHLIGHT; + + public PaginationButton(ProfileViewerPage screen, int x, int y, boolean isNextButton) { + super(x, y, 12, 17, Text.empty()); + this.screen = screen; + this.isNextButton = isNextButton; + if (isNextButton) { + TEXTURE = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_forward.png"); + HIGHLIGHT = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_forward_highlighted.png"); + } else { + TEXTURE = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_backward.png"); + HIGHLIGHT = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_backward_highlighted.png"); + } + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.drawTexture(TEXTURE, this.getX(), this.getY(), 0, 0, 12, 17, 12, 17); + if (isMouseOver(mouseX, mouseY)) context.drawTexture(HIGHLIGHT, this.getX(), this.getY(), 0, 0, 12, 17, 12, 17); + } + + @Override + public void onClick(double mouseX, double mouseY) { + if (isNextButton) { + screen.nextPage(); + } else { + screen.previousPage(); + } + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java new file mode 100644 index 00000000..b3389d39 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java @@ -0,0 +1,186 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory; + +import de.hysky.skyblocker.skyblock.PetCache; +import de.hysky.skyblocker.skyblock.itemlist.ItemFixerUpper; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.NEURepoManager; +import io.github.moulberry.repo.constants.PetNumbers; +import io.github.moulberry.repo.data.NEUItem; +import io.github.moulberry.repo.data.Rarity; +import io.github.moulberry.repo.util.PetId; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.LoreComponent; +import net.minecraft.component.type.ProfileComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtString; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_TEXTURE_PATTERN; +import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_UUID_PATTERN; + +public class Pet { + private final String name; + private final double xp; + private final String tier; + private final Optional<String> heldItem; + private final int level; + private final ItemStack icon; + + private static final Map<String, Integer> TIER_MAP = Map.of( + "COMMON", 0, "UNCOMMON", 1, "RARE", 2, "EPIC", 3, "LEGENDARY", 4, "MYTHIC", 5 + ); + + public Pet(PetCache.PetInfo petData) { + this.name = petData.type(); + this.xp = petData.exp(); + this.heldItem = petData.item(); + if ((heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST"))) { + this.tier = switch (petData.tier()) { + case "COMMON" -> "UNCOMMON"; + case "UNCOMMON" -> "RARE"; + case "RARE" -> "EPIC"; + case "EPIC" -> "LEGENDARY"; + case "LEGENDARY" -> "MYTHIC"; + default -> petData.tier(); + }; + } else { + this.tier = petData.tier(); + } + this.level = LevelFinder.getLevelInfo(this.name.equals("GOLDEN_DRAGON") ? "PET_GREG" : "PET_" + this.tier, (long) xp).level; + this.icon = createIcon(); + } + + public String getName() { return name; } + public long getXP() { return (long) xp; } + public int getTier() { return TIER_MAP.getOrDefault(tier, 0); } + public String getTierAsString() { return tier; } + public String getSkin() { return null; } + public int getLevel() { return level; } + public ItemStack getIcon() { return icon; } + + + private ItemStack createIcon() { + if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return Ico.BARRIER; + Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems(); + if (items == null) return Ico.BARRIER; + + String targetItemId = this.getName() + ";" + this.getTier(); + NEUItem item = items.values().stream() + .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(targetItemId)) + .findFirst().orElse(null); + + NEUItem petItem = null; + if (this.heldItem.isPresent()) { + petItem = items.values().stream() + .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(this.heldItem.get())) + .findFirst().orElse(null); + } + + return fromNEUItem(item, petItem); + } + + /** + * Converts NEU item data into an ItemStack. + * <p> This method converts NEU item data into a Pet by using the placeholder + * information from NEU-REPO and injecting the player's calculated pet stats into the lore and transforming + * the NBT Data into modern DataComponentTypes before returning the final ItemStack </p + * + * @param item The NEUItem representing the pet. + * @param helditem The NEUItem representing the held item, if any. + * @return The ItemStack representing the pet with all its properties set. + */ + private ItemStack fromNEUItem(NEUItem item, NEUItem helditem) { + if (item == null) return Ico.BARRIER; + List<Pair<String, String>> injectors = new ArrayList<>(createLoreReplacers(item.getSkyblockItemId(), helditem)); + Identifier itemId = Identifier.of(ItemFixerUpper.convertItemId(item.getMinecraftItemId(), item.getDamage())); + ItemStack stack = new ItemStack(Registries.ITEM.get(itemId)); + + NbtCompound customData = new NbtCompound(); + customData.put(ItemUtils.ID, NbtString.of(item.getSkyblockItemId())); + stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(injectData(item.getDisplayName(), injectors))); + + stack.set(DataComponentTypes.LORE, new LoreComponent( + item.getLore().stream().map(line -> injectData(line, injectors)) + .filter(line -> !line.contains("SKIP")).map(Text::of) + .collect(Collectors.toList()))); + + Matcher skullUuid = SKULL_UUID_PATTERN.matcher(item.getNbttag()); + Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag()); + if (skullUuid.find() && skullTexture.find()) { + UUID uuid = UUID.fromString(skullUuid.group(1)); + String textureValue = this.getSkin() == null ? skullTexture.group(1) : this.getSkin(); + stack.set(DataComponentTypes.PROFILE, new ProfileComponent( + Optional.of(item.getSkyblockItemId()), Optional.of(uuid), + ItemUtils.propertyMapWithTexture(textureValue))); + } + return stack; + } + + /** + * Generates a list of placeholder-replacement pairs for the itemName of a pet item. + * <p> This method uses the pet's data from the NEU repository and uses PetInfo to generate replacers, and optionally + * includes data about a held item. </p> + * + * @param itemSkyblockID The initial itemName string containing the pet's name and tier separated by a semicolon. + * @param helditem The NEUItem representing the held item, if any. + * @return A list of placeholder-replacement pairs to be used for injecting data into the pet item's itemName. + */ + private List<Pair<String, String>> createLoreReplacers(String itemSkyblockID, NEUItem helditem) { + List<Pair<String, String>> list = new ArrayList<>(); + Map<@PetId String, Map<Rarity, PetNumbers>> petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers(); + String petName = itemSkyblockID.split(";")[0]; + if (!itemSkyblockID.contains(";") || !petNums.containsKey(petName)) return list; + + Rarity rarity = Rarity.values()[Integer.parseInt(itemSkyblockID.split(";")[1])]; + try { + PetNumbers data = petNums.get(petName).get(rarity); + list.add(new Pair<>("\\{LVL\\}", String.valueOf(this.level))); + data.interpolatedStatsAtLevel(this.level).getStatNumbers().forEach((key, value) -> + list.add(new Pair<>("\\{" + key + "\\}", fixDecimals(value, true)))); + + List<Double> otherNumsMin = data.interpolatedStatsAtLevel(this.level).getOtherNumbers(); + for (int i = 0; i < otherNumsMin.size(); ++i) { + list.add(new Pair<>("\\{" + i + "\\}", fixDecimals(otherNumsMin.get(i), false))); + } + + list.add(new Pair<>("Right-click to add this pet to", + helditem != null ? "§r§6Held Item: " + helditem.getDisplayName() : "SKIP")); + list.add(new Pair<>("pet menu!", "SKIP")); + } catch (Exception e) { + if (petName.equals("GOLDEN_DRAGON")) { + list.add(new Pair<>("Golden Dragon", + "§r§7[Lvl " + this.level + "] " + "§6Golden Dragon Egg §c[Not Supported by NEU-Repo]")); + } + } + return list; + } + + private String injectData(String string, List<Pair<String, String>> injectors) { + for (Pair<String, String> injector : injectors) { + if (string.contains(injector.getLeft())) return injector.getRight(); + string = string.replaceAll(injector.getLeft(), injector.getRight()); + } + return string; + } + + private String fixDecimals(double num, boolean truncate) { + if (num % 1 == 0) return String.valueOf((int) num); + BigDecimal roundedNum = new BigDecimal(num).setScale(3, RoundingMode.HALF_UP); + return truncate && num > 1 ? String.valueOf(roundedNum.intValue()) + : roundedNum.stripTrailingZeros().toPlainString(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java new file mode 100644 index 00000000..26673693 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java @@ -0,0 +1,73 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.InventoryItemLoader; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.List; + +public class PlayerInventory implements ProfileViewerPage { + private static final Identifier TEXTURE = Identifier.of("textures/gui/container/generic_54.png"); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private final List<ItemStack> containerList; + + public PlayerInventory(JsonObject inventory) { + this.containerList = new InventoryItemLoader().loadItems(inventory); + } + + // Z-STACKING forces this nonsense of separating the Background texture and Item Drawing :( + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + drawContainerTextures(context, "Armour", rootX, rootY + 108, IntIntPair.of(1, 4)); + drawContainerTextures(context, "Inventory", rootX, rootY + 2, IntIntPair.of(4, 9)); + drawContainerTextures(context, "Equipment", rootX + 90, rootY + 108, IntIntPair.of(1, 4)); + + drawContainerItems(context, rootX, rootY + 108, IntIntPair.of(1, 4), 36, 40, mouseX, mouseY); + drawContainerItems(context, rootX, rootY + 2, IntIntPair.of(4, 9), 0, 36, mouseX, mouseY); + drawContainerItems(context, rootX + 90, rootY + 108, IntIntPair.of(1, 4), 40, containerList.size(), mouseX, mouseY); + } + + private void drawContainerTextures(DrawContext context, String containerName, int rootX, int rootY, IntIntPair dimensions) { + if (containerName.equals("Inventory")) { + context.drawTexture(TEXTURE, rootX, rootY + dimensions.leftInt() + 10, 0, 136, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17); + context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY, 169, 0, 7, dimensions.leftInt() * 18 + 21); + context.drawTexture(TEXTURE, rootX, rootY, 0, 0, dimensions.rightInt() * 18 + 7, 14); + context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY + dimensions.leftInt() * 18 + 21, 169, 215, 7, 7); + } else { + context.drawTexture(TEXTURE, rootX, rootY, 0, 0, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17); + context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY, 169, 0, 7, dimensions.leftInt() * 18 + 17); + context.drawTexture(TEXTURE, rootX, rootY + dimensions.leftInt() * 18 + 17, 0, 215, dimensions.rightInt() * 18 + 7, 7); + context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY + dimensions.leftInt() * 18 + 17, 169, 215, 7, 7); + } + + context.drawText(textRenderer, containerName, rootX + 7, rootY + 7, Color.DARK_GRAY.getRGB(), false); + } + + private void drawContainerItems(DrawContext context, int rootX, int rootY, IntIntPair dimensions, int startIndex, int endIndex, int mouseX, int mouseY) { + for (int i = 0; i < endIndex - startIndex; i++) { + if (containerList.get(startIndex + i) == ItemStack.EMPTY) continue; + int column = i % dimensions.rightInt(); + int row = i / dimensions.rightInt(); + + int x = rootX + 8 + column * 18; + int y = (rootY + 18 + row * 18) + (dimensions.leftInt() > 1 && row + 1 == dimensions.leftInt() ? 4 : 0); + + context.drawItem(containerList.get(startIndex + i), x, y); + context.drawItemInSlot(textRenderer, containerList.get(startIndex + i), x, y); + + if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) { + List<Text> tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC); + context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java new file mode 100644 index 00000000..99e728be --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class BackpackItemLoader extends ItemLoader { + @Override + public List<ItemStack> loadItems(JsonObject data) { + List<ItemStack> backpackItems = new ArrayList<>(); + + // Sort the data by keys numerically + List<Map.Entry<String, JsonElement>> sortedEntries = data.entrySet().stream() + .sorted((e1, e2) -> { + int key1 = Integer.parseInt(e1.getKey()); + int key2 = Integer.parseInt(e2.getKey()); + return Integer.compare(key1, key2); + }).toList(); + + for (int i = 0; i < sortedEntries.size(); i++) { + backpackItems.addAll(super.loadItems(sortedEntries.get(i).getValue().getAsJsonObject())); + int padding = (i + 1) * 45 % (backpackItems.isEmpty() ? 1 : backpackItems.size()); + for (int j = 0; j < padding; j++) { + backpackItems.add(ItemStack.EMPTY); + } + } + + return backpackItems; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java new file mode 100644 index 00000000..f73661a1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java @@ -0,0 +1,29 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class InventoryItemLoader extends ItemLoader { + + private static final String[] INVENTORIES = {"inv_contents", "inv_armor", "equipment_contents"}; + + @Override + public List<ItemStack> loadItems(JsonObject data) { + List<ItemStack> inventoryItems = new ArrayList<>(); + for (String inventory : INVENTORIES) { + List<ItemStack> inv = super.loadItems(data.getAsJsonObject(inventory)); + switch (inventory) { + case "inv_armor" -> inventoryItems.addAll(inv.reversed()); + case "inv_contents" -> { + inventoryItems.addAll(inv.subList(9,inv.size())); + inventoryItems.addAll(inv.subList(0, 9)); + } + default -> inventoryItems.addAll(inv); + } + } + return inventoryItems; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java new file mode 100644 index 00000000..9d9b1b07 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java @@ -0,0 +1,139 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import de.hysky.skyblocker.skyblock.PetCache; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.Pet; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.NEURepoManager; +import io.github.moulberry.repo.data.NEUItem; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.AttributeModifiersComponent; +import net.minecraft.component.type.DyedColorComponent; +import net.minecraft.component.type.LoreComponent; +import net.minecraft.component.type.ProfileComponent; +import net.minecraft.datafixer.fix.ItemIdFix; +import net.minecraft.datafixer.fix.ItemInstanceTheFlatteningFix; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.io.ByteArrayInputStream; +import java.util.*; +import java.util.stream.Collectors; + +import static de.hysky.skyblocker.skyblock.itemlist.ItemRepository.getItemStack; + +public class ItemLoader { + + public List<ItemStack> loadItems(JsonObject data) { + NbtList containerContent = decompress(data); + List<ItemStack> itemList = new ArrayList<>(); + + for (int i = 0; i < containerContent.size(); i++) { + if (containerContent.getCompound(i).getInt("id") == 0) { + itemList.add(ItemStack.EMPTY); + continue; + } + + NbtCompound nbttag = containerContent.getCompound(i).getCompound("tag"); + String internalName = nbttag.getCompound("ExtraAttributes").getString("id"); + if (internalName.equals("PET")) { + NbtCompound extraAttributes = nbttag .getCompound("ExtraAttributes"); + PetCache.PetInfo petInfo = PetCache.PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(extraAttributes.getString("petInfo"))).getOrThrow(); + Pet pet = new Pet(petInfo); + itemList.add(pet.getIcon()); + continue; + } + + Identifier itemId = identifierFromOldId(containerContent.getCompound(i).getInt("id"), containerContent.getCompound(i).getInt("Damage")); + ItemStack stack = itemId.toString().equals("minecraft:air") ? getItemStack(internalName) : new ItemStack(Registries.ITEM.get(itemId)); + + if (stack == null || stack.isEmpty() || stack.getItem().equals(Ico.BARRIER.getItem())) { + // Last ditch effort to find item in NEU REPO + Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems(); + stack = items.values().stream() + .filter(j -> Formatting.strip(j.getSkyblockItemId()).equals(Formatting.strip(internalName).replace(":", "-"))) + .findFirst() + .map(NEUItem::getSkyblockItemId) + .map(ItemRepository::getItemStack) + .orElse(Ico.BARRIER.copy()); + + + if (stack.getName().getString().contains("barrier")) { + stack.set(DataComponentTypes.CUSTOM_NAME, Text.literal("Err: " + internalName)); + itemList.add(stack); + continue; + } + } + + // Custom Data + NbtCompound customData = new NbtCompound(); + + // Add Skyblock Item Id + customData.put(ItemUtils.ID, NbtString.of(internalName)); + + + // Item Name + stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(nbttag.getCompound("display").getString("Name"))); + + // Lore + NbtList loreList = nbttag.getCompound("display").getList("Lore", 8); + stack.set(DataComponentTypes.LORE, new LoreComponent(loreList.stream() + .map(NbtElement::asString) + .map(Text::literal) + .collect(Collectors.toList()))); + + // add skull texture + NbtList texture = nbttag.getCompound("SkullOwner").getCompound("Properties").getList("textures", 10); + if (!texture.isEmpty()) { + stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of(internalName), Optional.of(UUID.fromString(nbttag.getCompound("SkullOwner").get("Id").asString())), ItemUtils.propertyMapWithTexture(texture.getCompound(0).getString("Value")))); + } + + // Colour + if (nbttag.getCompound("display").contains("color")) { + int color = nbttag.getCompound("display").getInt("color"); + stack.set(DataComponentTypes.DYED_COLOR, new DyedColorComponent(color, false)); + } + + // add enchantment glint + if (nbttag.getKeys().contains("ench")) { + stack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); + } + + // Hide weapon damage and other useless info + stack.set(DataComponentTypes.ATTRIBUTE_MODIFIERS, new AttributeModifiersComponent(List.of(), false)); + + // Set Count + stack.setCount(containerContent.getCompound(i).getInt("Count")); + + itemList.add(stack); + } + + return itemList; + } + + private static Identifier identifierFromOldId(int id, int damage) { + try { + return damage != 0 ? Identifier.of(ItemInstanceTheFlatteningFix.getItem(ItemIdFix.fromId(id), damage)) : Identifier.of(ItemIdFix.fromId(id)); + } catch (Exception e) { + return Identifier.of("air"); + } + } + + private static NbtList decompress(JsonObject data) { + try { + return NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(data.get("data").getAsString())), NbtSizeTracker.ofUnlimitedBytes()).getList("i", NbtElement.COMPOUND_TYPE); + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to decompress item data", e); + } + return null; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java new file mode 100644 index 00000000..cd3b7a26 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java @@ -0,0 +1,42 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import de.hysky.skyblocker.skyblock.PetCache; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.Pet; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class PetsInventoryItemLoader extends ItemLoader { + private static final List<String> TIER_ORDER = List.of("MYTHIC", "LEGENDARY", "EPIC", "RARE", "UNCOMMON", "COMMON"); + + @Override + public List<ItemStack> loadItems(JsonObject data) { + List<Pet> petList = new ArrayList<>(); + try { + JsonObject petsData = data.getAsJsonObject("pets_data"); + if (petsData != null && petsData.has("pets")) { + for (var petElement : petsData.get("pets").getAsJsonArray()) { + PetCache.PetInfo petInfo = PetCache.PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(petElement.toString())).getOrThrow(); + petList.add(new Pet(petInfo)); + } + } + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to load pets", e); + } + + // Sort pets by tier (in reverse order) and level (in reverse order) + petList.sort(Comparator.comparingInt((Pet pet) -> TIER_ORDER.indexOf(pet.getTierAsString())).reversed().thenComparingInt(Pet::getLevel).reversed()); + + List<ItemStack> itemList = new ArrayList<>(); + for (Pet pet : petList) { + itemList.add(pet.getIcon()); + } + return itemList; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java new file mode 100644 index 00000000..9d434726 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java @@ -0,0 +1,40 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class WardrobeInventoryItemLoader extends ItemLoader { + private final int activeSlot; + private final JsonObject activeArmorSet; + + public WardrobeInventoryItemLoader(JsonObject inventory) { + this.activeSlot = inventory.get("wardrobe_equipped_slot").getAsInt(); + this.activeArmorSet = inventory.get("inv_armor").getAsJsonObject(); + } + + @Override + public List<ItemStack> loadItems(JsonObject data) { + List<ItemStack> itemList = new ArrayList<>(); + + try { + itemList.addAll(super.loadItems(data)); + if (activeSlot != -1) { + List<ItemStack> activeArmour = super.loadItems(activeArmorSet).reversed(); + for (int i = 0; i < 4; i++) { + int baseIndex = activeSlot % 9; + int page = activeSlot / 9; + int slotIndex = (page * 36) + (i * 9) + baseIndex - 1; + itemList.set(slotIndex, activeArmour.get(i)); + } + } + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to load wardrobe items", e); + } + + return itemList; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java new file mode 100644 index 00000000..3a3870f3 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java @@ -0,0 +1,95 @@ +package de.hysky.skyblocker.skyblock.profileviewer.skills; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.Map; + +public class SkillWidget { + private final String SKILL_NAME; + private final LevelFinder.LevelInfo SKILL_LEVEL; + + private static final Identifier BAR_FILL = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_fill"); + private static final Identifier BAR_BACK = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_back"); + + private final ItemStack stack; + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final Map<String, ItemStack> SKILL_LOGO = Map.ofEntries( + Map.entry("Combat", Ico.STONE_SWORD), + Map.entry("Farming", Ico.GOLDEN_HOE), + Map.entry("Mining", Ico.STONE_PICKAXE), + Map.entry("Foraging", Ico.JUNGLE_SAPLING), + Map.entry("Fishing", Ico.FISH_ROD), + Map.entry("Enchanting", Ico.ENCHANTING_TABLE), + Map.entry("Alchemy", Ico.BREWING_STAND), + Map.entry("Taming", Ico.SPAWN_EGG), + Map.entry("Carpentry", Ico.CRAFTING_TABLE), + Map.entry("Catacombs", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")), + Map.entry("Runecraft", Ico.MAGMA_CREAM), + Map.entry("Social", Ico.EMERALD) + ); + private static final Map<String, Integer> SKILL_CAP = Map.ofEntries( + Map.entry("Combat", 60), + Map.entry("Farming", 60), + Map.entry("Mining", 60), + Map.entry("Foraging", 50), + Map.entry("Fishing", 50), + Map.entry("Enchanting", 60), + Map.entry("Alchemy", 50), + Map.entry("Taming", 60), + Map.entry("Carpentry", 50), + Map.entry("Catacombs", 50), + Map.entry("Runecraft", 25), + Map.entry("Social", 25) + ); + private static final Map<String, Integer> SOFT_SKILL_CAP = Map.of( + "Taming", 50, + "Farming", 50 + ); + + private static final Map<String, Integer> INFINITE = Map.of( + "Catacombs", 0 + ); + + public SkillWidget(String skill, long xp, int playerCap) { + this.SKILL_NAME = skill; + this.SKILL_LEVEL = LevelFinder.getLevelInfo(skill, xp); + if (SKILL_LEVEL.level >= SKILL_CAP.get(skill) && !INFINITE.containsKey(skill)) { + SKILL_LEVEL.fill = 1; + SKILL_LEVEL.level = SKILL_CAP.get(skill); + } + + this.stack = SKILL_LOGO.getOrDefault(skill, Ico.BARRIER); + if (playerCap != -1) { + this.SKILL_LEVEL.level = Math.min(SKILL_LEVEL.level, (SOFT_SKILL_CAP.get(this.SKILL_NAME) + playerCap)); + } + + } + + public void render(DrawContext context, int x, int y) { + context.drawItem(this.stack, x + 3, y + 2); + context.drawText(textRenderer, SKILL_NAME + " " + SKILL_LEVEL.level, x + 31, y + 2, Color.white.hashCode(), false); + + Color fillColor = Color.green; + if (SKILL_LEVEL.level >= SKILL_CAP.get(SKILL_NAME)) { + fillColor = Color.MAGENTA; + } + + if ((SOFT_SKILL_CAP.containsKey(SKILL_NAME) && SKILL_LEVEL.level > SOFT_SKILL_CAP.get(SKILL_NAME)) && SKILL_LEVEL.level < SKILL_CAP.get(SKILL_NAME) && SKILL_LEVEL.fill == 1 || + (SKILL_NAME.equals("Taming") && SKILL_LEVEL.level >= SOFT_SKILL_CAP.get(SKILL_NAME))) { + fillColor = Color.YELLOW; + } + + context.drawGuiTexture(BAR_BACK, x + 30, y + 12, 75, 6); + RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 12, (int) (75 * SKILL_LEVEL.fill), 6, fillColor); + } +}
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java new file mode 100644 index 00000000..c331bbdd --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java @@ -0,0 +1,93 @@ +package de.hysky.skyblocker.skyblock.profileviewer.skills; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +import java.util.ArrayList; +import java.util.List; + +public class SkillsPage implements ProfileViewerPage { + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png"); + private static final String[] SKILLS = {"Combat", "Mining", "Farming", "Foraging", "Fishing", "Enchanting", "Alchemy", "Taming", "Carpentry", "Catacombs", "Runecraft", "Social"}; + private static final int ROW_GAP = 28; + + private final JsonObject HYPIXEL_PROFILE; + private final JsonObject PLAYER_PROFILE; + + private final List<SkillWidget> skillWidgets = new ArrayList<>(); + private JsonObject skills; + + public SkillsPage(JsonObject hProfile, JsonObject pProfile) { + this.HYPIXEL_PROFILE = hProfile; + this.PLAYER_PROFILE = pProfile; + + try { + this.skills = this.PLAYER_PROFILE.getAsJsonObject("player_data").getAsJsonObject("experience"); + for (String skill : SKILLS) { + skillWidgets.add(new SkillWidget(skill, getSkillXP("SKILL_" + skill.toUpperCase()), getSkillCap(skill))); + } + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error creating widgets.", e); + } + } + + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + int column2 = rootX + 113; + for (int i = 0; i < skillWidgets.size(); i++) { + int x = (i < 6) ? rootX : column2; + int y = rootY + (i % 6) * ROW_GAP; + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + skillWidgets.get(i).render(context, x, y + 3); + } + } + + private int getSkillCap(String skill) { + try { + return switch (skill) { + case "Farming" -> this.PLAYER_PROFILE.getAsJsonObject("jacobs_contest").getAsJsonObject("perks").get("farming_level_cap").getAsInt(); + default -> -1; + }; + } catch (Exception e) { + return 0; + } + } + + private long getSkillXP(String skill) { + try { + return switch (skill) { + case "SKILL_CATACOMBS" -> getCatacombsXP(); + case "SKILL_SOCIAL" -> getCoopSocialXP(); + case "SKILL_RUNECRAFT" -> this.skills.get("SKILL_RUNECRAFTING").getAsLong(); + default -> this.skills.get(skill).getAsLong(); + }; + } catch (Exception e) { + return 0; + } + } + + private long getCatacombsXP() { + try { + JsonObject dungeonSkills = this.PLAYER_PROFILE.getAsJsonObject("dungeons").getAsJsonObject("dungeon_types"); + return dungeonSkills.getAsJsonObject("catacombs").get("experience").getAsLong(); + } catch (Exception e) { + return 0; + } + } + + private long getCoopSocialXP() { + long socialXP = 0; + JsonObject members = HYPIXEL_PROFILE.getAsJsonObject("members"); + for (String memberId : members.keySet()) { + try { + socialXP += members.getAsJsonObject(memberId).getAsJsonObject("player_data").getAsJsonObject("experience").get("SKILL_SOCIAL").getAsLong(); + } catch (Exception e) { + ProfileViewerScreen.LOGGER.warn("[Skyblocker Profile Viewer] Error calculating co-op social xp", e); + } + } + return socialXP; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java new file mode 100644 index 00000000..a9c05c11 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java @@ -0,0 +1,93 @@ +package de.hysky.skyblocker.skyblock.profileviewer.slayers; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.Map; + +public class SlayerWidget { + private final String slayerName; + private final LevelFinder.LevelInfo slayerLevel; + private JsonObject slayerData = null; + + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png"); + private static final Identifier BAR_FILL = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_fill"); + private static final Identifier BAR_BACK = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_back"); + private final Identifier item; + private final ItemStack drop; + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final Map<String, Identifier> HEAD_ICON = Map.ofEntries( + Map.entry("Zombie", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/zombie.png")), + Map.entry("Spider", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/spider.png")), + Map.entry("Wolf", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/wolf.png")), + Map.entry("Enderman", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/enderman.png")), + Map.entry("Vampire", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/vampire.png")), + Map.entry("Blaze", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/blaze.png")) + ); + + private static final Map<String, ItemStack> DROP_ICON = Map.ofEntries( + Map.entry("Zombie", Ico.FLESH), + Map.entry("Spider", Ico.STRING), + Map.entry("Wolf", Ico.MUTTON), + Map.entry("Enderman", Ico.E_PEARL), + Map.entry("Vampire", Ico.REDSTONE), + Map.entry("Blaze", Ico.B_POWDER) + ); + + public SlayerWidget(String slayer, long xp, JsonObject playerProfile) { + this.slayerName = slayer; + this.slayerLevel = LevelFinder.getLevelInfo(slayer, xp); + this.item = HEAD_ICON.get(slayer); + this.drop = DROP_ICON.getOrDefault(slayer, Ico.BARRIER); + try { + this.slayerData = playerProfile.getAsJsonObject("slayer").getAsJsonObject("slayer_bosses").getAsJsonObject(this.slayerName.toLowerCase()); + } catch (Exception ignored) {} + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + context.drawTexture(this.item, x + 1, y + 3, 0, 0, 20, 20, 20, 20); + context.drawText(textRenderer, slayerName + " " + slayerLevel.level, x + 31, y + 5, Color.white.hashCode(), false); + + int col2 = x + 113; + context.drawTexture(TEXTURE, col2, y, 0, 0, 109, 26, 109, 26); + context.drawItem(this.drop, col2 + 3, y + 5); + context.drawText(textRenderer, "§aKills: §r" + findTotalKills(), col2 + 30, y + 4, Color.white.hashCode(), true); + context.drawText(textRenderer, findTopTierKills(), findTopTierKills().equals("No Data") ? col2 + 30 : col2 + 29, y + 15, Color.white.hashCode(), true); + + context.drawGuiTexture(BAR_BACK, x + 30, y + 15, 75, 6); + Color fillColor = slayerLevel.fill == 1 ? Color.MAGENTA : Color.green; + RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 15, (int) (75 * slayerLevel.fill), 6, fillColor); + } + + private int findTotalKills() { + try { + int totalKills = 0; + for (String key : this.slayerData.keySet()) { + if (key.startsWith("boss_kills_tier_")) totalKills += this.slayerData.get(key).getAsInt(); + } + return totalKills; + } catch (Exception e) { + return 0; + } + } + + private String findTopTierKills() { + try { + for (int tier = 4; tier >= 0; tier--) { + String key = "boss_kills_tier_" + tier; + if (this.slayerData.has(key)) return "§cT" + (tier + 1) + " Kills: §r" + this.slayerData.get(key).getAsInt(); + } + } catch (Exception ignored) {} + return "No Data"; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java new file mode 100644 index 00000000..08e2ca06 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java @@ -0,0 +1,41 @@ +package de.hysky.skyblocker.skyblock.profileviewer.slayers; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import net.minecraft.client.gui.DrawContext; + +import java.util.ArrayList; +import java.util.List; + +public class SlayersPage implements ProfileViewerPage { + private static final String[] SLAYERS = {"Zombie", "Spider", "Wolf", "Enderman", "Vampire", "Blaze"}; + private static final int ROW_GAP = 28; + + private final List<SlayerWidget> slayerWidgets = new ArrayList<>(); + + public SlayersPage(JsonObject pProfile) { + try { + for (String slayer : SLAYERS) { + slayerWidgets.add(new SlayerWidget(slayer, getSlayerXP(slayer.toLowerCase(), pProfile), pProfile)); + } + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error creating slayer widgets", e); + } + } + + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + for (int i = 0; i < slayerWidgets.size(); i++) { + slayerWidgets.get(i).render(context, rootX, rootY + i * ROW_GAP); + } + } + + private long getSlayerXP(String slayer, JsonObject pProfile) { + try { + return pProfile.getAsJsonObject("slayer").getAsJsonObject("slayer_bosses") + .getAsJsonObject(slayer).get("xp").getAsLong(); + } catch (Exception e) { + return 0; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java new file mode 100644 index 00000000..b52fd579 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java @@ -0,0 +1,307 @@ +package de.hysky.skyblocker.skyblock.profileviewer.utils; + +import java.util.ArrayList; +import java.util.List; + +public class LevelFinder { + public static class LevelInfo { + public long xp; + public int level; + public double fill; + + public LevelInfo(long xp, int level) { + this.xp = xp; + this.level = level; + } + + public LevelInfo(int level, double fill) { + this.level = level; + this.fill = fill; + } + } + + private static final long CATA_XP_PER_LEVEL = 200_000_000; + private static final List<LevelInfo> GENERIC_SKILL_BOUNDARIES = createGenericSkillBoundaries(); + private static final List<LevelInfo> CATACOMBS_SKILL_BOUNDARIES = createCatacombsSkillBoundaries(); + private static final List<LevelInfo> RUNECRAFT_SKILL_BOUNDARIES = createRunecraftSkillBoundaries(); + private static final List<LevelInfo> SOCIAL_SKILL_BOUNDARIES = createSocialSkillBoundaries(); + + private static final List<LevelInfo> COMMON_PET_BOUNDARIES = createCommonPetBoundaries(); + private static final List<LevelInfo> UNCOMMON_PET_BOUNDARIES = createUncommonPetBoundaries(); + private static final List<LevelInfo> RARE_PET_BOUNDARIES = createRarePetBoundaries(); + private static final List<LevelInfo> EPIC_PET_BOUNDARIES = createEpicPetBoundaries(); + private static final List<LevelInfo> LEGENDARY_PET_BOUNDARIES = createLegendaryPetBoundaries(); + + + private static final List<LevelInfo> GENERIC_SLAYER_BOUNDARIES = createGenericSlayerBoundaries(); + private static final List<LevelInfo> VAMPIRE_SLAYER_BOUNDARIES = createVampireSlayerBoundaries(); + + private static List<LevelInfo> createGenericSkillBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 50L, 175L, 375L, 675L, 1175L, 1925L, 2925L, 4425L, 6425L, + 9925L, 14925L, 22425L, 32425L, 47425L, 67425L, 97425L, 147425L, + 222425L, 322425L, 522425L, 822425L, 1222425L, 1722425L, 2322425L, + 3022425L, 3822425L, 4722425L, 5722425L, 6822425L, 8022425L, + 9322425L, 10722425L, 12222425L, 13822425L, 15522425L, 17322425L, + 19222425L, 21222425L, 23322425L, 25522425L, 27822425L, 30222425L, + 32722425L, 35322425L, 38072425L, 40972425L, 44072425L, 47472425L, + 51172425L, 55172425L, 59472425L, 64072425L, 68972425L, 74172425L, + 79672425L, 85472425L, 91572425L, 97972425L, 104672425L, 111672425L + }; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + return boundaries; + } + + private static List<LevelInfo> createCatacombsSkillBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 50L, 125L, 235L, 395L, 625L, 955L, 1425L, 2095L, 3045L, + 4385L, 6275L, 8940L, 12700L, 17960L, 25340L, 35640L, 50040L, + 70040L, 97640L, 135640L, 188140L, 259640L, 356640L, 488640L, + 668640L, 911640L, 1239640L, 1684640L, 2284640L, 3084640L, + 4149640L, 5559640L, 7459640L, 9959640L, 13259640L, 17559640L, + 23159640L, 30359640L, 39359640L, 51359640L, 66359640L, 85359640L, + 109559640L, 139559640L, 177559640L, 225559640L, 295559640L, + 360559640L, 453559640L, 569809640L + }; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + return boundaries; + } + + private static List<LevelInfo> createRunecraftSkillBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 50L, 150L, 275L, 435L, 635L, 885L, 1200L, 1600L, 2100L, + 2725L, 3150L, 4510L, 5760L, 7325L, 9325L, 11825L, 14950L, + 18950L, 23950L, 30200L, 38050L, 47850L, 60100L, 75400L, 94500L + }; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + return boundaries; + } + + private static List<LevelInfo> createSocialSkillBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 50L, 150L, 300L, 550L, 1050L, 1800L, 2800L, 4050L, 5550L, + 7550L, 10050L, 13050L, 16800L, 21300L, 27300L, 35300L, 45300L, + 57800L, 72800L, 92800L, 117800L, 147800L, 182800L, 222800L, + 272800L + }; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + return boundaries; + } + + private static List<LevelInfo> createGenericSlayerBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = {0L, 5L, 15L, 200L, 1000L, 5000L,20000L,100000L,400000L,1000000L}; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + return boundaries; + } + + private static List<LevelInfo> createVampireSlayerBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = {0L, 20L, 75L, 240L, 840L, 2400L}; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + + return boundaries; + } + + private static List<LevelInfo> createCommonPetBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 0L, 100L, 210L, 330L, 460L, 605L, 765L, 940L, 1130L, 1340L, 1570L, 1820L, 2095L, + 2395L, 2725L, 3085L, 3485L, 3925L, 4415L, 4955L, 5555L, 6215L, 6945L, 7745L, + 8625L, 9585L, 10635L, 11785L, 13045L, 14425L, 15935L, 17585L, 19385L, 21345L, + 23475L, 25785L, 28285L, 30985L, 33905L, 37065L, 40485L, 44185L, 48185L, 52535L, + 57285L, 62485L, 68185L, 74485L, 81485L, 89285L, 97985L, 107685L, 118485L, 130485L, + 143785L, 158485L, 174685L, 192485L, 211985L, 233285L, 256485L, 281685L, 309085L, + 338885L, 371285L, 406485L, 444685L, 486085L, 530885L, 579285L, 631485L, 687685L, + 748085L, 812885L, 882285L, 956485L, 1035685L, 1120385L, 1211085L, 1308285L, + 1412485L, 1524185L, 1643885L, 1772085L, 1909285L, 2055985L, 2212685L, 2380385L, + 2560085L, 2752785L, 2959485L, 3181185L, 3418885L, 3673585L, 3946285L, 4237985L, + 4549685L, 4883385L, 5241085L, 5624785L + }; + + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + + return boundaries; + } + + private static List<LevelInfo> createUncommonPetBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 0L, 175L, 365L, 575L, 805L, 1055L, 1330L, 1630L, 1960L, 2320L, 2720L, 3160L, + 3650L, 4190L, 4790L, 5450L, 6180L, 6980L, 7860L, 8820L, 9870L, 11020L, 12280L, + 13660L, 15170L, 16820L, 18620L, 20580L, 22710L, 25020L, 27520L, 30220L, 33140L, + 36300L, 39720L, 43420L, 47420L, 51770L, 56520L, 61720L, 67420L, 73720L, 80720L, + 88520L, 97220L, 106920L, 117720L, 129720L, 143020L, 157720L, 173920L, 191720L, + 211220L, 232520L, 255720L, 280920L, 308320L, 338120L, 370520L, 405720L, 443920L, + 485320L, 530120L, 578520L, 630720L, 686920L, 747320L, 812120L, 881520L, 955720L, + 1034920L, 1119620L, 1210320L, 1307520L, 1411720L, 1523420L, 1643120L, 1771320L, + 1908520L, 2055220L, 2211920L, 2379620L, 2559320L, 2752020L, 2958720L, 3180420L, + 3418120L, 3672820L, 3945520L, 4237220L, 4548920L, 4882620L, 5240320L, 5624020L, + 6035720L, 6477420L, 6954120L, 7470820L, 8032520L, 8644220L + }; + + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + + return boundaries; + } + + private static List<LevelInfo> createRarePetBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 0L, 275L, 575L, 905L, 1265L, 1665L, 2105L, 2595L, 3135L, 3735L, 4395L, 5125L, + 5925L, 6805L, 7765L, 8815L, 9965L, 11225L, 12605L, 14115L, 15765L, 17565L, 19525L, + 21655L, 23965L, 26465L, 29165L, 32085L, 35245L, 38665L, 42365L, 46365L, 50715L, + 55465L, 60665L, 66365L, 72665L, 79665L, 87465L, 96165L, 105865L, 116665L, 128665L, + 141965L, 156665L, 172865L, 190665L, 210165L, 231465L, 254665L, 279865L, 307265L, + 337065L, 369465L, 404665L, 442865L, 484265L, 529065L, 577465L, 629665L, 685865L, + 746265L, 811065L, 880465L, 954665L, 1033865L, 1118565L, 1209265L, 1306465L, + 1410665L, 1522365L, 1642065L, 1770265L, 1907465L, 2054165L, 2210865L, 2378565L, + 2558265L, 2750965L, 2957665L, 3179365L, 3417065L, 3671765L, 3944465L, 4236165L, + 4547865L, 4881565L, 5239265L, 5622965L, 6034665L, 6476365L, 6953065L, 7469765L, + 8031465L, 8643165L, 9309865L, 10036565L, 10828265L, 11689965L, 12626665L + }; + + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + + return boundaries; + } + + private static List<LevelInfo> createEpicPetBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + long[] cumulativeXp = { + 0L, 0L, 440L, 930L, 1470L, 2070L, 2730L, 3460L, 4260L, 5140L, 6100L, 7150L, 8300L, + 9560L, 10940L, 12450L, 14100L, 15900L, 17860L, 19990L, 22300L, 24800L, 27500L, 30420L, + 33580L, 37000L, 40700L, 44700L, 49050L, 53800L, 59000L, 64700L, 71000L, 78000L, 85800L, + 94500L, 104200L, 115000L, 127000L, 140300L, 155000L, 171200L, 189000L, 208500L, 229800L, + 253000L, 278200L, 305600L, 335400L, 367800L, 403000L, 441200L, 482600L, 527400L, 575800L, + 628000L, 684200L, 744600L, 809400L, 878800L, 953000L, 1032200L, 1116900L, 1207600L, 1304800L, + 1409000L, 1520700L, 1640400L, 1768600L, 1905800L, 2052500L, 2209200L, 2376900L, 2556600L, + 2749300L, 2956000L, 3177700L, 3415400L, 3670100L, 3942800L, 4234500L, 4546200L, 4879900L, + 5237600L, 5621300L, 6033000L, 6474700L, 6951400L, 7468100L, 8029800L, 8641500L, 9308200L, + 10034900L, 10826600L, 11688300L, 12625000L, 13641700L, 14743400L, 15935100L, 17221800L, + 18608500L + }; + + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + + return boundaries; + } + + private static List<LevelInfo> createLegendaryPetBoundaries() { + List<LevelInfo> boundaries = new ArrayList<>(); + Long[] cumulativeXp = { + 0L, 0L, 660L, 1390L, 2190L, 3070L, 4030L, 5080L, 6230L, 7490L, + 8870L, 10380L, 12030L, 13830L, 15790L, 17920L, 20230L, 22730L, + 25430L, 28350L, 31510L, 34930L, 38630L, 42630L, 46980L, 51730L, + 56930L, 62630L, 68930L, 75930L, 83730L, 92430L, 102130L, 112930L, + 124930L, 138230L, 152930L, 169130L, 186930L, 206430L, 227730L, + 250930L, 276130L, 303530L, 333330L, 365730L, 400930L, 439130L, + 480530L, 525330L, 573730L, 625930L, 682130L, 742530L, 807330L, + 876730L, 950930L, 1030130L, 1114830L, 1205530L, 1302730L, 1406930L, + 1518630L, 1638330L, 1766530L, 1903730L, 2050430L, 2207130L, 2374830L, + 2554530L, 2747230L, 2953930L, 3175630L, 3413330L, 3668030L, 3940730L, + 4232430L, 4544130L, 4877830L, 5235530L, 5619230L, 6030930L, 6472630L, + 6949330L, 7466030L, 8027730L, 8639430L, 9306130L, 10032830L, 10824530L, + 11686230L, 12622930L, 13639630L, 14741330L, 15933030L, 17219730L, 18606430L, + 20103130L, 21719830L, 23466530L, 25353230L, 25353230L, 25358785L, 27245485L, + 29132185L, 31018885L, 32905585L, 34792285L, 36678985L, 38565685L, 40452385L, + 42339085L, 44225785L, 46112485L, 47999185L, 49885885L, 51772585L, 53659285L, + 55545985L, 57432685L, 59319385L, 61206085L, 63092785L, 64979485L, 66866185L, + 68752885L, 70639585L, 72526285L, 74412985L, 76299685L, 78186385L, 80073085L, + 81959785L, 83846485L, 85733185L, 87619885L, 89506585L, 91393285L, 93279985L, + 95166685L, 97053385L, 98940085L, 100826785L, 102713485L, 104600185L, 106486885L, + 108373585L, 110260285L, 112146985L, 114033685L, 115920385L, 117807085L, 119693785L, + 121580485L, 123467185L, 125353885L, 127240585L, 129127285L, 131013985L, 132900685L, + 134787385L, 136674085L, 138560785L, 140447485L, 142334185L, 144220885L, 146107585L, + 147994285L, 149880985L, 151767685L, 153654385L, 155541085L, 157427785L, 159314485L, + 161201185L, 163087885L, 164974585L, 166861285L, 168747985L, 170634685L, 172521385L, + 174408085L, 176294785L, 178181485L, 180068185L, 181954885L, 183841585L, 185728285L, + 187614985L, 189501685L, 191388385L, 193275085L, 195161785L, 197048485L, 198935185L, + 200821885L, 202708585L, 204595285L, 206481985L, 208368685L, 210255385L + }; + for (int i = 0; i < cumulativeXp.length; i++) { + boundaries.add(new LevelInfo(cumulativeXp[i], i)); + } + + return boundaries; + } + + public static LevelInfo getLevelInfo(String name, long xp) { + List<LevelInfo> boundaries = getLevelBoundaries(name, xp); + for (int i = boundaries.size() - 1; i >= 0 ; i--) { + if (xp >= boundaries.get(i).xp) { + double fill; + if (i < boundaries.getLast().level) { + double currentLevelXP = boundaries.get(i).xp; + double nextLevelXP = boundaries.get(i + 1).xp; + double levelXPRange = nextLevelXP - currentLevelXP; + double xpInCurrentLevel = xp - currentLevelXP; + fill = xpInCurrentLevel / levelXPRange; + } else { + fill = 1.0; + } + return new LevelInfo(boundaries.get(i).level, fill); + } + } + return new LevelInfo(0L, 0); + } + + + private static List<LevelInfo> getLevelBoundaries(String levelName, long xp) { + return switch (levelName) { + case "Vampire" -> VAMPIRE_SLAYER_BOUNDARIES; + case "Zombie", "Spider", "Wolf", "Enderman", "Blaze" -> GENERIC_SLAYER_BOUNDARIES; + case "PET_COMMON" -> COMMON_PET_BOUNDARIES; + case "PET_UNCOMMON" -> UNCOMMON_PET_BOUNDARIES; + case "PET_RARE" -> RARE_PET_BOUNDARIES; + case "PET_EPIC" -> EPIC_PET_BOUNDARIES; + case "PET_LEGENDARY", "PET_MYTHIC" -> LEGENDARY_PET_BOUNDARIES.subList(0,101); + case "PET_GREG" -> LEGENDARY_PET_BOUNDARIES; + case "Social" -> SOCIAL_SKILL_BOUNDARIES; + case "Runecraft" -> RUNECRAFT_SKILL_BOUNDARIES; + case "Catacombs" -> calculateCatacombsSkillBoundaries(xp); + default -> GENERIC_SKILL_BOUNDARIES; + }; + } + + private static List<LevelInfo> calculateCatacombsSkillBoundaries(long xp) { + if (xp >= CATACOMBS_SKILL_BOUNDARIES.getLast().xp) { + int additionalLevels = (int) ((xp - CATACOMBS_SKILL_BOUNDARIES.getLast().xp) / CATA_XP_PER_LEVEL) ; + + List<LevelInfo> updatedBoundaries = new ArrayList<>(CATACOMBS_SKILL_BOUNDARIES); + for (int i = 0; i <= additionalLevels; i++) { + int level = CATACOMBS_SKILL_BOUNDARIES.getLast().level + i + 1; + long nextLevelXP = updatedBoundaries.getLast().xp + CATA_XP_PER_LEVEL; + updatedBoundaries.add(new LevelInfo(nextLevelXP, level)); + } + + return updatedBoundaries; + } + + return CATACOMBS_SKILL_BOUNDARIES; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java new file mode 100644 index 00000000..b074952c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java @@ -0,0 +1,27 @@ +package de.hysky.skyblocker.skyblock.profileviewer.utils; + +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ProfileComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; + +import java.util.Optional; +import java.util.UUID; + +public class SkullCreator { + public static ItemStack createSkull(String textureB64) { + ItemStack skull = new ItemStack(Items.PLAYER_HEAD); + try { + PropertyMap map = new PropertyMap(); + map.put("textures", new Property("textures", textureB64)); + ProfileComponent profile = new ProfileComponent(Optional.of("skull"), Optional.of(UUID.randomUUID()), map); + skull.set(DataComponentTypes.PROFILE, profile); + } catch (Exception e) { + ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to create skull", e); + } + return skull; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java new file mode 100644 index 00000000..4c9dcda4 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java @@ -0,0 +1,65 @@ +package de.hysky.skyblocker.skyblock.profileviewer.utils; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ButtonTextures; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.LoreComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import java.awt.*; + +public class SubPageSelectButton extends ClickableWidget { + private final ProfileViewerPage page; + private final int index; + private boolean toggled; + + private static final ButtonTextures TEXTURES = new ButtonTextures(Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled.png"), Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon.png"), Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled_highlighted.png"), Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_highlighted.png")); + private final ItemStack ICON; + + public SubPageSelectButton(ProfileViewerPage page, int x, int y, int index, ItemStack item) { + super(x, y, 22, 22, item.getName()); + this.ICON = item; + this.toggled = index == 0; + this.index = index; + this.page = page; + visible = false; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.fill(this.getX(), this.getY(), this.getX() + 20, this.getY() + 20, Color.BLACK.getRGB()); + context.drawTexture(TEXTURES.get(toggled, isHovered()), this.getX() + 1, this.getY() + 1,0, 0, 18, 18, 18, 18); + context.drawItem(ICON, this.getX() + 2, this.getY() + 2); + if ((mouseX > getX() + 1 && mouseX < getX() + 19 && mouseY > getY() + 1 && mouseY < getY() + 19)) { + LoreComponent lore = ICON.get(DataComponentTypes.LORE); + if (lore != null) context.drawTooltip(MinecraftClient.getInstance().textRenderer, lore.lines(), mouseX, mouseY + 10); + } + } + + @Override + protected boolean clicked(double mouseX, double mouseY) { + return this.active && this.visible &&(mouseX > getX() + 1 && mouseX < getX() + 19 && mouseY > getY() + 1 && mouseY < getY() + 19); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} + + public void setToggled(boolean toggled) { + this.toggled = toggled; + } + + @Override + public void onClick(double mouseX, double mouseY) { + page.onNavButtonClick(this); + } + + public int getIndex() { + return index; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/quicknav/QuickNavButton.java b/src/main/java/de/hysky/skyblocker/skyblock/quicknav/QuickNavButton.java index f9ca0940..f7f33d0f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/quicknav/QuickNavButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/quicknav/QuickNavButton.java @@ -94,7 +94,7 @@ public class QuickNavButton extends ClickableWidget { RenderSystem.disableDepthTest(); // Construct the texture identifier based on the index and toggled state - Identifier tabTexture = new Identifier("container/creative_inventory/tab_" + (isTopTab() ? "top" : "bottom") + "_" + (toggled ? "selected" : "unselected") + "_" + (index % 7 + 1)); + Identifier tabTexture = Identifier.ofVanilla("container/creative_inventory/tab_" + (isTopTab() ? "top" : "bottom") + "_" + (toggled ? "selected" : "unselected") + "_" + (index % 7 + 1)); // Render the button texture context.drawGuiTexture(tabTexture, this.getX(), this.getY(), this.width, this.height); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/EffigyWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/EffigyWaypoints.java index 11963857..ef77c602 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/EffigyWaypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/EffigyWaypoints.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.rift; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; @@ -26,6 +27,7 @@ public class EffigyWaypoints { new BlockPos(240, 129, 118) //Effigy 6 ); private static final List<BlockPos> UNBROKEN_EFFIGIES = new ArrayList<>(); + private static final float[] RED = ColorUtils.getFloatComponents(DyeColor.RED); protected static void updateEffigies() { if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableEffigyWaypoints || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !Utils.getIslandArea().contains("Stillgore Château")) return; @@ -57,13 +59,12 @@ public class EffigyWaypoints { protected static void render(WorldRenderContext context) { if (SkyblockerConfigManager.get().slayers.vampireSlayer.enableEffigyWaypoints && Utils.getIslandArea().contains("Stillgore Château")) { for (BlockPos effigy : UNBROKEN_EFFIGIES) { - float[] colorComponents = DyeColor.RED.getColorComponents(); if (SkyblockerConfigManager.get().slayers.vampireSlayer.compactEffigyWaypoints) { - RenderHelper.renderFilledWithBeaconBeam(context, effigy.down(6), colorComponents, 0.5F, true); + RenderHelper.renderFilledWithBeaconBeam(context, effigy.down(6), RED, 0.5F, true); } else { - RenderHelper.renderFilledWithBeaconBeam(context, effigy, colorComponents, 0.5F, true); + RenderHelper.renderFilledWithBeaconBeam(context, effigy, RED, 0.5F, true); for (int i = 1; i < 6; i++) { - RenderHelper.renderFilled(context, effigy.down(i), colorComponents, 0.5F - (0.075F * i), true); + RenderHelper.renderFilled(context, effigy.down(i), RED, 0.5F - (0.075F * i), true); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java index 039d331c..8e3d1a91 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java @@ -7,9 +7,9 @@ import com.google.gson.JsonParser; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.OtherLocationsConfig; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.PosUtils; import de.hysky.skyblocker.utils.Utils; @@ -43,11 +43,11 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit public class EnigmaSouls { private static final Logger LOGGER = LoggerFactory.getLogger(EnigmaSouls.class); private static final Supplier<Waypoint.Type> TYPE_SUPPLIER = () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType; - private static final Identifier WAYPOINTS_JSON = new Identifier(SkyblockerMod.NAMESPACE, "rift/enigma_soul_waypoints.json"); + private static final Identifier WAYPOINTS_JSON = Identifier.of(SkyblockerMod.NAMESPACE, "rift/enigma_soul_waypoints.json"); private static final Map<BlockPos, ProfileAwareWaypoint> SOUL_WAYPOINTS = new HashMap<>(42); private static final Path FOUND_SOULS_FILE = SkyblockerMod.CONFIG_DIR.resolve("found_enigma_souls.json"); - private static final float[] GREEN = DyeColor.GREEN.getColorComponents(); - private static final float[] RED = DyeColor.RED.getColorComponents(); + private static final float[] GREEN = ColorUtils.getFloatComponents(DyeColor.GREEN); + private static final float[] RED = ColorUtils.getFloatComponents(DyeColor.RED); private static CompletableFuture<Void> soulsLoaded; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java index 7d7d0c34..6a366122 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java @@ -5,6 +5,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.waypoint.Waypoint; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; @@ -23,11 +24,11 @@ import java.util.function.Supplier; public class MirrorverseWaypoints { private static final Logger LOGGER = LoggerFactory.getLogger("skyblocker"); private static final Supplier<Waypoint.Type> WAYPOINT_TYPE = () -> Waypoint.Type.HIGHLIGHT; - private static final Identifier WAYPOINTS_JSON = new Identifier(SkyblockerMod.NAMESPACE, "rift/mirrorverse_waypoints.json"); + private static final Identifier WAYPOINTS_JSON = Identifier.of(SkyblockerMod.NAMESPACE, "rift/mirrorverse_waypoints.json"); private static Waypoint[] LAVA_PATH_WAYPOINTS; private static Waypoint[] UPSIDE_DOWN_WAYPOINTS; private static Waypoint[] TURBULATOR_WAYPOINTS; - private static final float[] COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); + private static final float[] COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.RED); private static CompletableFuture<Void> waypointsLoaded; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/OverlayScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/OverlayScreen.java index a03f3549..3e392f07 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/OverlayScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/OverlayScreen.java @@ -3,9 +3,11 @@ package de.hysky.skyblocker.skyblock.searchoverlay; import de.hysky.skyblocker.config.SkyblockerConfigManager; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.item.ItemStack; +import net.minecraft.text.MutableText; import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -16,10 +18,13 @@ import static de.hysky.skyblocker.skyblock.itemlist.ItemRepository.getItemStack; public class OverlayScreen extends Screen { - protected static final Identifier SEARCH_ICON_TEXTURE = new Identifier("icon/search"); + protected static final Identifier SEARCH_ICON_TEXTURE = Identifier.ofVanilla("icon/search"); + private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("social_interactions/background"); private static final int rowHeight = 20; private TextFieldWidget searchField; private ButtonWidget finishedButton; + private ButtonWidget maxPetButton; + private ButtonWidget dungeonStarButton; private ButtonWidget[] suggestionButtons; private ButtonWidget[] historyButtons; @@ -44,10 +49,11 @@ public class OverlayScreen extends Screen { searchField.setMaxLength(30); // finish buttons - finishedButton = ButtonWidget.builder(Text.literal("").setStyle(Style.EMPTY.withColor(Formatting.GREEN)), a -> close()) + finishedButton = ButtonWidget.builder(Text.literal(""), a -> close()) .position(startX + rowWidth - rowHeight, startY) .size(rowHeight, rowHeight).build(); + // suggested item buttons int rowOffset = rowHeight; int totalSuggestions = SkyblockerConfigManager.get().uiAndVisuals.searchOverlay.maxSuggestions; @@ -80,6 +86,26 @@ public class OverlayScreen extends Screen { break; } } + //auction only elements + if (SearchOverManager.isAuction) { + //max pet level button + maxPetButton = ButtonWidget.builder(Text.literal(""), a -> { + SearchOverManager.maxPetLevel = !SearchOverManager.maxPetLevel; + updateMaxPetText(); + }) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.general.searchOverlay.maxPet.@Tooltip"))) + .position(startX, startY - rowHeight - 8) + .size(rowWidth / 2, rowHeight).build(); + updateMaxPetText(); + + //dungeon star input + dungeonStarButton = ButtonWidget.builder(Text.literal("✪"), a -> updateStars()) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.general.searchOverlay.starsTooltip"))) + .position(startX + (int) (rowWidth * 0.5), startY - rowHeight - 8) + .size(rowWidth / 2, rowHeight).build(); + + updateStars(); + } //add drawables in order to make tab navigation sensible addDrawableChild(searchField); @@ -93,11 +119,86 @@ public class OverlayScreen extends Screen { } addDrawableChild(finishedButton); + if (SearchOverManager.isAuction) { + addDrawableChild(maxPetButton); + addDrawableChild(dungeonStarButton); + } + //focus the search box this.setInitialFocus(searchField); } /** + * Finds if the mouse is clicked on the dungeon star button and if so works out what stars the user clicked on + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return super + */ + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (SearchOverManager.isAuction && dungeonStarButton.isHovered() && client != null) { + double actualTextWidth = client.textRenderer.getWidth(dungeonStarButton.getMessage()); + double textOffset = (dungeonStarButton.getWidth() - actualTextWidth) / 2; + double offset = mouseX - (dungeonStarButton.getX() + textOffset); + int starCount = (int) ((offset / actualTextWidth) * 10); + starCount = Math.clamp(starCount + 1, 0, 10); + //if same as old value set stars to 0 else set to selected amount + if (starCount == SearchOverManager.dungeonStars) { + SearchOverManager.dungeonStars = 0; + } else { + SearchOverManager.dungeonStars = starCount; + } + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + /** + * Updates the text displayed on the max pet level button to represent the settings current state + */ + private void updateMaxPetText() { + if (SearchOverManager.maxPetLevel) { + maxPetButton.setMessage(Text.translatable("skyblocker.config.general.searchOverlay.maxPet").append(Text.literal(" ✔")).formatted(Formatting.GREEN)); + } else { + maxPetButton.setMessage(Text.translatable("skyblocker.config.general.searchOverlay.maxPet").append(Text.literal(" ❌")).formatted(Formatting.RED)); + } + } + + /** + * Updates stars in dungeon star input to represent the current star value + */ + private void updateStars() { + MutableText stars = Text.empty(); + for (int i = 0; i < SearchOverManager.dungeonStars; i++) { + stars.append(Text.literal("✪").formatted(i < 5 ? Formatting.YELLOW : Formatting.RED)); + } + for (int i = SearchOverManager.dungeonStars; i < 10; i++) { + stars.append(Text.literal("✪")); + } + dungeonStarButton.setMessage(stars); + } + + /** + * Renders the background for the search using the social interactions background + * @param context context + * @param mouseX mouseX + * @param mouseY mouseY + * @param delta delta + */ + @Override + public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { + super.renderBackground(context, mouseX, mouseY, delta); + //find max height + int maxHeight = rowHeight * (1 + suggestionButtons.length + historyButtons.length); + if (historyButtons.length > 0) { //add space for history label if it could exist + maxHeight += (int) (rowHeight * 0.75); + } + context.drawGuiTexture(BACKGROUND_TEXTURE, searchField.getX() - 8, searchField.getY() - 8, (int) (this.width * 0.4) + 16, maxHeight + 16); + } + + /** * Renders the search icon, label for the history and item Stacks for item names */ @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java index 917a6aa0..70dd8ada 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java @@ -10,6 +10,7 @@ import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; import de.hysky.skyblocker.utils.NEURepoManager; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import io.github.moulberry.repo.data.NEUItem; +import io.github.moulberry.repo.util.NEUId; import it.unimi.dsi.fastutil.Pair; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; @@ -24,10 +25,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -40,8 +38,7 @@ public class SearchOverManager { private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Search Overlay"); private static final Pattern BAZAAR_ENCHANTMENT_PATTERN = Pattern.compile("ENCHANTMENT_(\\D*)_(\\d+)"); - private static final Pattern AUCTION_PET_AND_RUNE_PATTERN = Pattern.compile("([A-Z0-9_]+);(\\d+)"); - + private static final String PET_NAME_START = "[Lvl {LVL}] "; /** * converts index (in array) +1 to a roman numeral */ @@ -52,15 +49,19 @@ public class SearchOverManager { private static @Nullable SignBlockEntity sign = null; private static boolean signFront = true; - private static boolean isAuction; + protected static boolean isAuction; private static boolean isCommand; protected static String search = ""; + protected static Boolean maxPetLevel = false; + protected static int dungeonStars = 0; // Use non-final variables and swap them to prevent concurrent modification - private static HashSet<String> bazaarItems; - private static HashSet<String> auctionItems; - private static HashMap<String, String> namesToId; + private static HashSet<String> bazaarItems = new HashSet<>(); + private static HashSet<String> auctionItems = new HashSet<>(); + private static HashSet<String> auctionPets = new HashSet<>(); + private static HashSet<String> starableItems = new HashSet<>(); + private static HashMap<String, String> namesToId = new HashMap<>(); public static String[] suggestionsArray = {}; @@ -91,6 +92,8 @@ public class SearchOverManager { private static void loadItems() { HashSet<String> bazaarItems = new HashSet<>(); HashSet<String> auctionItems = new HashSet<>(); + HashSet<String> auctionPets = new HashSet<>(); + HashSet<String> starableItems = new HashSet<>(); HashMap<String, String> namesToId = new HashMap<>(); //get bazaar items @@ -139,28 +142,28 @@ public class SearchOverManager { //get auction items try { + Set<@NEUId String> essenceCosts = NEURepoManager.NEU_REPO.getConstants().getEssenceCost().getCosts().keySet(); if (TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { TooltipInfoType.THREE_DAY_AVERAGE.run(); } for (Map.Entry<String, JsonElement> entry : TooltipInfoType.THREE_DAY_AVERAGE.getData().entrySet()) { String id = entry.getKey(); - - Matcher matcher = AUCTION_PET_AND_RUNE_PATTERN.matcher(id); - if (matcher.find()) {//is a pet or rune convert id to name - String name = matcher.group(1).replace("_", " "); - name = capitalizeFully(name); - auctionItems.add(name); - namesToId.put(name, id); - continue; - } - //something else look up in NEU repo. + //look up in NEU repo. id = id.split("[+-]")[0]; NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); if (neuItem != null) { String name = Formatting.strip(neuItem.getDisplayName()); + //add names that are pets to the list of pets to work with the lvl 100 button + if (name != null && name.startsWith(PET_NAME_START)) { + name = name.replace(PET_NAME_START, ""); + auctionPets.add(name.toLowerCase()); + } + //if it has essence cost add to starable items + if (name != null && essenceCosts.contains(neuItem.getSkyblockItemId())) { + starableItems.add(name.toLowerCase()); + } auctionItems.add(name); namesToId.put(name, id); - continue; } } } catch (Exception e) { @@ -169,11 +172,14 @@ public class SearchOverManager { SearchOverManager.bazaarItems = bazaarItems; SearchOverManager.auctionItems = auctionItems; + SearchOverManager.auctionPets = auctionPets; + SearchOverManager.starableItems = starableItems; SearchOverManager.namesToId = namesToId; } /** * Capitalizes the first letter off every word in a string + * * @param str string to capitalize */ private static String capitalizeFully(String str) { @@ -188,8 +194,9 @@ public class SearchOverManager { /** * Receives data when a search is started and resets values - * @param sign the sign that is being edited - * @param front if it's the front of the sign + * + * @param sign the sign that is being edited + * @param front if it's the front of the sign * @param isAuction if the sign is loaded from the auction house menu or bazaar */ public static void updateSign(@NotNull SignBlockEntity sign, boolean front, boolean isAuction) { @@ -214,6 +221,7 @@ public class SearchOverManager { /** * Updates the search value and the suggestions based on that value. + * * @param newValue new search value */ protected static void updateSearch(String newValue) { @@ -221,11 +229,30 @@ public class SearchOverManager { //update the suggestion values int totalSuggestions = SkyblockerConfigManager.get().uiAndVisuals.searchOverlay.maxSuggestions; if (newValue.isBlank() || totalSuggestions == 0) return; //do not search for empty value - suggestionsArray = (isAuction ? auctionItems : bazaarItems).stream().filter(item -> item.toLowerCase().contains(search.toLowerCase())).limit(totalSuggestions).toArray(String[]::new); + suggestionsArray = (isAuction ? auctionItems : bazaarItems).stream().sorted(Comparator.comparing(SearchOverManager::shouldFrontLoad, Comparator.reverseOrder())).filter(item -> item.toLowerCase().contains(search.toLowerCase())).limit(totalSuggestions).toArray(String[]::new); + } + + /** + * determines if a value should be moved to the front of the search + * + * @param name name of the suggested item + * @return if the value should be at the front of the search queue + */ + private static boolean shouldFrontLoad(String name) { + if (!isAuction) { + return false; + } + //do nothing to non pets + if (!auctionPets.contains(name.toLowerCase())) { + return false; + } + //only front load pets when there is enough of the pet typed, so it does not spoil searching for other items + return (double) search.length() / name.length() > 0.5; } /** * Gets the suggestion in the suggestion array at the index + * * @param index index of suggestion */ protected static String getSuggestion(int index) { @@ -242,6 +269,7 @@ public class SearchOverManager { /** * Gets the item name in the history array at the index + * * @param index index of suggestion */ protected static String getHistory(int index) { @@ -286,13 +314,18 @@ public class SearchOverManager { } /** - *Saves the current value of ({@link SearchOverManager#search}) then pushes it to a command or sign depending on how the gui was opened + * Saves the current value of ({@link SearchOverManager#search}) then pushes it to a command or sign depending on how the gui was opened */ protected static void pushSearch() { //save to history if (!search.isEmpty()) { saveHistory(); } + //add pet level or dungeon starts if in ah + if (isAuction) { + addExtras(); + } + //push if (isCommand) { pushCommand(); } else { @@ -301,6 +334,45 @@ public class SearchOverManager { } /** + * Adds pet level 100 or necessary dungeon starts if needed + */ + private static void addExtras() { + // pet level + if (maxPetLevel) { + if (auctionPets.contains(search.toLowerCase())) { + if (search.equalsIgnoreCase("golden dragon")) { + search = "[Lvl 200] " + search; + } else { + search = "[Lvl 100] " + search; + } + } + } else { + // still filter for only pets + if (auctionPets.contains(search.toLowerCase())) { + // add bracket so only get pets + search = "] " + search; + } + } + + // dungeon stars + // check if it's a dungeon item and if so add correct stars + if (dungeonStars > 0 && starableItems.contains(search.toLowerCase())) { + StringBuilder starString = new StringBuilder(" "); + //add stars up to 5 + starString.append("✪".repeat(Math.max(0, Math.min(dungeonStars, 5)))); + //add number for other stars + switch (dungeonStars) { + case 6 -> starString.append("➊"); + case 7 -> starString.append("➋"); + case 8 -> starString.append("➌"); + case 9 -> starString.append("➍"); + case 10 -> starString.append("➎"); + } + search += starString.toString(); + } + } + + /** * runs the command to search for the value in ({@link SearchOverManager#search}) */ private static void pushCommand() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java index c2c952cf..21d66805 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java @@ -97,7 +97,6 @@ public class Shortcuts { // Party commandArgs.put("/pa", "/p accept"); - commands.put("/pv", "/p leave"); commands.put("/pd", "/p disband"); commands.put("/rp", "/reparty"); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java index 3918037f..a6b5e62d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.shortcut; +import de.hysky.skyblocker.debug.Debug; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.Element; @@ -77,6 +78,11 @@ public class ShortcutsConfigListWidget extends ElementListWidget<ShortcutsConfig } @Override + protected boolean isSelectedEntry(int index) { + return Debug.debugEnabled() ? Objects.equals(getSelectedOrNull(), children().get(index)) : super.isSelectedEntry(index); + } + + @Override protected boolean removeEntry(AbstractShortcutEntry entry) { return super.removeEntry(entry); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java index 120eb099..66735511 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java @@ -11,7 +11,6 @@ import net.minecraft.screen.ScreenTexts; import net.minecraft.text.Text; public class ShortcutsConfigScreen extends Screen { - private ShortcutsConfigListWidget shortcutsConfigListWidget; private ButtonWidget buttonDelete; private ButtonWidget buttonNew; @@ -41,14 +40,14 @@ public class ShortcutsConfigScreen extends Screen { shortcutsConfigListWidget.setDimensions(width, height - 96); shortcutsConfigListWidget.updatePositions(); } else { - shortcutsConfigListWidget = new ShortcutsConfigListWidget(client, this, width, height - 96, 32, 25); + shortcutsConfigListWidget = new ShortcutsConfigListWidget(client, this, width, height - 96, 32, 24); initialized = true; } addDrawableChild(shortcutsConfigListWidget); GridWidget gridWidget = new GridWidget(); gridWidget.getMainPositioner().marginX(5).marginY(2); GridWidget.Adder adder = gridWidget.createAdder(2); - buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.delete"), button -> { + buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.deleteButton"), button -> { if (client != null && shortcutsConfigListWidget.getSelectedOrNull() instanceof ShortcutsConfigListWidget.ShortcutEntry shortcutEntry) { scrollAmount = shortcutsConfigListWidget.getScrollAmount(); client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.stringifiedTranslatable("skyblocker.shortcuts.deleteWarning", shortcutEntry), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL)); @@ -57,16 +56,10 @@ public class ShortcutsConfigScreen extends Screen { adder.add(buttonDelete); buttonNew = ButtonWidget.builder(Text.translatable("skyblocker.shortcuts.new"), buttonNew -> shortcutsConfigListWidget.addShortcutAfterSelected()).build(); adder.add(buttonNew); - adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { - if (client != null) { - close(); - } - }).build()); + adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> close()).build()); buttonDone = ButtonWidget.builder(ScreenTexts.DONE, button -> { shortcutsConfigListWidget.saveShortcuts(); - if (client != null) { - close(); - } + close(); }).tooltip(Tooltip.of(Text.translatable("skyblocker.shortcuts.commandSuggestionTooltip"))).build(); adder.add(buttonDone); gridWidget.refreshPositions(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java index 982fa16e..18a65049 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java @@ -81,7 +81,7 @@ public class ScreenMaster { // WHY MUST IT ALWAYS BE SUCH NESTED GARBAGE MINECRAFT KEEP THAT IN DFU FFS ResourceManagerHelper.registerBuiltinResourcePack( - new Identifier(SkyblockerMod.NAMESPACE, "top_aligned"), + Identifier.of(SkyblockerMod.NAMESPACE, "top_aligned"), SkyblockerMod.SKYBLOCKER_MOD, ResourcePackActivationType.NORMAL ); @@ -91,7 +91,7 @@ public class ScreenMaster { new SimpleSynchronousResourceReloadListener() { @Override public Identifier getFabricId() { - return new Identifier("skyblocker", "tabhud"); + return Identifier.of(SkyblockerMod.NAMESPACE, "tabhud"); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java index 818056f0..b37a3883 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java @@ -1,8 +1,11 @@ package de.hysky.skyblocker.skyblock.tabhud.util; +import net.minecraft.enchantment.Enchantment; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import static net.minecraft.enchantment.Enchantments.PROTECTION; + /** * Stores convenient shorthands for common ItemStack definitions */ @@ -10,23 +13,29 @@ public class Ico { public static final ItemStack MAP = new ItemStack(Items.FILLED_MAP); public static final ItemStack NTAG = new ItemStack(Items.NAME_TAG); public static final ItemStack EMERALD = new ItemStack(Items.EMERALD); + public static final ItemStack MAGMA_CREAM = new ItemStack(Items.MAGMA_CREAM); public static final ItemStack AMETHYST_SHARD = new ItemStack(Items.AMETHYST_SHARD); public static final ItemStack CLOCK = new ItemStack(Items.CLOCK); public static final ItemStack DIASWORD = new ItemStack(Items.DIAMOND_SWORD); public static final ItemStack DBUSH = new ItemStack(Items.DEAD_BUSH); public static final ItemStack VILLAGER = new ItemStack(Items.VILLAGER_SPAWN_EGG); + public static final ItemStack SPAWN_EGG = new ItemStack(Items.GHAST_SPAWN_EGG); public static final ItemStack MOREGOLD = new ItemStack(Items.GOLDEN_APPLE); public static final ItemStack COMPASS = new ItemStack(Items.COMPASS); public static final ItemStack SUGAR = new ItemStack(Items.SUGAR); - public static final ItemStack HOE = new ItemStack(Items.IRON_HOE); + public static final ItemStack IRON_HOE = new ItemStack(Items.IRON_HOE); + public static final ItemStack GOLDEN_HOE = new ItemStack(Items.GOLDEN_HOE); public static final ItemStack GOLD = new ItemStack(Items.GOLD_INGOT); + public static final ItemStack IRON = new ItemStack(Items.IRON_INGOT); public static final ItemStack BONE = new ItemStack(Items.BONE); public static final ItemStack SIGN = new ItemStack(Items.OAK_SIGN); public static final ItemStack FISH_ROD = new ItemStack(Items.FISHING_ROD); - public static final ItemStack SWORD = new ItemStack(Items.IRON_SWORD); + public static final ItemStack STONE_SWORD = new ItemStack(Items.STONE_SWORD); + public static final ItemStack IRON_SWORD = new ItemStack(Items.IRON_SWORD); public static final ItemStack LANTERN = new ItemStack(Items.LANTERN); public static final ItemStack COOKIE = new ItemStack(Items.COOKIE); public static final ItemStack POTION = new ItemStack(Items.POTION); + public static final ItemStack S_POTION = new ItemStack(Items.SPLASH_POTION); public static final ItemStack BARRIER = new ItemStack(Items.BARRIER); public static final ItemStack PLAYER = new ItemStack(Items.PLAYER_HEAD); public static final ItemStack WATER = new ItemStack(Items.WATER_BUCKET); @@ -37,6 +46,7 @@ public class Ico { public static final ItemStack STRING = new ItemStack(Items.STRING); public static final ItemStack WITHER = new ItemStack(Items.WITHER_SKELETON_SKULL); public static final ItemStack FLESH = new ItemStack(Items.ROTTEN_FLESH); + public static final ItemStack MUTTON = new ItemStack(Items.MUTTON); public static final ItemStack DRAGON = new ItemStack(Items.DRAGON_HEAD); public static final ItemStack DIAMOND = new ItemStack(Items.DIAMOND); public static final ItemStack ICE = new ItemStack(Items.ICE); @@ -46,25 +56,18 @@ public class Ico { public static final ItemStack BOOK = new ItemStack(Items.WRITABLE_BOOK); public static final ItemStack FURNACE = new ItemStack(Items.FURNACE); public static final ItemStack CHESTPLATE = new ItemStack(Items.IRON_CHESTPLATE); + public static final ItemStack L_CHESTPLATE = new ItemStack(Items.LEATHER_CHESTPLATE); public static final ItemStack B_ROD = new ItemStack(Items.BLAZE_ROD); + public static final ItemStack B_POWDER = new ItemStack(Items.BLAZE_POWDER); public static final ItemStack BOW = new ItemStack(Items.BOW); public static final ItemStack COPPER = new ItemStack(Items.COPPER_INGOT); public static final ItemStack NETHERITE_UPGRADE_ST = new ItemStack(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE); public static final ItemStack COMPOSTER = new ItemStack(Items.COMPOSTER); public static final ItemStack SAPLING = new ItemStack(Items.OAK_SAPLING); public static final ItemStack SEEDS = new ItemStack(Items.WHEAT_SEEDS); - public static final ItemStack WHEAT = new ItemStack(Items.WHEAT); - public static final ItemStack CARROT = new ItemStack(Items.CARROT); - public static final ItemStack POTATO = new ItemStack(Items.POTATO); - public static final ItemStack SUGAR_CANE = new ItemStack(Items.SUGAR_CANE); - public static final ItemStack NETHER_WART = new ItemStack(Items.NETHER_WART); - public static final ItemStack MUSHROOM = new ItemStack(Items.RED_MUSHROOM); - public static final ItemStack CACTUS = new ItemStack(Items.CACTUS); - public static final ItemStack MELON = new ItemStack(Items.MELON); - public static final ItemStack PUMPKIN = new ItemStack(Items.PUMPKIN); - public static final ItemStack COCOA_BEANS = new ItemStack(Items.COCOA_BEANS); public static final ItemStack MILESTONE = new ItemStack(Items.LODESTONE); - public static final ItemStack PICKAXE = new ItemStack(Items.IRON_PICKAXE); + public static final ItemStack STONE_PICKAXE = new ItemStack(Items.STONE_PICKAXE); + public static final ItemStack IRON_PICKAXE = new ItemStack(Items.IRON_PICKAXE); public static final ItemStack NETHER_STAR = new ItemStack(Items.NETHER_STAR); public static final ItemStack HEART_OF_THE_SEA = new ItemStack(Items.HEART_OF_THE_SEA); public static final ItemStack EXPERIENCE_BOTTLE = new ItemStack(Items.EXPERIENCE_BOTTLE); @@ -73,4 +76,13 @@ public class Ico { public static final ItemStack ENCHANTED_BOOK = new ItemStack(Items.ENCHANTED_BOOK); public static final ItemStack SPIDER_EYE = new ItemStack(Items.SPIDER_EYE); public static final ItemStack BLUE_ICE = new ItemStack(Items.BLUE_ICE); + public static final ItemStack JUNGLE_SAPLING = new ItemStack(Items.JUNGLE_SAPLING); + public static final ItemStack ENCHANTING_TABLE = new ItemStack(Items.ENCHANTING_TABLE); + public static final ItemStack BREWING_STAND = new ItemStack(Items.BREWING_STAND); + public static final ItemStack CRAFTING_TABLE = new ItemStack(Items.CRAFTING_TABLE); + public static final ItemStack PAINTING = new ItemStack(Items.PAINTING); + public static final ItemStack E_PEARL = new ItemStack(Items.ENDER_PEARL); + public static final ItemStack FEATHER = new ItemStack(Items.FEATHER); + public static final ItemStack E_CHEST = new ItemStack(Items.ENDER_CHEST); + public static final ItemStack MYCELIUM = new ItemStack(Items.MYCELIUM); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java index 9c299210..79193b51 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java @@ -38,7 +38,7 @@ public class DungeonDeathWidget extends Widget { this.addComponent(deaths); } - this.addSimpleIcoText(Ico.SWORD, "Damage Dealt:", Formatting.RED, 26); + this.addSimpleIcoText(Ico.IRON_SWORD, "Damage Dealt:", Formatting.RED, 26); this.addSimpleIcoText(Ico.POTION, "Healing Done:", Formatting.RED, 27); this.addSimpleIcoText(Ico.NTAG, "Milestone:", Formatting.YELLOW, 28); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java index ec935faf..8f50f9ff 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java @@ -32,10 +32,10 @@ public class ElectionWidget extends Widget { static { MAYOR_DATA.put("Aatrox", Ico.DIASWORD); - MAYOR_DATA.put("Cole", Ico.PICKAXE); + MAYOR_DATA.put("Cole", Ico.IRON_PICKAXE); MAYOR_DATA.put("Diana", Ico.BONE); MAYOR_DATA.put("Diaz", Ico.GOLD); - MAYOR_DATA.put("Finnegan", Ico.HOE); + MAYOR_DATA.put("Finnegan", Ico.IRON_HOE); MAYOR_DATA.put("Foxy", Ico.SUGAR); MAYOR_DATA.put("Paul", Ico.COMPASS); MAYOR_DATA.put("Scorpius", Ico.MOREGOLD); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java index 75652b33..9e1f3989 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java @@ -82,14 +82,14 @@ public class GardenSkillsWidget extends Widget { Text speed = Widget.simpleEntryText(67, "SPD", Formatting.WHITE); IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed); Text farmfort = Widget.simpleEntryText(68, "FFO", Formatting.GOLD); - IcoTextComponent ffo = new IcoTextComponent(Ico.HOE, farmfort); + IcoTextComponent ffo = new IcoTextComponent(Ico.IRON_HOE, farmfort); TableComponent tc = new TableComponent(2, 1, Formatting.YELLOW.getColorValue()); tc.addToCell(0, 0, spd); tc.addToCell(1, 0, ffo); this.addComponent(tc); - this.addComponent(new IcoTextComponent(Ico.HOE, PlayerListMgr.textAt(70))); + this.addComponent(new IcoTextComponent(Ico.IRON_HOE, PlayerListMgr.textAt(70))); ProgressComponent pc2; Matcher milestoneMatcher = PlayerListMgr.regexAt(69, MS_PATTERN); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java index 24dcc229..c28c8679 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java @@ -26,7 +26,7 @@ public class JacobsContestWidget extends Widget { //TODO Properly match the contest placement and display it private static final Pattern CROP_PATTERN = Pattern.compile("(?<fortune>[☘○]) (?<crop>[A-Za-z ]+).*"); - private static final Map<String, ItemStack> FARM_DATA = Map.ofEntries( + public static final Map<String, ItemStack> FARM_DATA = Map.ofEntries( entry("Wheat", new ItemStack(Items.WHEAT)), entry("Sugar Cane", new ItemStack(Items.SUGAR_CANE)), entry("Carrot", new ItemStack(Items.CARROT)), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java index 3c218fb1..34d15a28 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java @@ -44,7 +44,7 @@ public class ReputationWidget extends Widget { if (fname.equals("Mage")) { faction = new IcoTextComponent(Ico.POTION, Text.literal(fname).formatted(Formatting.DARK_AQUA)); } else { - faction = new IcoTextComponent(Ico.SWORD, Text.literal(fname).formatted(Formatting.RED)); + faction = new IcoTextComponent(Ico.IRON_SWORD, Text.literal(fname).formatted(Formatting.RED)); } } this.addComponent(faction); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java index 379fbb62..c9cf61aa 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java @@ -58,13 +58,13 @@ public class SkillsWidget extends Widget { Text speed = Widget.simpleEntryText(67, "SPD", Formatting.WHITE); IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed); Text strength = Widget.simpleEntryText(68, "STR", Formatting.RED); - IcoTextComponent str = new IcoTextComponent(Ico.SWORD, strength); + IcoTextComponent str = new IcoTextComponent(Ico.IRON_SWORD, strength); Text critDmg = Widget.simpleEntryText(69, "CCH", Formatting.BLUE); - IcoTextComponent cdg = new IcoTextComponent(Ico.SWORD, critDmg); + IcoTextComponent cdg = new IcoTextComponent(Ico.IRON_SWORD, critDmg); Text critCh = Widget.simpleEntryText(70, "CDG", Formatting.BLUE); - IcoTextComponent cch = new IcoTextComponent(Ico.SWORD, critCh); + IcoTextComponent cch = new IcoTextComponent(Ico.IRON_SWORD, critCh); Text aSpeed = Widget.simpleEntryText(71, "ASP", Formatting.YELLOW); - IcoTextComponent asp = new IcoTextComponent(Ico.HOE, aSpeed); + IcoTextComponent asp = new IcoTextComponent(Ico.IRON_HOE, aSpeed); TableComponent tc = new TableComponent(2, 3, Formatting.YELLOW.getColorValue()); tc.addToCell(0, 0, spd); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java new file mode 100644 index 00000000..da6f52c8 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java @@ -0,0 +1,73 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.waypoint.NamedWaypoint; +import de.hysky.skyblocker.utils.waypoint.WaypointCategory; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +import java.util.Arrays; + +public abstract class AbstractWaypointsScreen<T extends Screen> extends Screen { + protected final T parent; + protected final Multimap<String, WaypointCategory> waypoints; + protected String island; + protected WaypointsListWidget waypointsListWidget; + protected DropdownWidget<Location> islandWidget; + + public AbstractWaypointsScreen(Text title, T parent) { + this(title, parent, MultimapBuilder.hashKeys().arrayListValues().build()); + } + + public AbstractWaypointsScreen(Text title, T parent, Multimap<String, WaypointCategory> waypoints) { + this(title, parent, waypoints, Utils.getLocationRaw()); + } + + public AbstractWaypointsScreen(Text title, T parent, Multimap<String, WaypointCategory> waypoints, String island) { + super(title); + this.parent = parent; + this.waypoints = waypoints; + this.island = island; + } + + @Override + protected void init() { + super.init(); + waypointsListWidget = addDrawableChild(new WaypointsListWidget(client, this, width, height - 96, 32, 24)); + islandWidget = addDrawableChild(new DropdownWidget<>(client, width - 160, 8, 150, height - 8, Arrays.asList(Location.values()), this::islandChanged, Location.from(island))); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (islandWidget.mouseClicked(mouseX, mouseY, button)) { + return true; + } + boolean mouseClicked = super.mouseClicked(mouseX, mouseY, button); + updateButtons(); + return mouseClicked; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + if (islandWidget.isMouseOver(mouseX, mouseY) && islandWidget.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) { + return true; + } + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + protected void islandChanged(Location location) { + island = location.id(); + waypointsListWidget.setIsland(island); + } + + protected abstract boolean isEnabled(NamedWaypoint waypoint); + + protected abstract void enabledChanged(NamedWaypoint waypoint, boolean enabled); + + protected void updateButtons() { + waypointsListWidget.updateButtons(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java new file mode 100644 index 00000000..724cb461 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java @@ -0,0 +1,141 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.text.Style; +import net.minecraft.text.Text; + +import java.util.List; +import java.util.function.Consumer; + +public class DropdownWidget<T> extends ElementListWidget<DropdownWidget.Entry<T>> { + private static final MinecraftClient client = MinecraftClient.getInstance(); + public static final int ENTRY_HEIGHT = 15; + protected final List<T> entries; + protected final Consumer<T> selectCallback; + protected T prevSelected; + protected T selected; + protected boolean open; + + public DropdownWidget(MinecraftClient minecraftClient, int x, int y, int width, int maxHeight, List<T> entries, Consumer<T> selectCallback, T selected) { + super(minecraftClient, width, Math.min((entries.size() + 1) * ENTRY_HEIGHT + 8, maxHeight), y, ENTRY_HEIGHT); + setX(x); + this.entries = entries; + this.selectCallback = selectCallback; + this.selected = selected; + setRenderHeader(true, ENTRY_HEIGHT + 4); + for (T entry : entries) { + addEntry(new Entry<>(this, entry)); + } + } + + @Override + public int getRowLeft() { + return getX(); + } + + @Override + public int getRowWidth() { + return getWidth(); + } + + @Override + protected boolean clickedHeader(int x, int y) { + open = !open; + return true; + } + + @Override + protected void renderHeader(DrawContext context, int x, int y) { + context.drawTextWithShadow(client.textRenderer, selected.toString(), x + 4, y + 2, 0xFFFFFFFF); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.getMatrices().push(); + context.getMatrices().translate(0, 0, 100); + + int y = getY() - (int) getScrollAmount(); + int height = getMaxPosition(); + + context.fill(getX(), y, getX() + width, y + headerHeight, 0xFF000000); + context.drawHorizontalLine(getX(), getX() + width, y, 0xFFFFFFFF); + context.drawHorizontalLine(getX(), getX() + width, y + headerHeight, 0xFFFFFFFF); + context.drawVerticalLine(getX(), y, y + headerHeight, 0xFFFFFFFF); + context.drawVerticalLine(getX() + width, y, y + headerHeight, 0xFFFFFFFF); + + if (open) { + context.fill(getX(), y + headerHeight + 1, getX() + width, y + height, 0xFF000000); + context.drawHorizontalLine(getX(), getX() + width, y + height, 0xFFFFFFFF); + context.drawVerticalLine(getX(), y + headerHeight, y + height, 0xFFFFFFFF); + context.drawVerticalLine(getX() + width, y + headerHeight, y + height, 0xFFFFFFFF); + + super.renderWidget(context, mouseX, mouseY, delta); + } else { + renderHeader(context, getRowLeft(), y + 4); + } + + context.getMatrices().pop(); + } + + @Override + protected void drawMenuListBackground(DrawContext context) {} + + @Override + protected void drawHeaderAndFooterSeparators(DrawContext context) {} + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + return open && super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + protected void select(T entry) { + selected = entry; + open = false; + setScrollAmount(0); + if (selected != prevSelected) { + selectCallback.accept(entry); + prevSelected = selected; + } + } + + static class Entry<T> extends ElementListWidget.Entry<Entry<T>> { + private final DropdownWidget<T> dropdownWidget; + private final T entry; + + public Entry(DropdownWidget<T> dropdownWidget, T entry) { + this.dropdownWidget = dropdownWidget; + this.entry = entry; + } + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(); + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + context.drawTextWithShadow(client.textRenderer, Text.literal(entry.toString()).fillStyle(Style.EMPTY.withUnderline(hovered)), x + 14, y + 2, 0xFFFFFFFF); + if (dropdownWidget.selected == this.entry) { + context.drawTextWithShadow(client.textRenderer, "✔", x + 4, y + 2, 0xFFFFFFFF); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0 && dropdownWidget.open) { + dropdownWidget.select(entry); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java index 25741b22..91b98e90 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java @@ -8,6 +8,7 @@ import com.mojang.brigadier.CommandDispatcher; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.HelperConfig; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.NEURepoManager; import de.hysky.skyblocker.utils.PosUtils; @@ -73,7 +74,7 @@ public class FairySouls { private static void loadFairySouls() { fairySoulsLoaded = NEURepoManager.runAsyncAfterLoad(() -> { maxSouls = NEURepoManager.NEU_REPO.getConstants().getFairySouls().getMaxSouls(); - NEURepoManager.NEU_REPO.getConstants().getFairySouls().getSoulLocations().forEach((location, fairiesForLocation) -> fairySouls.put(location, fairiesForLocation.stream().map(coordinate -> new BlockPos(coordinate.getX(), coordinate.getY(), coordinate.getZ())).collect(Collectors.toUnmodifiableMap(pos -> pos, pos -> new ProfileAwareWaypoint(pos, TYPE_SUPPLIER, DyeColor.GREEN.getColorComponents(), DyeColor.RED.getColorComponents()))))); + NEURepoManager.NEU_REPO.getConstants().getFairySouls().getSoulLocations().forEach((location, fairiesForLocation) -> fairySouls.put(location, fairiesForLocation.stream().map(coordinate -> new BlockPos(coordinate.getX(), coordinate.getY(), coordinate.getZ())).collect(Collectors.toUnmodifiableMap(pos -> pos, pos -> new ProfileAwareWaypoint(pos, TYPE_SUPPLIER, ColorUtils.getFloatComponents(DyeColor.GREEN), ColorUtils.getFloatComponents(DyeColor.RED)))))); LOGGER.debug("[Skyblocker] Loaded {} fairy souls across {} locations", fairySouls.values().stream().mapToInt(Map::size).sum(), fairySouls.size()); try (BufferedReader reader = Files.newBufferedReader(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json"))) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java index 254077ad..ffeba7ea 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.waypoint; import com.mojang.brigadier.Command; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; @@ -46,7 +47,7 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit public class MythologicalRitual { private static final Pattern GRIFFIN_BURROW_DUG = Pattern.compile("(?<message>You dug out a Griffin Burrow!|You finished the Griffin burrow chain!) \\((?<index>\\d)/4\\)"); - private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents(); + private static final float[] ORANGE_COLOR_COMPONENTS = ColorUtils.getFloatComponents(DyeColor.ORANGE); private static long lastEchoTime; private static final Map<BlockPos, GriffinBurrow> griffinBurrows = new HashMap<>(); @Nullable diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java index bbc9a655..f8930882 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java @@ -408,7 +408,7 @@ public class OrderedWaypoints { } @Override - protected float[] getColorComponents() { + public float[] getColorComponents() { if (this.colorComponents.length != 3) { return switch (this.relativeIndex) { case PREVIOUS -> RED; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java index 3a9af459..38e30ea8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java @@ -8,6 +8,7 @@ import com.mojang.brigadier.CommandDispatcher; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.OtherLocationsConfig; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.PosUtils; import de.hysky.skyblocker.utils.Utils; @@ -58,7 +59,7 @@ public class Relics { private static void loadRelics(MinecraftClient client) { relicsLoaded = CompletableFuture.runAsync(() -> { - try (BufferedReader reader = client.getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "spidersden/relics.json"))) { + try (BufferedReader reader = client.getResourceManager().openAsReader(Identifier.of(SkyblockerMod.NAMESPACE, "spidersden/relics.json"))) { for (Map.Entry<String, JsonElement> json : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) { if (json.getKey().equals("total")) { totalRelics = json.getValue().getAsInt(); @@ -66,7 +67,7 @@ public class Relics { for (JsonElement locationJson : json.getValue().getAsJsonArray().asList()) { JsonObject posData = locationJson.getAsJsonObject(); BlockPos pos = new BlockPos(posData.get("x").getAsInt(), posData.get("y").getAsInt(), posData.get("z").getAsInt()); - relics.put(pos, new ProfileAwareWaypoint(pos, TYPE_SUPPLIER, DyeColor.YELLOW.getColorComponents(), DyeColor.BROWN.getColorComponents())); + relics.put(pos, new ProfileAwareWaypoint(pos, TYPE_SUPPLIER, ColorUtils.getFloatComponents(DyeColor.YELLOW), ColorUtils.getFloatComponents(DyeColor.BROWN))); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java new file mode 100644 index 00000000..18096117 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java @@ -0,0 +1,132 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Multimaps; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import de.hysky.skyblocker.utils.waypoint.WaypointCategory; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.toast.SystemToast; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class Waypoints { + public static final Logger LOGGER = LoggerFactory.getLogger(Waypoints.class); + private static final Codec<List<WaypointCategory>> CODEC = WaypointCategory.CODEC.listOf(); + private static final Codec<List<WaypointCategory>> SKYTILS_CODEC = WaypointCategory.SKYTILS_CODEC.listOf(); + protected static final SystemToast.Type WAYPOINTS_TOAST_TYPE = new SystemToast.Type(); + + private static final Path waypointsFile = FabricLoader.getInstance().getConfigDir().resolve(SkyblockerMod.NAMESPACE).resolve("waypoints.json"); + protected static final Multimap<String, WaypointCategory> waypoints = MultimapBuilder.hashKeys().arrayListValues().build(); + + public static void init() { + loadWaypoints(); + ClientLifecycleEvents.CLIENT_STOPPING.register(Waypoints::saveWaypoints); + WorldRenderEvents.AFTER_TRANSLUCENT.register(Waypoints::render); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("waypoints").executes(Scheduler.queueOpenScreenCommand(() -> new WaypointsScreen(MinecraftClient.getInstance().currentScreen)))))); + } + + public static void loadWaypoints() { + waypoints.clear(); + try (BufferedReader reader = Files.newBufferedReader(waypointsFile)) { + List<WaypointCategory> waypoints = CODEC.parse(JsonOps.INSTANCE, SkyblockerMod.GSON.fromJson(reader, JsonArray.class)).resultOrPartial(LOGGER::error).orElseThrow(); + waypoints.forEach(waypointCategory -> Waypoints.waypoints.put(waypointCategory.island(), waypointCategory)); + } catch (Exception e) { + LOGGER.error("[Skyblocker Waypoints] Encountered exception while loading waypoints", e); + } + } + + public static List<WaypointCategory> fromSkytilsBase64(String base64, String defaultIsland) { + try { + if (base64.startsWith("<Skytils-Waypoint-Data>(V")) { + int version = Integer.parseInt(base64.substring(26, base64.indexOf(')'))); + if (version == 1) { + return fromSkytilsJson(new String(Base64.getDecoder().decode(base64.substring(base64.indexOf(':') + 1))), defaultIsland); + } else { + LOGGER.error("[Skyblocker Waypoints] Unknown Skytils waypoint data version: " + version); + } + } else return fromSkytilsJson(new String(Base64.getDecoder().decode(base64)), defaultIsland); + } catch (NumberFormatException e) { + LOGGER.error("[Skyblocker Waypoints] Encountered exception while parsing Skytils waypoint data version", e); + } catch (IllegalArgumentException e) { + LOGGER.error("[Skyblocker Waypoints] Encountered exception while decoding Skytils waypoint data", e); + } + return Collections.emptyList(); + } + + public static List<WaypointCategory> fromSkytilsJson(String waypointCategories, String defaultIsland) { + JsonArray waypointCategoriesJson; + try { + waypointCategoriesJson = SkyblockerMod.GSON.fromJson(waypointCategories, JsonObject.class).getAsJsonArray("categories"); + } catch (JsonSyntaxException e) { + JsonObject waypointCategoryJson = new JsonObject(); + waypointCategoryJson.addProperty("name", "New Category"); + waypointCategoryJson.addProperty("island", defaultIsland); + waypointCategoryJson.add("waypoints", SkyblockerMod.GSON.fromJson(waypointCategories, JsonArray.class)); + waypointCategoriesJson = new JsonArray(); + waypointCategoriesJson.add(waypointCategoryJson); + } + return SKYTILS_CODEC.parse(JsonOps.INSTANCE, waypointCategoriesJson).resultOrPartial(LOGGER::error).orElseThrow(); + } + + public static String toSkytilsBase64(List<WaypointCategory> waypointCategories) { + return Base64.getEncoder().encodeToString(toSkytilsJson(waypointCategories).getBytes()); + } + + public static String toSkytilsJson(List<WaypointCategory> waypointCategories) { + JsonObject waypointCategoriesJson = new JsonObject(); + waypointCategoriesJson.add("categories", SKYTILS_CODEC.encodeStart(JsonOps.INSTANCE, waypointCategories).resultOrPartial(LOGGER::error).orElseThrow()); + return SkyblockerMod.GSON_COMPACT.toJson(waypointCategoriesJson); + } + + public static void saveWaypoints(MinecraftClient client) { + try (BufferedWriter writer = Files.newBufferedWriter(waypointsFile)) { + JsonElement waypointsJson = CODEC.encodeStart(JsonOps.INSTANCE, List.copyOf(waypoints.values())).resultOrPartial(LOGGER::error).orElseThrow(); + SkyblockerMod.GSON.toJson(waypointsJson, writer); + LOGGER.info("[Skyblocker Waypoints] Saved waypoints"); + } catch (Exception e) { + LOGGER.error("[Skyblocker Waypoints] Encountered exception while saving waypoints", e); + } + } + + public static Multimap<String, WaypointCategory> waypointsDeepCopy() { + return waypoints.values().stream().map(WaypointCategory::deepCopy).collect(Multimaps.toMultimap(WaypointCategory::island, Function.identity(), () -> MultimapBuilder.hashKeys().arrayListValues().build())); + } + + public static void render(WorldRenderContext context) { + if (SkyblockerConfigManager.get().uiAndVisuals.waypoints.enableWaypoints) { + Collection<WaypointCategory> categories = waypoints.get(Utils.getLocationRaw()); + for (WaypointCategory category : categories) { + if (category != null) { + category.render(context); + } + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java new file mode 100644 index 00000000..f4b54342 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java @@ -0,0 +1,318 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import de.hysky.skyblocker.mixins.accessors.CheckboxWidgetAccessor; +import de.hysky.skyblocker.utils.waypoint.NamedWaypoint; +import de.hysky.skyblocker.utils.waypoint.WaypointCategory; +import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.*; +import net.minecraft.text.Text; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class WaypointsListWidget extends ElementListWidget<WaypointsListWidget.AbstractWaypointEntry> { + private final AbstractWaypointsScreen<?> screen; + private String island; + private List<WaypointCategory> waypoints; + + public WaypointsListWidget(MinecraftClient client, AbstractWaypointsScreen<?> screen, int width, int height, int y, int itemHeight) { + super(client, width, height, y, itemHeight); + this.screen = screen; + setIsland(screen.island); + } + + @Override + public int getRowWidth() { + return super.getRowWidth() + 100; + } + + @Override + protected int getScrollbarX() { + return super.getScrollbarX(); + } + + Optional<WaypointCategoryEntry> getCategory() { + if (getSelectedOrNull() instanceof WaypointCategoryEntry category) { + return Optional.of(category); + } else if (getSelectedOrNull() instanceof WaypointEntry waypointEntry) { + return Optional.of(waypointEntry.category); + } + return Optional.empty(); + } + + void setIsland(String island) { + this.island = island; + waypoints = (List<WaypointCategory>) screen.waypoints.get(island); + updateEntries(); + } + + void addWaypointCategoryAfterSelected() { + WaypointCategoryEntry categoryEntry = new WaypointCategoryEntry(); + Optional<WaypointCategoryEntry> selectedCategoryEntryOptional = getCategory(); + int index = waypoints.size(); + int entryIndex = children().size(); + if (selectedCategoryEntryOptional.isPresent()) { + WaypointCategoryEntry selectedCategoryEntry = selectedCategoryEntryOptional.get(); + index = waypoints.indexOf(selectedCategoryEntry.category) + 1; + entryIndex = children().indexOf(selectedCategoryEntry) + 1; + while (entryIndex < children().size() && !(children().get(entryIndex) instanceof WaypointCategoryEntry)) { + entryIndex++; + } + } + waypoints.add(index, categoryEntry.category); + children().add(entryIndex, categoryEntry); + } + + void updateEntries() { + clearEntries(); + for (WaypointCategory category : waypoints) { + WaypointCategoryEntry categoryEntry = new WaypointCategoryEntry(category); + addEntry(categoryEntry); + for (NamedWaypoint waypoint : category.waypoints()) { + addEntry(new WaypointEntry(categoryEntry, waypoint)); + } + } + } + + void updateButtons() { + for (Entry<AbstractWaypointEntry> entry : children()) { + if (entry instanceof WaypointCategoryEntry categoryEntry && categoryEntry.enabled.isChecked() != categoryEntry.category.waypoints().stream().allMatch(screen::isEnabled)) { + ((CheckboxWidgetAccessor) categoryEntry.enabled).setChecked(!categoryEntry.enabled.isChecked()); + } else if (entry instanceof WaypointEntry waypointEntry && waypointEntry.enabled.isChecked() != screen.isEnabled(waypointEntry.waypoint)) { + waypointEntry.enabled.onPress(); + } + } + } + + private BlockPos getDefaultPos() { + return client.crosshairTarget instanceof BlockHitResult blockHitResult && client.crosshairTarget.getType() == HitResult.Type.BLOCK ? blockHitResult.getBlockPos() : client.player != null ? client.player.getBlockPos() : BlockPos.ORIGIN; + } + + protected abstract static class AbstractWaypointEntry extends ElementListWidget.Entry<AbstractWaypointEntry> { + } + + protected class WaypointCategoryEntry extends AbstractWaypointEntry { + private WaypointCategory category; + private final List<ClickableWidget> children; + private final CheckboxWidget enabled; + private final TextFieldWidget nameField; + private final ButtonWidget buttonNewWaypoint; + private final ButtonWidget buttonDelete; + + public WaypointCategoryEntry() { + this(new WaypointCategory("New Category", island, new ArrayList<>())); + } + + public WaypointCategoryEntry(WaypointCategory category) { + this.category = category; + enabled = CheckboxWidget.builder(Text.literal(""), client.textRenderer).checked(!category.waypoints().isEmpty() && category.waypoints().stream().allMatch(screen::isEnabled)).callback((checkbox, checked) -> category.waypoints().forEach(waypoint -> screen.enabledChanged(waypoint, checked))).build(); + nameField = new TextFieldWidget(client.textRenderer, 70, 20, Text.literal("Name")); + nameField.setText(category.name()); + nameField.setChangedListener(this::updateName); + buttonNewWaypoint = ButtonWidget.builder(Text.translatable("skyblocker.waypoints.new"), buttonNewWaypoint -> { + WaypointEntry waypointEntry = new WaypointEntry(this); + int entryIndex; + if (getSelectedOrNull() instanceof WaypointEntry selectedWaypointEntry && selectedWaypointEntry.category == this) { + entryIndex = WaypointsListWidget.this.children().indexOf(selectedWaypointEntry) + 1; + } else { + entryIndex = WaypointsListWidget.this.children().indexOf(this) + 1; + while (entryIndex < WaypointsListWidget.this.children().size() && !(WaypointsListWidget.this.children().get(entryIndex) instanceof WaypointCategoryEntry)) { + entryIndex++; + } + } + category.waypoints().add(waypointEntry.waypoint); + WaypointsListWidget.this.children().add(entryIndex, waypointEntry); + }).width(72).build(); + buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.deleteButton"), buttonDelete -> { + int entryIndex = WaypointsListWidget.this.children().indexOf(this) + 1; + while (entryIndex < WaypointsListWidget.this.children().size() && !(WaypointsListWidget.this.children().get(entryIndex) instanceof WaypointCategoryEntry)) { + WaypointsListWidget.this.children().remove(entryIndex); + } + WaypointsListWidget.this.children().remove(this); + waypoints.remove(category); + }).width(38).build(); + children = List.of(enabled, nameField, buttonNewWaypoint, buttonDelete); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return children; + } + + @Override + public List<? extends Element> children() { + return children; + } + + private void updateName(String name) { + int index = waypoints.indexOf(category); + category = category.withName(name); + if (index >= 0) { + waypoints.set(index, category); + } + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + enabled.setPosition(x, y + 1); + nameField.setPosition(x + 22, y); + buttonNewWaypoint.setPosition(x + entryWidth - 115, y); + buttonDelete.setPosition(x + entryWidth - 38, y); + for (ClickableWidget child : children) { + child.render(context, mouseX, mouseY, tickDelta); + } + } + } + + protected class WaypointEntry extends AbstractWaypointEntry { + private final WaypointCategoryEntry category; + private NamedWaypoint waypoint; + private final List<ClickableWidget> children; + private final CheckboxWidget enabled; + private final TextFieldWidget nameField; + private final TextFieldWidget xField; + private final TextFieldWidget yField; + private final TextFieldWidget zField; + private final TextFieldWidget colorField; + private final ButtonWidget buttonDelete; + + public WaypointEntry(WaypointCategoryEntry category) { + this(category, new NamedWaypoint(getDefaultPos(), "New Waypoint", new float[]{0f, 1f, 0f})); + } + + public WaypointEntry(WaypointCategoryEntry category, NamedWaypoint waypoint) { + this.category = category; + this.waypoint = waypoint; + enabled = CheckboxWidget.builder(Text.literal(""), client.textRenderer).checked(screen.isEnabled(waypoint)).callback((checkbox, checked) -> screen.enabledChanged(waypoint, checked)).build(); + nameField = new TextFieldWidget(client.textRenderer, 65, 20, Text.literal("Name")); + nameField.setText(waypoint.getName().getString()); + nameField.setChangedListener(this::updateName); + xField = new TextFieldWidget(client.textRenderer, 26, 20, Text.literal("X")); + xField.setText(Integer.toString(waypoint.pos.getX())); + xField.setTextPredicate(this::checkInt); + xField.setChangedListener(this::updateX); + yField = new TextFieldWidget(client.textRenderer, 26, 20, Text.literal("Y")); + yField.setText(Integer.toString(waypoint.pos.getY())); + yField.setTextPredicate(this::checkInt); + yField.setChangedListener(this::updateY); + zField = new TextFieldWidget(client.textRenderer, 26, 20, Text.literal("Z")); + zField.setText(Integer.toString(waypoint.pos.getZ())); + zField.setTextPredicate(this::checkInt); + zField.setChangedListener(this::updateZ); + colorField = new TextFieldWidget(client.textRenderer, 56, 20, Text.literal("Color")); + colorField.setText(String.format("%02X%02X%02X%02X", (int) (waypoint.alpha * 255), (int) (waypoint.getColorComponents()[0] * 255), (int) (waypoint.getColorComponents()[1] * 255), (int) (waypoint.getColorComponents()[2] * 255))); + colorField.setChangedListener(this::updateColor); + buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.deleteButton"), button -> { + category.category.waypoints().remove(waypoint); + WaypointsListWidget.this.children().remove(this); + }).width(38).build(); + children = List.of(enabled, nameField, xField, yField, zField, colorField, buttonDelete); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return children; + } + + @Override + public List<? extends Element> children() { + return children; + } + + private void updateName(String name) { + if (waypoint.name.getString().equals(name)) return; + int index = category.category.waypoints().indexOf(waypoint); + waypoint = waypoint.withName(name); + if (index >= 0) { + category.category.waypoints().set(index, waypoint); + } + } + + private boolean checkInt(String string) { + try { + parseEmptiableInt(string); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + private void updateX(String xString) { + updateInt(xString, waypoint.pos.getX(), waypoint::withX); + } + + private void updateY(String yString) { + updateInt(yString, waypoint.pos.getY(), waypoint::withY); + } + + private void updateZ(String zString) { + updateInt(zString, waypoint.pos.getZ(), waypoint::withZ); + } + + private void updateInt(String newValueString, int currentValue, Int2ObjectFunction<NamedWaypoint> wither) { + try { + int index = category.category.waypoints().indexOf(waypoint); + int newValue = parseEmptiableInt(newValueString); + if (newValue == currentValue) return; + waypoint = wither.apply(newValue); + if (index >= 0) { + category.category.waypoints().set(index, waypoint); + } + } catch (NumberFormatException e) { + Waypoints.LOGGER.warn("[Skyblocker Waypoints] Failed to parse integer: {}", newValueString, e); + } + } + + private void updateColor(String colorString) { + try { + int index = category.category.waypoints().indexOf(waypoint); + int colorInt = parseEmptiableInt(colorString, 16); + float[] colorComponents = {((colorInt & 0x00FF0000) >> 16) / 255f, ((colorInt & 0x0000FF00) >> 8) / 255f, (colorInt & 0x000000FF) / 255f}; + float alpha = ((colorInt & 0xFF000000) >>> 24) / 255f; + if (Arrays.equals(waypoint.getColorComponents(), colorComponents) && waypoint.alpha == alpha) return; + waypoint = waypoint.withColor(colorComponents, alpha); + if (index >= 0) { + category.category.waypoints().set(index, waypoint); + } + } catch (NumberFormatException e) { + Waypoints.LOGGER.warn("[Skyblocker Waypoints] Failed to parse color: {}", colorString, e); + } + } + + private int parseEmptiableInt(String value) throws NumberFormatException { + return value.isEmpty() || value.equals("-") ? 0 : Integer.parseInt(value); + } + + @SuppressWarnings("SameParameterValue") + private int parseEmptiableInt(String value, int radix) throws NumberFormatException { + return value.isEmpty() || value.equals("-") ? 0 : Integer.parseInt(value, radix); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + context.drawTextWithShadow(client.textRenderer, "X:", width / 2 - 56, y + 6, 0xFFFFFF); + context.drawTextWithShadow(client.textRenderer, "Y:", width / 2 - 19, y + 6, 0xFFFFFF); + context.drawTextWithShadow(client.textRenderer, "Z:", width / 2 + 18, y + 6, 0xFFFFFF); + context.drawTextWithShadow(client.textRenderer, "#", x + entryWidth - 105, y + 6, 0xFFFFFF); + enabled.setPosition(x + 10, y + 1); + nameField.setPosition(x + 32, y); + xField.setPosition(width / 2 - 48, y); + yField.setPosition(width / 2 - 11, y); + zField.setPosition(width / 2 + 26, y); + colorField.setPosition(x + entryWidth - 99, y); + buttonDelete.setPosition(x + entryWidth - 38, y); + for (ClickableWidget child : children) { + child.render(context, mouseX, mouseY, tickDelta); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsScreen.java new file mode 100644 index 00000000..23a24361 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsScreen.java @@ -0,0 +1,76 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import de.hysky.skyblocker.utils.waypoint.NamedWaypoint; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.GridWidget; +import net.minecraft.client.gui.widget.SimplePositioningWidget; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; + +public class WaypointsScreen extends AbstractWaypointsScreen<Screen> { + private ButtonWidget buttonNew; + private ButtonWidget buttonDone; + + public WaypointsScreen(Screen parent) { + super(Text.translatable("skyblocker.waypoints.config"), parent, Waypoints.waypointsDeepCopy()); + } + + @Override + protected void init() { + super.init(); + GridWidget gridWidget = new GridWidget(); + gridWidget.getMainPositioner().marginX(5).marginY(2); + GridWidget.Adder adder = gridWidget.createAdder(2); + adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.share"), buttonShare -> client.setScreen(new WaypointsShareScreen(this, waypoints))).build()); + buttonNew = adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.newCategory"), buttonNew -> waypointsListWidget.addWaypointCategoryAfterSelected()).build()); + adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> close()).build()); + buttonDone = adder.add(ButtonWidget.builder(ScreenTexts.DONE, button -> { + saveWaypoints(); + close(); + }).build()); + gridWidget.refreshPositions(); + SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); + gridWidget.forEachChild(this::addDrawableChild); + updateButtons(); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 16, 0xFFFFFF); + } + + @Override + protected boolean isEnabled(NamedWaypoint waypoint) { + return waypoint.shouldRender(); + } + + @Override + protected void enabledChanged(NamedWaypoint waypoint, boolean enabled) { + waypoint.setShouldRender(enabled); + } + + private void saveWaypoints() { + Waypoints.waypoints.clear(); + Waypoints.waypoints.putAll(waypoints); + Waypoints.saveWaypoints(client); + } + + @Override + public void close() { + assert client != null; + if (!waypoints.equals(Waypoints.waypoints)) { + client.setScreen(new ConfirmScreen(confirmedAction -> client.setScreen(confirmedAction ? parent : this), + Text.translatable("text.skyblocker.quit_config"), + Text.translatable("text.skyblocker.quit_config_sure"), + Text.translatable("text.skyblocker.quit_discard"), + ScreenTexts.CANCEL + )); + } else { + client.setScreen(parent); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java new file mode 100644 index 00000000..aee21ec8 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java @@ -0,0 +1,86 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import com.google.common.collect.Multimap; +import de.hysky.skyblocker.utils.waypoint.NamedWaypoint; +import de.hysky.skyblocker.utils.waypoint.WaypointCategory; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.GridWidget; +import net.minecraft.client.gui.widget.SimplePositioningWidget; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class WaypointsShareScreen extends AbstractWaypointsScreen<WaypointsScreen> { + private final Set<NamedWaypoint> selectedWaypoints = new HashSet<>(); + + protected WaypointsShareScreen(WaypointsScreen parent, Multimap<String, WaypointCategory> waypoints) { + super(Text.translatable("skyblocker.waypoints.shareWaypoints"), parent, waypoints, parent.island); + } + + @Override + protected void init() { + super.init(); + GridWidget gridWidget = new GridWidget(); + gridWidget.getMainPositioner().marginX(5).marginY(2); + GridWidget.Adder adder = gridWidget.createAdder(2); + adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.importWaypointsSkytils"), buttonImport -> { + try { + List<WaypointCategory> waypointCategories = Waypoints.fromSkytilsBase64(client.keyboard.getClipboard(), island); + for (WaypointCategory waypointCategory : waypointCategories) { + selectedWaypoints.addAll(waypointCategory.waypoints()); + waypoints.put(waypointCategory.island(), waypointCategory); + } + waypointsListWidget.updateEntries(); + SystemToast.show(client.getToastManager(), Waypoints.WAYPOINTS_TOAST_TYPE, Text.translatable("skyblocker.waypoints.importSuccess"), Text.translatable("skyblocker.waypoints.importSuccessText", waypointCategories.stream().map(WaypointCategory::waypoints).mapToInt(List::size).sum(), waypointCategories.size())); + } catch (Exception e) { + Waypoints.LOGGER.error("[Skyblocker Waypoints] Encountered exception while parsing Skytils waypoint data", e); + SystemToast.show(client.getToastManager(), Waypoints.WAYPOINTS_TOAST_TYPE, Text.translatable("skyblocker.waypoints.importError"), Text.translatable("skyblocker.waypoints.importErrorText")); + } + }).tooltip(Tooltip.of(Text.translatable("skyblocker.waypoints.importWaypointsSkytils.tooltip"))).build()); + adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.importWaypointsSnoopy"), buttonImport -> { + }).tooltip(Tooltip.of(Text.translatable("skyblocker.waypoints.importWaypointsSnoopy.tooltip"))).build()); + adder.add(ButtonWidget.builder(ScreenTexts.BACK, buttonBack -> close()).build()); + adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.exportWaypointsSkytils"), buttonExport -> { + try { + List<WaypointCategory> waypointCategories = waypoints.values().stream().filter(waypointCategory -> waypointCategory.island().equals(island)).map(WaypointCategory.filter(selectedWaypoints::contains)).filter(waypointCategory -> !waypointCategory.waypoints().isEmpty()).toList(); + client.keyboard.setClipboard(Waypoints.toSkytilsBase64(waypointCategories)); + SystemToast.show(client.getToastManager(), Waypoints.WAYPOINTS_TOAST_TYPE, Text.translatable("skyblocker.waypoints.exportSuccess"), Text.translatable("skyblocker.waypoints.exportSuccessText", waypointCategories.stream().map(WaypointCategory::waypoints).mapToInt(List::size).sum(), waypointCategories.size())); + } catch (Exception e) { + Waypoints.LOGGER.error("[Skyblocker Waypoints] Encountered exception while serializing Skytils waypoint data", e); + SystemToast.show(client.getToastManager(), Waypoints.WAYPOINTS_TOAST_TYPE, Text.translatable("skyblocker.waypoints.exportError"), Text.translatable("skyblocker.waypoints.exportErrorText")); + } + }).tooltip(Tooltip.of(Text.translatable("skyblocker.waypoints.exportWaypointsSkytils.tooltip"))).build()); + gridWidget.refreshPositions(); + SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); + gridWidget.forEachChild(this::addDrawableChild); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 16, 0xFFFFFF); + } + + @Override + protected boolean isEnabled(NamedWaypoint waypoint) { + return selectedWaypoints.contains(waypoint); + } + + @Override + protected void enabledChanged(NamedWaypoint waypoint, boolean enabled) { + if (enabled) selectedWaypoints.add(waypoint); + else selectedWaypoints.remove(waypoint); + } + + @SuppressWarnings("DataFlowIssue") + @Override + public void close() { + client.setScreen(parent); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java b/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java new file mode 100644 index 00000000..5f65c336 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java @@ -0,0 +1,172 @@ +package de.hysky.skyblocker.utils; + +import java.nio.ByteBuffer; +import java.security.PrivateKey; +import java.security.Signature; +import java.util.Base64; +import java.util.Objects; +import java.util.UUID; + +import de.hysky.skyblocker.mixins.accessors.MinecraftClientAccessor; +import net.minecraft.client.session.ProfileKeys; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import com.google.gson.JsonParser; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.SharedConstants; +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.encryption.PlayerKeyPair; +import net.minecraft.text.Text; +import net.minecraft.util.Uuids; +import net.minecraft.util.dynamic.Codecs; + +/** + * This class is responsible for communicating with the API to retrieve a fully custom token used to gain access to more privileged APIs + * such as the Hypixel API Proxy. The main point of this is to verify that a person is most likely playing Minecraft, and thus is likely to be + * using the mod, and not somebody who is attempting to freeload off of our services. + */ +public class ApiAuthentication { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final String MINECRAFT_VERSION = SharedConstants.getGameVersion().getName(); + private static final String AUTH_URL = "https://hysky.de/api/aaron/authenticate"; + private static final String CONTENT_TYPE = "application/json"; + private static final String ALGORITHM = "SHA256withRSA"; + + private static TokenInfo tokenInfo = null; + + public static void init() { + //Update token after the profileKeys instance is initialized + ClientLifecycleEvents.CLIENT_STARTED.register(_client -> updateToken()); + } + + /** + * Refreshes the token by fetching the player's key pair from the Minecraft Services API. + * + * We use the player's uuid, public key, public key signature, public key expiry date, and randomly signed data by the private key to verify the identity/legitimacy + * of the player without the server needing to handle any privileged information. + * + * Mojang provides a signature for each key pair which is comprised of the player's uuid, public key, and expiry time so we can easily validate if the key pair + * was generated by Mojang and is tied to said player. For information about what the randomly signed data is used for and why see {@link #getRandomSignedData(PrivateKey)} + */ + private static void updateToken() { + ProfileKeys profileKeys = ((MinecraftClientAccessor) CLIENT).getProfileKeys(); + //The fetching runs async in ProfileKeysImpl#getKeyPair + profileKeys.fetchKeyPair().thenAcceptAsync(playerKeypairOpt -> { + if (playerKeypairOpt.isPresent()) { + PlayerKeyPair playerKeyPair = playerKeypairOpt.get(); + + //The key header and footer can be sent but that doesn't matter to the server + String publicKey = Base64.getMimeEncoder().encodeToString(playerKeyPair.publicKey().data().key().getEncoded()); + byte[] publicKeySignature = playerKeyPair.publicKey().data().keySignature(); + long expiresAt = playerKeyPair.publicKey().data().expiresAt().toEpochMilli(); + + TokenRequest.KeyPairInfo keyPairInfo = new TokenRequest.KeyPairInfo(Objects.requireNonNull(CLIENT.getSession().getUuidOrNull()), publicKey, publicKeySignature, expiresAt); + TokenRequest.SignedData signedData = Objects.requireNonNull(getRandomSignedData(playerKeyPair.privateKey())); + TokenRequest tokenRequest = new TokenRequest(keyPairInfo, signedData, SkyblockerMod.SKYBLOCKER_MOD.getMetadata().getId(), MINECRAFT_VERSION, SkyblockerMod.VERSION); + + String request = SkyblockerMod.GSON.toJson(TokenRequest.CODEC.encodeStart(JsonOps.INSTANCE, tokenRequest).getOrThrow()); + + try { + tokenInfo = TokenInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(Http.sendPostRequest(AUTH_URL, request, CONTENT_TYPE))).getOrThrow(); + int refreshAtTicks = (int) (((tokenInfo.expiresAt() - tokenInfo.issuedAt()) / 1000L) - 300L) * 20; //Refresh 5 minutes before expiry date + + Scheduler.INSTANCE.schedule(ApiAuthentication::updateToken, refreshAtTicks, true); + } catch (Exception e) { + //Try again in 1 minute + logErrorAndScheduleRetry(Text.translatable("skyblocker.api.token.authFailure"), 60 * 20, "[Skyblocker Api Auth] Failed to refresh the api token! Some features might not work :(", e); + } + } else { + //The Minecraft Services API is probably down so we will retry in 5 minutes, either that or your access token has expired (game open for 24h) and you need to restart. + logErrorAndScheduleRetry(Text.translatable("skyblocker.api.token.noProfileKeys"), 300 * 20, "[Skyblocker Api Auth] Failed to fetch profile keys! Some features may not work temporarily :( (Has your game been open for more than 24 hours? If so restart.)"); + } + }).exceptionally(throwable -> { + //Try again in 1 minute + logErrorAndScheduleRetry(Text.translatable("skyblocker.api.token.authFailure"), 60 * 20, "[Skyblocker Api Auth] Encountered an unexpected exception while refreshing the api token!", throwable); + + return null; + }); + } + + /** + * Signs a string of random data with the key pair's private key. This is required to know if you are the real holder of the key pair or not. Why? Because your public key, + * public key signature, and expiry time are forwarded by the server to all players on the same server as you. This means that a malicious + * individual could scrape key pairs and pretend to be someone else when requesting a token from our API. So by signing data with the private key, + * the server can use the public key to verify its integrity which proves that the requester is the true holder of the complete key pair and not someone trying to pose as them for malicious purposes. + */ + private static TokenRequest.SignedData getRandomSignedData(PrivateKey privateKey) { + try { + Signature signature = Signature.getInstance(ALGORITHM); + UUID uuid = UUID.randomUUID(); + ByteBuffer buf = ByteBuffer.allocate(16) + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()); + + signature.initSign(privateKey); + signature.update(buf.array()); + + byte[] signedData = signature.sign(); + + return new TokenRequest.SignedData(buf.array(), signedData); + } catch (Exception e) { + LOGGER.error("[Skyblocker Api Auth] Failed to sign random data!", e); + } + + //This should never ever be the case, since we are signing data that is not invalid in any case + return null; + } + + private static void logErrorAndScheduleRetry(Text warningMessage, int retryAfter, String logMessage, Object... logArgs) { + LOGGER.error(logMessage, logArgs); + Scheduler.INSTANCE.schedule(ApiAuthentication::updateToken, retryAfter, true); + + if (CLIENT.player != null) CLIENT.player.sendMessage(Constants.PREFIX.get().append(warningMessage)); + } + + @Nullable + public static String getToken() { + return tokenInfo != null ? tokenInfo.token() : null; + } + + private record TokenRequest(KeyPairInfo keyPairInfo, SignedData signedData, String mod, String minecraftVersion, String modVersion) { + private static final Codec<TokenRequest> CODEC = RecordCodecBuilder.create(instance -> instance.group( + KeyPairInfo.CODEC.fieldOf("keyPair").forGetter(TokenRequest::keyPairInfo), + SignedData.CODEC.fieldOf("signedData").forGetter(TokenRequest::signedData), + Codec.STRING.fieldOf("mod").forGetter(TokenRequest::mod), + Codec.STRING.fieldOf("minecraftVersion").forGetter(TokenRequest::minecraftVersion), + Codec.STRING.fieldOf("modVersion").forGetter(TokenRequest::modVersion)) + .apply(instance, TokenRequest::new)); + + private record KeyPairInfo(UUID uuid, String publicKey, byte[] publicKeySignature, long expiresAt) { + private static final Codec<KeyPairInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group( + Uuids.STRING_CODEC.fieldOf("uuid").forGetter(KeyPairInfo::uuid), + Codec.STRING.fieldOf("publicKey").forGetter(KeyPairInfo::publicKey), + Codecs.BASE_64.fieldOf("publicKeySignature").forGetter(KeyPairInfo::publicKeySignature), + Codec.LONG.fieldOf("expiresAt").forGetter(KeyPairInfo::expiresAt)) + .apply(instance, KeyPairInfo::new)); + } + + private record SignedData(byte[] original, byte[] signed) { + private static final Codec<SignedData> CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codecs.BASE_64.fieldOf("original").forGetter(SignedData::original), + Codecs.BASE_64.fieldOf("signed").forGetter(SignedData::signed)) + .apply(instance, SignedData::new)); + } + } + + private record TokenInfo(String token, long issuedAt, long expiresAt) { + private static final Codec<TokenInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("token").forGetter(TokenInfo::token), + Codec.LONG.fieldOf("issuedAt").forGetter(TokenInfo::issuedAt), + Codec.LONG.fieldOf("expiresAt").forGetter(TokenInfo::expiresAt)) + .apply(instance, TokenInfo::new)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java b/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java index c63af3ba..93e314a7 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java @@ -1,16 +1,14 @@ package de.hysky.skyblocker.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.gson.JsonParser; import com.mojang.util.UndashedUuid; - import de.hysky.skyblocker.utils.Http.ApiResponse; import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.MinecraftClient; import net.minecraft.client.session.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /* * Contains only basic helpers for using Http APIs diff --git a/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java b/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java index 0196edf2..2c8f5e4a 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java @@ -1,8 +1,11 @@ package de.hysky.skyblocker.utils; +import net.minecraft.util.DyeColor; + 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. */ @@ -13,4 +16,11 @@ public class ColorUtils { (color & 0xFF) / 255f }; } + + /** + * @param dye The dye from which the entity color will be used for the components. + */ + public static float[] getFloatComponents(DyeColor dye) { + return getFloatComponents(dye.getEntityColor()); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java index 871eac78..1adf75d3 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Http.java +++ b/src/main/java/de/hysky/skyblocker/utils/Http.java @@ -1,5 +1,10 @@ package de.hysky.skyblocker.utils; +import de.hysky.skyblocker.SkyblockerMod; +import net.minecraft.SharedConstants; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -16,11 +21,6 @@ import java.time.Duration; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; -import org.jetbrains.annotations.NotNull; - -import de.hysky.skyblocker.SkyblockerMod; -import net.minecraft.SharedConstants; - /** * @implNote All http requests are sent using HTTP 2 */ @@ -33,15 +33,18 @@ public class Http { .followRedirects(Redirect.NORMAL) .build(); - private static ApiResponse sendCacheableGetRequest(String url) throws IOException, InterruptedException { - HttpRequest request = HttpRequest.newBuilder() + private static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .GET() .header("Accept", "application/json") .header("Accept-Encoding", "gzip, deflate") .header("User-Agent", USER_AGENT) .version(Version.HTTP_2) - .uri(URI.create(url)) - .build(); + .uri(URI.create(url)); + + if (token != null) requestBuilder.header("Token", token); + + HttpRequest request = requestBuilder.build(); HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream()); InputStream decodedInputStream = getDecodedInputStream(response); @@ -69,7 +72,7 @@ public class Http { } public static String sendGetRequest(String url) throws IOException, InterruptedException { - return sendCacheableGetRequest(url).content(); + return sendCacheableGetRequest(url, null).content(); } public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException { @@ -84,8 +87,27 @@ public class Http { return response.headers(); } + public static String sendPostRequest(String url, String requestBody, String contentType) throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .POST(BodyPublishers.ofString(requestBody)) + .header("Accept", contentType) + .header("Accept-Encoding", "gzip, deflate") + .header("Content-Type", contentType) + .header("User-Agent", USER_AGENT) + .version(Version.HTTP_2) + .uri(URI.create(url)) + .build(); + + HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream()); + InputStream decodedInputStream = getDecodedInputStream(response); + + String responseBody = new String(decodedInputStream.readAllBytes()); + + return responseBody; + } + public static ApiResponse sendName2UuidRequest(String name) throws IOException, InterruptedException { - return sendCacheableGetRequest(NAME_2_UUID + name); + return sendCacheableGetRequest(NAME_2_UUID + name, null); } /** @@ -96,7 +118,7 @@ public class Http { * @implNote the {@code v2} prefix is automatically added */ public static ApiResponse sendHypixelRequest(String endpoint, @NotNull String query) throws IOException, InterruptedException { - return sendCacheableGetRequest(HYPIXEL_PROXY + endpoint + query); + return sendCacheableGetRequest(HYPIXEL_PROXY + endpoint + query, ApiAuthentication.getToken()); } private static InputStream getDecodedInputStream(HttpResponse<InputStream> response) { diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 13b28808..de7a0f9e 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -1,5 +1,7 @@ package de.hysky.skyblocker.utils; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; @@ -8,10 +10,15 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import it.unimi.dsi.fastutil.ints.IntIntPair; +import it.unimi.dsi.fastutil.longs.LongBooleanPair; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.component.ComponentChanges; +import net.minecraft.component.ComponentHolder; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.LoreComponent; import net.minecraft.component.type.NbtComponent; @@ -20,7 +27,6 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtElement; import net.minecraft.registry.Registries; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.Text; @@ -29,13 +35,7 @@ import net.minecraft.util.dynamic.Codecs; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -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; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -46,8 +46,6 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit public class ItemUtils { public static final String ID = "id"; public static final String UUID = "uuid"; - private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH); - private static final DateTimeFormatter OLD_OBTAINED_DATE_FORMAT = DateTimeFormatter.ofPattern("M/d/yy h:m a").withZone(ZoneId.of("UTC")).localizedBy(Locale.ENGLISH); public static final Pattern NOT_DURABILITY = Pattern.compile("[^0-9 /]"); public static final Predicate<String> FUEL_PREDICATE = line -> line.contains("Fuel: "); private static final Codec<RegistryEntry<Item>> EMPTY_ALLOWING_ITEM_CODEC = Registries.ITEM.getEntryCodec(); @@ -65,7 +63,7 @@ public class ItemUtils { } @SuppressWarnings("deprecation") - public static NbtCompound getCustomData(@NotNull ItemStack stack) { + public static NbtCompound getCustomData(@NotNull ComponentHolder stack) { return stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).getNbt(); } @@ -75,7 +73,7 @@ public class ItemUtils { * @param stack the item stack to get the internal name from * @return an optional containing the internal name of the item stack */ - public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) { + public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) { NbtCompound customData = getCustomData(stack); return customData.contains(ID) ? Optional.of(customData.getString(ID)) : Optional.empty(); } @@ -86,7 +84,7 @@ public class ItemUtils { * @param stack the item stack to get the internal name from * @return the internal name of the item stack, or an empty string if the item stack is null or does not have an internal name */ - public static String getItemId(@NotNull ItemStack stack) { + public static String getItemId(@NotNull ItemStack stack) { return getCustomData(stack).getString(ID); } @@ -96,7 +94,7 @@ public class ItemUtils { * @param stack the item stack to get the UUID from * @return an optional containing the UUID of the item stack */ - public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) { + public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) { NbtCompound customData = getCustomData(stack); return customData.contains(UUID) ? Optional.of(customData.getString(UUID)) : Optional.empty(); } @@ -107,11 +105,46 @@ public class ItemUtils { * @param stack the item stack to get the UUID from * @return the UUID of the item stack, or an empty string if the item stack is null or does not have a UUID */ - public static String getItemUuid(@NotNull ItemStack stack) { + public static String getItemUuid(@NotNull ComponentHolder stack) { return getCustomData(stack).getString(UUID); } /** + * Gets the bazaar sell price or the lowest bin based on the id of the item stack. + * + * @return An {@link LongBooleanPair} with the {@code left long} representing the item's price, + * and the {@code right boolean} indicating if the price was based on complete data. + */ + public static DoubleBooleanPair getItemPrice(@NotNull ItemStack stack) { + return getItemPrice(getItemId(stack)); + } + + /** + * Gets the bazaar sell price or the lowest bin of the item with the specified id. + * + * @return An {@link LongBooleanPair} with the {@code left long} representing the item's price, + * and the {@code right boolean} indicating if the price was based on complete data. + */ + public static DoubleBooleanPair getItemPrice(@Nullable String id) { + JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData(); + JsonObject lowestBinPrices = TooltipInfoType.LOWEST_BINS.getData(); + + if (id == null || id.isEmpty() || bazaarPrices == null || lowestBinPrices == null) return DoubleBooleanPair.of(0, false); + + if (bazaarPrices.has(id)) { + JsonElement sellPrice = bazaarPrices.get(id).getAsJsonObject().get("sellPrice"); + boolean isPriceNull = sellPrice.isJsonNull(); + return DoubleBooleanPair.of(isPriceNull ? 0 : sellPrice.getAsDouble(), !isPriceNull); + } + + if (lowestBinPrices.has(id)) { + return DoubleBooleanPair.of(lowestBinPrices.get(id).getAsDouble(), true); + } + + return DoubleBooleanPair.of(0, false); + } + + /** * This method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum. * Currently, there are two types of string timestamps the legacy which is built like this * "dd/MM/yy hh:mm" ("25/04/20 16:38") and the current which is built like this @@ -126,21 +159,11 @@ public class ItemUtils { * * @param stack the item under the pointer * @return if the item have a "Timestamp" it will be shown formated on the tooltip + * @deprecated use {@link ObtainedDateTooltip#getTimestamp(ItemStack)} instead */ - public static String getTimestamp(ItemStack stack) { - NbtCompound customData = getCustomData(stack); - - if (customData != null && customData.contains("timestamp", NbtElement.LONG_TYPE)) { - Instant date = Instant.ofEpochMilli(customData.getLong("timestamp")); - return OBTAINED_DATE_FORMATTER.format(date); - } - - if (customData != null && customData.contains("timestamp", NbtElement.STRING_TYPE)) { - TemporalAccessor date = OLD_OBTAINED_DATE_FORMAT.parse(customData.getString("timestamp")); - return OBTAINED_DATE_FORMATTER.format(date); - } - - return ""; + @Deprecated + public static String getTimestamp(ItemStack stack) { + return ObtainedDateTooltip.getTimestamp(stack); } public static boolean hasCustomDurability(@NotNull ItemStack stack) { @@ -198,7 +221,7 @@ public class ItemUtils { public static List<Text> getLore(ItemStack item) { return item.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines(); } - + public static PropertyMap propertyMapWithTexture(String textureValue) { return Codecs.GAME_PROFILE_PROPERTY_MAP.parse(JsonOps.INSTANCE, JsonParser.parseString("[{\"name\":\"textures\",\"value\":\"" + textureValue + "\"}]")).getOrThrow(); } @@ -207,12 +230,12 @@ public class ItemUtils { if (!stack.isOf(Items.PLAYER_HEAD) || !stack.contains(DataComponentTypes.PROFILE)) return ""; ProfileComponent profile = stack.get(DataComponentTypes.PROFILE); - String texture = profile.properties().get("textures").stream() + if (profile == null) return ""; + + return profile.properties().get("textures").stream() .map(Property::value) .findFirst() .orElse(""); - - return texture; } public static Optional<String> getHeadTextureOptional(ItemStack stack) { diff --git a/src/main/java/de/hysky/skyblocker/utils/PosUtils.java b/src/main/java/de/hysky/skyblocker/utils/PosUtils.java index 73ada889..4ca37a83 100644 --- a/src/main/java/de/hysky/skyblocker/utils/PosUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/PosUtils.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.utils; +import com.google.gson.JsonObject; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -17,6 +18,10 @@ public final class PosUtils { return new BlockPos(Integer.parseInt(posArray[0]), Integer.parseInt(posArray[1]), Integer.parseInt(posArray[2])); } + public static BlockPos parsePosJson(JsonObject posJson) { + return new BlockPos(posJson.get("x").getAsInt(), posJson.get("y").getAsInt(), posJson.get("z").getAsInt()); + } + public static String getPosString(BlockPos blockPos) { return blockPos.getX() + "," + blockPos.getY() + "," + blockPos.getZ(); } diff --git a/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java b/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java index 0a1f238a..aa7a0492 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java @@ -4,7 +4,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import de.hysky.skyblocker.SkyblockerMod; import it.unimi.dsi.fastutil.objects.ObjectLongPair; -import net.minecraft.client.MinecraftClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,17 +16,24 @@ public class ProfileUtils { private static final long HYPIXEL_API_COOLDOWN = 300000; // 5min = 300000 public static Map<String, ObjectLongPair<JsonObject>> players = new HashMap<>(); - - public static void init() { - updateProfile(); - } - - public static CompletableFuture<JsonObject> updateProfile() { - return updateProfile(MinecraftClient.getInstance().getSession().getUsername()); + public static Map<String, ObjectLongPair<JsonObject>> profiles = new HashMap<>(); + + public static CompletableFuture<JsonObject> updateProfileByName(String name) { + return fetchFullProfile(name).thenApply(profile -> { + JsonObject player = profile.getAsJsonArray("profiles").asList().stream() + .map(JsonElement::getAsJsonObject) + .filter(profileObj -> profileObj.getAsJsonPrimitive("selected").getAsBoolean()) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No selected profile found!?")) + .getAsJsonObject("members").get(name).getAsJsonObject(); + + players.put(name, ObjectLongPair.of(player, System.currentTimeMillis())); + return player; + }); } - public static CompletableFuture<JsonObject> updateProfile(String name) { - ObjectLongPair<JsonObject> playerCache = players.get(name); + public static CompletableFuture<JsonObject> fetchFullProfile(String name) { + ObjectLongPair<JsonObject> playerCache = profiles.get(name); if (playerCache != null && playerCache.rightLong() + HYPIXEL_API_COOLDOWN > System.currentTimeMillis()) { return CompletableFuture.completedFuture(playerCache.left()); } @@ -36,19 +42,12 @@ public class ProfileUtils { String uuid = ApiUtils.name2Uuid(name); try (Http.ApiResponse response = Http.sendHypixelRequest("skyblock/profiles", "?uuid=" + uuid)) { if (!response.ok()) { - throw new IllegalStateException("Failed to get profile uuid for players " + name + "! Response: " + response.content()); + throw new IllegalStateException("Failed to get profile uuid for player: " + name + "! Response: " + response.content()); } - JsonObject responseJson = SkyblockerMod.GSON.fromJson(response.content(), JsonObject.class); - - JsonObject player = responseJson.getAsJsonArray("profiles").asList().stream() - .map(JsonElement::getAsJsonObject) - .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean()) - .findFirst() - .orElseThrow(() -> new IllegalStateException("No selected profile found!?")) - .getAsJsonObject("members").get(uuid).getAsJsonObject(); + JsonObject profile = SkyblockerMod.GSON.fromJson(response.content(), JsonObject.class); + profiles.put(name, ObjectLongPair.of(profile, System.currentTimeMillis())); - players.put(name, ObjectLongPair.of(player, System.currentTimeMillis())); - return player; + return profile; } catch (Exception e) { LOGGER.error("[Skyblocker Profile Utils] Failed to get Player Profile Data for players {}, is the API Down/Limited?", name, e); } diff --git a/src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java b/src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java new file mode 100644 index 00000000..007cb0b1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java @@ -0,0 +1,54 @@ +package de.hysky.skyblocker.utils; + +public class RomanNumerals { + private RomanNumerals() { + } + private static int getDecimalValue(char romanChar) { + return switch (romanChar) { + case 'I' -> 1; + case 'V' -> 5; + case 'X' -> 10; + case 'L' -> 50; + case 'C' -> 100; + case 'D' -> 500; + case 'M' -> 1000; + default -> 0; + }; + } + + /** + * Checks if a string is a valid roman numeral. + * It's the caller's responsibility to clean up the string before calling this method (such as trimming it). + * @param romanNumeral The roman numeral to check. + * @return True if the string is a valid roman numeral, false otherwise. + * @implNote This will only check if the string contains valid roman numeral characters. It won't check if the numeral is well-formed. + */ + public static boolean isValidRomanNumeral(String romanNumeral) { + if (romanNumeral == null || romanNumeral.isEmpty()) return false; + for (int i = 0; i < romanNumeral.length(); i++) { + if (getDecimalValue(romanNumeral.charAt(i)) == 0) return false; + } + return true; + } + + /** + * Converts a roman numeral to a decimal number. + * + * @param romanNumeral The roman numeral to convert. + * @return The decimal number, or 0 if the roman numeral string has non-roman characters in it, or if the string is empty or null. + */ + public static int romanToDecimal(String romanNumeral) { + if (romanNumeral == null || romanNumeral.isEmpty()) return 0; + romanNumeral = romanNumeral.trim().toUpperCase(); + int decimal = 0; + int lastNumber = 0; + for (int i = romanNumeral.length() - 1; i >= 0; i--) { + char ch = romanNumeral.charAt(i); + int number = getDecimalValue(ch); + if (number == 0) return 0; //Malformed roman numeral + decimal = number >= lastNumber ? decimal + number : decimal - number; + lastNumber = number; + } + return decimal; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index 62a3b897..84b3cb9e 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -2,14 +2,14 @@ package de.hysky.skyblocker.utils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.mojang.util.UndashedUuid; + import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; -import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.PacketSender; @@ -19,6 +19,7 @@ import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.scoreboard.*; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.jetbrains.annotations.NotNull; @@ -234,7 +235,6 @@ public class Utils { if (!isOnSkyblock) { if (!isInjected) { isInjected = true; - ItemTooltipCallback.EVENT.register(ItemTooltip::getTooltip); } isOnSkyblock = true; SkyblockEvents.JOIN.invoker().onSkyblockJoin(); @@ -355,6 +355,23 @@ public class Utils { } } + // TODO: Combine with `ChocolateFactorySolver.formatTime` and move into `SkyblockTime`. + public static Text getDurationText(int timeInSeconds) { + int seconds = timeInSeconds % 60; + int minutes = (timeInSeconds/60) % 60; + int hours = (timeInSeconds/3600); + + MutableText time = Text.empty(); + if (hours > 0) { + time.append(hours + "h").append(" "); + } + if (hours > 0 || minutes > 0) { + time.append(minutes + "m").append(" "); + } + time.append(seconds + "s"); + return time; + } + private static void updateFromPlayerList(MinecraftClient client) { if (client.getNetworkHandler() == null) { return; @@ -492,4 +509,8 @@ public class Utils { ((MessageHandlerAccessor) client.getMessageHandler()).invokeAddToChatLog(message, Instant.now()); client.getNarratorManager().narrateSystemMessage(message); } + + public static String getUndashedUuid() { + return UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/config/DurationController.java b/src/main/java/de/hysky/skyblocker/utils/config/DurationController.java new file mode 100644 index 00000000..09edcf3c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/config/DurationController.java @@ -0,0 +1,70 @@ +package de.hysky.skyblocker.utils.config; + +import de.hysky.skyblocker.utils.Utils; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.string.IStringController; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public record DurationController(Option<Integer> option) implements IStringController<Integer> { + + private static final Pattern secondsPattern = Pattern.compile("(^|\\s)(\\d+)s(\\s|$)"); + private static final Pattern minutesPattern = Pattern.compile("(^|\\s)(\\d+)m(\\s|$)"); + private static final Pattern hoursPattern = Pattern.compile("(^|\\s)(\\d+)h(\\s|$)"); + + @Override + public String getString() { + return Utils.getDurationText(option.pendingValue()).getString(); + } + + + @Override + public void setFromString(String value) { + Matcher hoursMatcher = hoursPattern.matcher(value); + Matcher minutesMatcher = minutesPattern.matcher(value); + Matcher secondsMatcher = secondsPattern.matcher(value); + + int result = 0; + if (hoursMatcher.find()) { + result += Integer.parseInt(hoursMatcher.group(2)) * 3600; + } + if (minutesMatcher.find()) { + result += Integer.parseInt(minutesMatcher.group(2)) * 60; + } + if (secondsMatcher.find()) { + result += Integer.parseInt(secondsMatcher.group(2)); + } + option.requestSet(result); + } + + + @Override + public boolean isInputValid(String s) { + Matcher hoursMatcher = hoursPattern.matcher(s); + Matcher minutesMatcher = minutesPattern.matcher(s); + Matcher secondsMatcher = secondsPattern.matcher(s); + + int hoursCount = 0; + while (hoursMatcher.find()) hoursCount++; + int minutesCount = 0; + while (minutesMatcher.find()) minutesCount++; + int secondsCount = 0; + while (secondsMatcher.find()) secondsCount++; + + if (hoursCount == 0 && minutesCount == 0 && secondsCount == 0) return false; + if (hoursCount > 1 || minutesCount > 1 || secondsCount > 1) return false; + s = s.replaceAll(hoursPattern.pattern(), ""); + s = s.replaceAll(minutesPattern.pattern(), ""); + s = s.replaceAll(secondsPattern.pattern(), ""); + return s.isBlank(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) { + return new DurationControllerWidget(this, screen, widgetDimension); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java b/src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java new file mode 100644 index 00000000..f25cd088 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java @@ -0,0 +1,38 @@ +package de.hysky.skyblocker.utils.config; + +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.string.IStringController; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.function.Consumer; + +public class DurationControllerWidget extends StringControllerElement { + + public DurationControllerWidget(IStringController<?> control, YACLScreen screen, Dimension<Integer> dim) { + super(control, screen, dim, false); + } + + @Override + public void unfocus() { + if (control.isInputValid(inputField)) super.unfocus(); + else modifyInput(stringBuilder -> stringBuilder.replace(0, stringBuilder.length(), control.getString())); + } + + @Override + public boolean modifyInput(Consumer<StringBuilder> consumer) { + StringBuilder temp = new StringBuilder(inputField); + consumer.accept(temp); + inputField = temp.toString(); + return true; + } + + @Override + protected Text getValueText() { + Text valueText = super.getValueText(); + boolean inputValid = control.isInputValid(valueText.getString()); + return valueText.copy().formatted(inputValid ? Formatting.WHITE: Formatting.RED); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java b/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java index 3543a2f1..a9b227a1 100644 --- a/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java +++ b/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java @@ -1,25 +1,26 @@ package de.hysky.skyblocker.utils.datafixer; import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.Optional; import com.mojang.brigadier.StringReader; import com.mojang.serialization.Dynamic; +import net.minecraft.client.MinecraftClient; import net.minecraft.command.argument.ItemStringReader; import net.minecraft.command.argument.ItemStringReader.ItemResult; -import net.minecraft.component.DataComponentType; +import net.minecraft.component.ComponentType; import net.minecraft.datafixer.Schemas; import net.minecraft.datafixer.TypeReferences; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtOps; -import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.BuiltinRegistries; import net.minecraft.registry.Registries; import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.RegistryWrapper.WrapperLookup; import net.minecraft.util.Identifier; /** @@ -30,10 +31,10 @@ import net.minecraft.util.Identifier; public class ItemStackComponentizationFixer { private static final int ITEM_NBT_DATA_VERSION = 3817; private static final int ITEM_COMPONENTS_DATA_VERSION = 3825; - private static final DynamicRegistryManager REGISTRY_MANAGER = new DynamicRegistryManager.ImmutableImpl(List.of(Registries.ITEM, Registries.DATA_COMPONENT_TYPE)); + private static final WrapperLookup LOOKUP = BuiltinRegistries.createWrapperLookup(); public static ItemStack fixUpItem(NbtCompound nbt) { - Dynamic<NbtElement> dynamic = Schemas.getFixer().update(TypeReferences.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nbt), ITEM_NBT_DATA_VERSION, ITEM_COMPONENTS_DATA_VERSION); + Dynamic<NbtElement> dynamic = Schemas.getFixer().update(TypeReferences.ITEM_STACK, new Dynamic<>(getRegistryLookup().getOps(NbtOps.INSTANCE), nbt), ITEM_NBT_DATA_VERSION, ITEM_COMPONENTS_DATA_VERSION); return ItemStack.CODEC.parse(dynamic).getOrThrow(); } @@ -44,11 +45,11 @@ public class ItemStackComponentizationFixer { * @return The {@link ItemStack}'s components as a string which is in the format that the {@code /give} command accepts. */ public static String componentsAsString(ItemStack stack) { - RegistryOps<NbtElement> nbtRegistryOps = REGISTRY_MANAGER.getOps(NbtOps.INSTANCE); + RegistryOps<NbtElement> nbtRegistryOps = getRegistryLookup().getOps(NbtOps.INSTANCE); return Arrays.toString(stack.getComponentChanges().entrySet().stream().map(entry -> { @SuppressWarnings("unchecked") - DataComponentType<Object> dataComponentType = (DataComponentType<Object>) entry.getKey(); + ComponentType<Object> dataComponentType = (ComponentType<Object>) entry.getKey(); Identifier componentId = Registries.DATA_COMPONENT_TYPE.getId(dataComponentType); Optional<NbtElement> encodedComponent = dataComponentType.getCodec().encodeStart(nbtRegistryOps, entry.getValue().orElseThrow()).result(); @@ -66,17 +67,26 @@ public class ItemStackComponentizationFixer { * @return an {@link ItemStack} or {@link ItemStack#EMPTY} if there was an exception thrown. */ public static ItemStack fromComponentsString(String itemId, int count, String componentsString) { - ItemStringReader reader = new ItemStringReader(REGISTRY_MANAGER); + ItemStringReader reader = new ItemStringReader(getRegistryLookup()); try { ItemResult result = reader.consume(new StringReader(itemId + componentsString)); ItemStack stack = new ItemStack(result.item(), count); - stack.applyComponentsFrom(result.components()); + //Vanilla skips validation with /give so we will too + stack.applyUnvalidatedChanges(result.components()); return stack; } catch (Exception ignored) {} return ItemStack.EMPTY; } + + /** + * Tries to get the dynamic registry manager instance currently in use or else returns {@link #LOOKUP} + */ + public static WrapperLookup getRegistryLookup() { + MinecraftClient client = MinecraftClient.getInstance(); + return client != null && client.getNetworkHandler() != null && client.getNetworkHandler().getRegistryManager() != null ? client.getNetworkHandler().getRegistryManager() : LOOKUP; + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java index a6772fb2..a5b9bf6b 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -18,6 +18,7 @@ import net.minecraft.client.render.*; import net.minecraft.client.render.VertexFormat.DrawMode; import net.minecraft.client.texture.Scaling; import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.BufferAllocator; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.sound.SoundEvents; import net.minecraft.text.OrderedText; @@ -25,6 +26,7 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; +import net.minecraft.util.math.ColorHelper; import net.minecraft.util.math.Vec3d; import org.joml.Matrix3f; @@ -40,11 +42,12 @@ import java.lang.invoke.MethodType; public class RenderHelper { private static final Logger LOGGER = LogUtils.getLogger(); - private static final Identifier TRANSLUCENT_DRAW = new Identifier(SkyblockerMod.NAMESPACE, "translucent_draw"); + private static final Identifier TRANSLUCENT_DRAW = Identifier.of(SkyblockerMod.NAMESPACE, "translucent_draw"); private static final MethodHandle SCHEDULE_DEFERRED_RENDER_TASK = getDeferredRenderTaskHandle(); private static final Vec3d ONE = new Vec3d(1, 1, 1); private static final int MAX_OVERWORLD_BUILD_HEIGHT = 319; - private static final MinecraftClient client = MinecraftClient.getInstance(); + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final BufferAllocator ALLOCATOR = new BufferAllocator(1536); public static void init() { WorldRenderEvents.AFTER_TRANSLUCENT.addPhaseOrdering(Event.DEFAULT_PHASE, TRANSLUCENT_DRAW); @@ -95,7 +98,7 @@ public class RenderHelper { matrices.push(); matrices.translate(pos.getX() - camera.getX(), pos.getY() - camera.getY(), pos.getZ() - camera.getZ()); - BeaconBlockEntityRendererInvoker.renderBeam(matrices, context.consumers(), context.tickDelta(), context.world().getTime(), 0, MAX_OVERWORLD_BUILD_HEIGHT, colorComponents); + BeaconBlockEntityRendererInvoker.renderBeam(matrices, context.consumers(), context.tickCounter().getTickDelta(true), context.world().getTime(), 0, MAX_OVERWORLD_BUILD_HEIGHT, ColorHelper.Argb.fromFloats(1f, colorComponents[0], colorComponents[1], colorComponents[2])); matrices.pop(); } @@ -110,7 +113,6 @@ public class RenderHelper { MatrixStack matrices = context.matrixStack(); Vec3d camera = context.camera().getPos(); Tessellator tessellator = RenderSystem.renderThreadTesselator(); - BufferBuilder buffer = tessellator.getBuffer(); RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); @@ -122,9 +124,9 @@ public class RenderHelper { matrices.push(); matrices.translate(-camera.getX(), -camera.getY(), -camera.getZ()); - buffer.begin(DrawMode.LINES, VertexFormats.LINES); + BufferBuilder buffer = tessellator.begin(DrawMode.LINES, VertexFormats.LINES); WorldRenderer.drawBox(matrices, buffer, box, colorComponents[0], colorComponents[1], colorComponents[2], 1f); - tessellator.draw(); + BufferRenderer.drawWithGlobalProgram(buffer.end()); matrices.pop(); RenderSystem.lineWidth(1f); @@ -156,7 +158,6 @@ public class RenderHelper { matrices.translate(-camera.x, -camera.y, -camera.z); Tessellator tessellator = RenderSystem.renderThreadTesselator(); - BufferBuilder buffer = tessellator.getBuffer(); Matrix4f positionMatrix = matrices.peek().getPositionMatrix(); Matrix3f normalMatrix = matrices.peek().getNormalMatrix(); @@ -172,7 +173,7 @@ public class RenderHelper { RenderSystem.enableDepthTest(); RenderSystem.depthFunc(throughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); - buffer.begin(DrawMode.LINE_STRIP, VertexFormats.LINES); + BufferBuilder buffer = tessellator.begin(DrawMode.LINE_STRIP, VertexFormats.LINES); for (int i = 0; i < points.length; i++) { Vec3d nextPoint = points[i + 1 == points.length ? i - 1 : i + 1]; @@ -180,11 +181,10 @@ public class RenderHelper { buffer .vertex(positionMatrix, (float) points[i].getX(), (float) points[i].getY(), (float) points[i].getZ()) .color(colorComponents[0], colorComponents[1], colorComponents[2], alpha) - .normal(normalVec.x, normalVec.y, normalVec.z) - .next(); + .normal(normalVec.x, normalVec.y, normalVec.z); } - tessellator.draw(); + BufferRenderer.drawWithGlobalProgram(buffer.end()); matrices.pop(); GL11.glDisable(GL11.GL_LINE_SMOOTH); @@ -201,7 +201,6 @@ public class RenderHelper { matrices.translate(-camera.x, -camera.y, -camera.z); Tessellator tessellator = RenderSystem.renderThreadTesselator(); - BufferBuilder buffer = tessellator.getBuffer(); Matrix4f positionMatrix = matrices.peek().getPositionMatrix(); GL11.glEnable(GL11.GL_LINE_SMOOTH); @@ -219,22 +218,21 @@ public class RenderHelper { Vec3d offset = Vec3d.fromPolar(context.camera().getPitch(), context.camera().getYaw()); Vec3d cameraPoint = camera.add(offset); - buffer.begin(DrawMode.LINES, VertexFormats.LINES); + BufferBuilder buffer = tessellator.begin(DrawMode.LINES, VertexFormats.LINES); + Vector3f normal = new Vector3f((float) offset.x, (float) offset.y, (float) offset.z); buffer .vertex(positionMatrix, (float) cameraPoint.x , (float) cameraPoint.y, (float) cameraPoint.z) .color(colorComponents[0], colorComponents[1], colorComponents[2], alpha) - .normal(normal.x, normal.y, normal.z) - .next(); + .normal(normal.x, normal.y, normal.z); buffer .vertex(positionMatrix, (float) point.getX(), (float) point.getY(), (float) point.getZ()) .color(colorComponents[0], colorComponents[1], colorComponents[2], alpha) - .normal(normal.x, normal.y, normal.z) - .next(); + .normal(normal.x, normal.y, normal.z); - tessellator.draw(); + BufferRenderer.drawWithGlobalProgram(buffer.end()); matrices.pop(); GL11.glDisable(GL11.GL_LINE_SMOOTH); @@ -250,7 +248,6 @@ public class RenderHelper { positionMatrix.translate((float) -camera.x, (float) -camera.y, (float) -camera.z); Tessellator tessellator = RenderSystem.renderThreadTesselator(); - BufferBuilder buffer = tessellator.getBuffer(); RenderSystem.setShader(GameRenderer::getPositionColorProgram); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); @@ -259,11 +256,11 @@ public class RenderHelper { RenderSystem.disableCull(); RenderSystem.depthFunc(throughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); - buffer.begin(DrawMode.QUADS, VertexFormats.POSITION_COLOR); + BufferBuilder buffer = tessellator.begin(DrawMode.QUADS, VertexFormats.POSITION_COLOR); for (int i = 0; i < 4; i++) { - buffer.vertex(positionMatrix, (float) points[i].getX(), (float) points[i].getY(), (float) points[i].getZ()).color(colorComponents[0], colorComponents[1], colorComponents[2], alpha).next(); + buffer.vertex(positionMatrix, (float) points[i].getX(), (float) points[i].getY(), (float) points[i].getZ()).color(colorComponents[0], colorComponents[1], colorComponents[2], alpha); } - tessellator.draw(); + BufferRenderer.drawWithGlobalProgram(buffer.end()); RenderSystem.enableCull(); RenderSystem.depthFunc(GL11.GL_LEQUAL); @@ -290,20 +287,18 @@ public class RenderHelper { Matrix4f positionMatrix = new Matrix4f(); Camera camera = context.camera(); Vec3d cameraPos = camera.getPos(); - TextRenderer textRenderer = client.textRenderer; + TextRenderer textRenderer = CLIENT.textRenderer; scale *= 0.025f; positionMatrix .translate((float) (pos.getX() - cameraPos.getX()), (float) (pos.getY() - cameraPos.getY()), (float) (pos.getZ() - cameraPos.getZ())) .rotate(camera.getRotation()) - .scale(-scale, -scale, scale); + .scale(scale, -scale, scale); float xOffset = -textRenderer.getWidth(text) / 2f; - Tessellator tessellator = RenderSystem.renderThreadTesselator(); - BufferBuilder buffer = tessellator.getBuffer(); - VertexConsumerProvider.Immediate consumers = VertexConsumerProvider.immediate(buffer); + VertexConsumerProvider.Immediate consumers = VertexConsumerProvider.immediate(ALLOCATOR); RenderSystem.depthFunc(throughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); @@ -361,8 +356,8 @@ public class RenderHelper { } private static void playNotificationSound() { - if (client.player != null) { - client.player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 100f, 0.1f); + if (CLIENT.player != null) { + CLIENT.player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 100f, 0.1f); } } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractContainerMatcher.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractContainerMatcher.java new file mode 100644 index 00000000..bf255218 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractContainerMatcher.java @@ -0,0 +1,29 @@ +package de.hysky.skyblocker.utils.render.gui; + +import de.hysky.skyblocker.skyblock.ChestValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Pattern; + +public abstract class AbstractContainerMatcher { + /** + * The title of the screen must match this pattern for this to be applied. Null means it will be applied to all screens. + * @implNote Don't end your regex with a {@code $} as {@link ChestValue} appends text to the end of the title, + * so the regex will stop matching if the player uses chest value. + */ + @Nullable + public final Pattern titlePattern; + + protected AbstractContainerMatcher() { + this((Pattern) null); + } + + protected AbstractContainerMatcher(@NotNull String titlePattern) { + this(Pattern.compile(titlePattern)); + } + + protected AbstractContainerMatcher(@Nullable Pattern titlePattern) { + this.titlePattern = titlePattern; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java index e7a3e8b2..903611ae 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java @@ -12,13 +12,11 @@ import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; -import java.util.function.Consumer; - /** * A more bare-bones version of Vanilla's Popup Screen. Meant to be extended. */ public class AbstractPopupScreen extends Screen { - private static final Identifier BACKGROUND_TEXTURE = new Identifier("popup/background"); + private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("popup/background"); private final Screen backgroundScreen; protected AbstractPopupScreen(Text title, Screen backgroundScreen) { diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java index 0417dc3c..81c9ebec 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java @@ -11,17 +11,15 @@ import java.util.regex.Pattern; /** * Abstract class for gui solvers. Extend this class to add a new gui solver, like terminal solvers or experiment solvers. */ -public abstract class ContainerSolver { - private final Pattern containerName; - - protected ContainerSolver(String containerName) { - this.containerName = Pattern.compile(containerName); +public abstract class ContainerSolver extends AbstractContainerMatcher { + protected ContainerSolver(String titlePattern) { + super(titlePattern); } protected abstract boolean isEnabled(); public final Pattern getName() { - return containerName; + return titlePattern; } protected void start(GenericContainerScreen screen) { diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java new file mode 100644 index 00000000..889d3b02 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java @@ -0,0 +1,39 @@ +package de.hysky.skyblocker.utils.render.gui; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ButtonTextures; +import net.minecraft.client.gui.widget.ToggleButtonWidget; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; + +public class SideTabButtonWidget extends ToggleButtonWidget { + private static final ButtonTextures TEXTURES = new ButtonTextures(Identifier.ofVanilla("recipe_book/tab"), Identifier.ofVanilla("recipe_book/tab_selected")); + protected @NotNull ItemStack icon; + + public void setIcon(@NotNull ItemStack icon) { + this.icon = icon.copy(); + } + + public SideTabButtonWidget(int x, int y, boolean toggled, @NotNull ItemStack icon) { + super(x, y, 35, 27, toggled); + this.icon = icon.copy(); + setTextures(TEXTURES); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + if (textures == null) return; + Identifier identifier = textures.get(true, this.toggled); + int x = getX(); + if (toggled) x -= 2; + context.drawGuiTexture(identifier, x, this.getY(), this.width, this.height); + context.drawItem(icon, x + 9, getY() + 5); + } + + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + if (!isToggled()) this.setToggled(true); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java index c21485e2..cb754308 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java @@ -9,6 +9,7 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallba import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderTickCounter; import net.minecraft.util.math.MathHelper; import java.util.LinkedHashSet; @@ -81,8 +82,8 @@ public class TitleContainer { titles.remove(title); } - private static void render(DrawContext context, float tickDelta) { - render(context, titles, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y, tickDelta); + private static void render(DrawContext context, RenderTickCounter tickCounter) { + render(context, titles, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y, tickCounter.getTickDelta(true)); } protected static void render(DrawContext context, Set<Title> titles, int xPos, int yPos, float tickDelta) { diff --git a/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java b/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java index 2f5375fe..547adc5c 100644 --- a/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java +++ b/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.utils.scheduler; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; @@ -14,6 +15,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -73,18 +75,56 @@ public class Scheduler { } } + /** + * Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed. + * + * @param screenFactory the factory of the screen to open + * @return the command + */ + public static Command<FabricClientCommandSource> queueOpenScreenFactoryCommand(Function<CommandContext<FabricClientCommandSource>, Screen> screenFactory) { + return context -> queueOpenScreen(screenFactory.apply(context)); + } + + /** + * Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed. + * + * @param screenSupplier the supplier of the screen to open + * @return the command + */ public static Command<FabricClientCommandSource> queueOpenScreenCommand(Supplier<Screen> screenSupplier) { - return context -> INSTANCE.queueOpenScreen(screenSupplier); + return context -> queueOpenScreen(screenSupplier.get()); + } + + /** + * Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed. + * + * @param screen the screen to open + * @return the command + */ + public static Command<FabricClientCommandSource> queueOpenScreenCommand(Screen screen) { + return context -> queueOpenScreen(screen); } /** * Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed. * + * @deprecated Use {@link #queueOpenScreen(Screen)} instead * @param screenSupplier the supplier of the screen to open * @see #queueOpenScreenCommand(Supplier) */ - public int queueOpenScreen(Supplier<Screen> screenSupplier) { - MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screenSupplier.get())); + @Deprecated(forRemoval = true) + public static int queueOpenScreen(Supplier<Screen> screenSupplier) { + return queueOpenScreen(screenSupplier.get()); + } + + /** + * Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed. + * + * @param screen the screen to open + * @see #queueOpenScreenFactoryCommand(Function) + */ + public static int queueOpenScreen(Screen screen) { + MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screen)); return Command.SINGLE_SUCCESS; } diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java new file mode 100644 index 00000000..2f02b51f --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java @@ -0,0 +1,128 @@ +package de.hysky.skyblocker.utils.waypoint; + +import com.google.common.primitives.Floats; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.text.Text; +import net.minecraft.text.TextCodecs; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.util.Objects; +import java.util.function.Supplier; + +public class NamedWaypoint extends Waypoint { + public static final Codec<NamedWaypoint> CODEC = RecordCodecBuilder.create(instance -> instance.group( + BlockPos.CODEC.fieldOf("pos").forGetter(secretWaypoint -> secretWaypoint.pos), + TextCodecs.CODEC.fieldOf("name").forGetter(secretWaypoint -> secretWaypoint.name), + Codec.floatRange(0, 1).listOf().comapFlatMap( + colorComponentsList -> colorComponentsList.size() == 3 ? DataResult.success(Floats.toArray(colorComponentsList)) : DataResult.error(() -> "Expected 3 color components, got " + colorComponentsList.size() + " instead"), + Floats::asList + ).fieldOf("colorComponents").forGetter(secretWaypoint -> secretWaypoint.colorComponents), + Codec.FLOAT.fieldOf("alpha").forGetter(secretWaypoint -> secretWaypoint.alpha), + Codec.BOOL.fieldOf("shouldRender").forGetter(Waypoint::shouldRender) + ).apply(instance, NamedWaypoint::new)); + public static final Codec<NamedWaypoint> SKYTILS_CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.INT.fieldOf("x").forGetter(waypoint -> waypoint.pos.getX()), + Codec.INT.fieldOf("y").forGetter(waypoint -> waypoint.pos.getY()), + Codec.INT.fieldOf("z").forGetter(waypoint -> waypoint.pos.getZ()), + Codec.either(Codec.STRING, Codec.INT).xmap(either -> either.map(str -> str, Object::toString), Either::left).fieldOf("name").forGetter(waypoint -> waypoint.name.getString()), + Codec.INT.fieldOf("color").forGetter(waypoint -> (int) (waypoint.alpha * 255) << 24 | (int) (waypoint.colorComponents[0] * 255) << 16 | (int) (waypoint.colorComponents[1] * 255) << 8 | (int) (waypoint.colorComponents[2] * 255)), + Codec.BOOL.fieldOf("enabled").forGetter(Waypoint::shouldRender) + ).apply(instance, NamedWaypoint::fromSkytils)); + public final Text name; + public final Vec3d centerPos; + + public NamedWaypoint(BlockPos pos, String name, float[] colorComponents) { + this(pos, name, colorComponents, true); + } + + public NamedWaypoint(BlockPos pos, String name, float[] colorComponents, boolean shouldRender) { + this(pos, name, colorComponents, DEFAULT_HIGHLIGHT_ALPHA, shouldRender); + } + + public NamedWaypoint(BlockPos pos, String name, float[] colorComponents, float alpha, boolean shouldRender) { + this(pos, Text.of(name), colorComponents, alpha, shouldRender); + } + + public NamedWaypoint(BlockPos pos, Text name, float[] colorComponents, float alpha, boolean shouldRender) { + this(pos, name, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType, colorComponents, alpha, shouldRender); + } + + public NamedWaypoint(BlockPos pos, Text name, Supplier<Type> typeSupplier, float[] colorComponents) { + this(pos, name, typeSupplier, colorComponents, DEFAULT_HIGHLIGHT_ALPHA, true); + } + + public NamedWaypoint(BlockPos pos, Text name, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, boolean shouldRender) { + super(pos, typeSupplier, colorComponents, alpha, DEFAULT_LINE_WIDTH, true, shouldRender); + this.name = name; + this.centerPos = pos.toCenterPos(); + } + + public static NamedWaypoint fromSkytils(int x, int y, int z, String name, int color, boolean enabled) { + float alpha = ((color & 0xFF000000) >>> 24) / 255f; + if (alpha == 0) { + alpha = DEFAULT_HIGHLIGHT_ALPHA; + } + return new NamedWaypoint(new BlockPos(x, y, z), name, new float[]{((color & 0x00FF0000) >> 16) / 255f, ((color & 0x0000FF00) >> 8) / 255f, (color & 0x000000FF) / 255f}, alpha, enabled); + } + + public NamedWaypoint copy() { + return new NamedWaypoint(pos, name, typeSupplier, getColorComponents(), alpha, shouldRender()); + } + + @Override + public NamedWaypoint withX(int x) { + return new NamedWaypoint(new BlockPos(x, pos.getY(), pos.getZ()), name, typeSupplier, getColorComponents(), alpha, shouldRender()); + } + + @Override + public NamedWaypoint withY(int y) { + return new NamedWaypoint(pos.withY(y), name, typeSupplier, getColorComponents(), alpha, shouldRender()); + } + + @Override + public NamedWaypoint withZ(int z) { + return new NamedWaypoint(new BlockPos(pos.getX(), pos.getY(), z), name, typeSupplier, getColorComponents(), alpha, shouldRender()); + } + + @Override + public NamedWaypoint withColor(float[] colorComponents, float alpha) { + return new NamedWaypoint(pos, name, typeSupplier, colorComponents, alpha, shouldRender()); + } + + public Text getName() { + return name; + } + + public NamedWaypoint withName(String name) { + return new NamedWaypoint(pos, Text.literal(name), typeSupplier, getColorComponents(), alpha, shouldRender()); + } + + protected boolean shouldRenderName() { + return true; + } + + @Override + public void render(WorldRenderContext context) { + super.render(context); + if (shouldRenderName()) { + RenderHelper.renderText(context, name, centerPos.add(0, 1, 0), true); + } + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), name); + } + + @Override + public boolean equals(Object obj) { + return this == obj || super.equals(obj) && obj instanceof NamedWaypoint waypoint && name.equals(waypoint.name); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/ProfileAwareWaypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/ProfileAwareWaypoint.java index 7aa99d14..7369a2ef 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/ProfileAwareWaypoint.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/ProfileAwareWaypoint.java @@ -38,7 +38,7 @@ public class ProfileAwareWaypoint extends Waypoint { } @Override - protected float[] getColorComponents() { + public float[] getColorComponents() { return foundProfiles.contains(Utils.getProfile()) ? foundColor : missingColor; } } diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java index 622e1658..c991fb9c 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java @@ -3,9 +3,12 @@ package de.hysky.skyblocker.utils.waypoint; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.Renderable; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.util.StringIdentifiable; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; +import java.util.Arrays; +import java.util.Objects; import java.util.function.Supplier; public class Waypoint implements Renderable { @@ -15,9 +18,9 @@ public class Waypoint implements Renderable { final Box box; final Supplier<Type> typeSupplier; protected final float[] colorComponents; - final float alpha; - final float lineWidth; - final boolean throughWalls; + public final float alpha; + public final float lineWidth; + public final boolean throughWalls; private boolean shouldRender; public Waypoint(BlockPos pos, Type type, float[] colorComponents) { @@ -55,6 +58,22 @@ public class Waypoint implements Renderable { this.shouldRender = shouldRender; } + public Waypoint withX(int x) { + return new Waypoint(new BlockPos(x, pos.getY(), pos.getZ()), typeSupplier, getColorComponents(), alpha, lineWidth, throughWalls, shouldRender()); + } + + public Waypoint withY(int y) { + return new Waypoint(pos.withY(y), typeSupplier, getColorComponents(), alpha, lineWidth, throughWalls, shouldRender()); + } + + public Waypoint withZ(int z) { + return new Waypoint(new BlockPos(pos.getX(), pos.getY(), z), typeSupplier, getColorComponents(), alpha, lineWidth, throughWalls, shouldRender()); + } + + public Waypoint withColor(float[] colorComponents, float alpha) { + return new Waypoint(pos, typeSupplier, colorComponents, alpha, lineWidth, throughWalls, shouldRender()); + } + public boolean shouldRender() { return shouldRender; } @@ -71,7 +90,11 @@ public class Waypoint implements Renderable { this.shouldRender = !this.shouldRender; } - protected float[] getColorComponents() { + public void setShouldRender(boolean shouldRender) { + this.shouldRender = shouldRender; + } + + public float[] getColorComponents() { return colorComponents; } @@ -94,7 +117,17 @@ public class Waypoint implements Renderable { } } - public enum Type { + @Override + public int hashCode() { + return Objects.hash(pos, typeSupplier.get(), Arrays.hashCode(colorComponents), alpha, lineWidth, throughWalls, shouldRender); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj) || obj instanceof Waypoint other && pos.equals(other.pos) && typeSupplier.get() == other.typeSupplier.get() && Arrays.equals(colorComponents, other.colorComponents) && alpha == other.alpha && lineWidth == other.lineWidth && throughWalls == other.throughWalls && shouldRender == other.shouldRender; + } + + public enum Type implements StringIdentifiable { WAYPOINT, OUTLINED_WAYPOINT, HIGHLIGHT, @@ -102,6 +135,11 @@ public class Waypoint implements Renderable { OUTLINE; @Override + public String asString() { + return name().toLowerCase(); + } + + @Override public String toString() { return switch (this) { case WAYPOINT -> "Waypoint"; diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java new file mode 100644 index 00000000..db2a6d82 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java @@ -0,0 +1,43 @@ +package de.hysky.skyblocker.utils.waypoint; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; + +import java.util.List; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public record WaypointCategory(String name, String island, List<NamedWaypoint> waypoints) { + public static final Codec<WaypointCategory> CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(WaypointCategory::name), + Codec.STRING.fieldOf("island").forGetter(WaypointCategory::island), + NamedWaypoint.CODEC.listOf().fieldOf("waypoints").forGetter(WaypointCategory::waypoints) + ).apply(instance, WaypointCategory::new)); + public static final Codec<WaypointCategory> SKYTILS_CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(WaypointCategory::name), + Codec.STRING.fieldOf("island").forGetter(WaypointCategory::island), + NamedWaypoint.SKYTILS_CODEC.listOf().fieldOf("waypoints").forGetter(WaypointCategory::waypoints) + ).apply(instance, WaypointCategory::new)); + + public static UnaryOperator<WaypointCategory> filter(Predicate<NamedWaypoint> predicate) { + return waypointCategory -> new WaypointCategory(waypointCategory.name(), waypointCategory.island(), waypointCategory.waypoints().stream().filter(predicate).toList()); + } + + public WaypointCategory withName(String name) { + return new WaypointCategory(name, island(), waypoints()); + } + + public WaypointCategory deepCopy() { + return new WaypointCategory(name(), island(), waypoints().stream().map(NamedWaypoint::copy).collect(Collectors.toList())); + } + + public void render(WorldRenderContext context) { + for (NamedWaypoint waypoint : waypoints) { + if (waypoint.shouldRender()) { + waypoint.render(context); + } + } + } +} diff --git a/src/main/resources/assets/skyblocker/lang/de_de.json b/src/main/resources/assets/skyblocker/lang/de_de.json index cb2cb517..64c08939 100644 --- a/src/main/resources/assets/skyblocker/lang/de_de.json +++ b/src/main/resources/assets/skyblocker/lang/de_de.json @@ -39,7 +39,6 @@ "key.skyblocker.toggleA": "HUD auf Tab A umschalten", "skyblocker.config.helpers.experiments.enableSuperpairsSolver": "Superpairs Solver aktivieren", "skyblocker.config.helpers.experiments.enableUltrasequencerSolver": "Ultrasequencer Solver aktivieren", - "skyblocker.option.general.bars.barpositions": "Balkenposition Bearbeiten", "skyblocker.config.general.acceptReparty": "Automatische Annahme von Reparty", "skyblocker.config.quickNav.enableQuickNav": "Schnellnavigation aktivieren", "key.itemProtection": "Artikel schützen", diff --git a/src/main/resources/assets/skyblocker/lang/en_ca.json b/src/main/resources/assets/skyblocker/lang/en_ca.json index 5877f5e3..12f2fa3f 100644 --- a/src/main/resources/assets/skyblocker/lang/en_ca.json +++ b/src/main/resources/assets/skyblocker/lang/en_ca.json @@ -42,5 +42,8 @@ "skyblocker.bars.config.mainColor": "Main Colour", "skyblocker.bars.config.overflowColor": "Overflow Colour", "skyblocker.bars.config.textColor": "Text Colour", - "skyblocker.debug.toggledShowingInvisibleArmorStands": "Toggled showing invisible armour stands to %s" + "skyblocker.debug.toggledShowingInvisibleArmorStands": "Toggled showing invisible armour stands to %s", + "skyblocker.config.uiAndVisuals.compactDamage.critDamageGradientEnd": "Crit Damage Gradient End Colour", + "skyblocker.config.uiAndVisuals.compactDamage.critDamageGradientStart": "Crit Damage Gradient Start Colour", + "skyblocker.config.uiAndVisuals.compactDamage.normalDamageColor": "Normal Damage Colour" } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 5a63d857..0fea49ef 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -210,6 +210,8 @@ "skyblocker.config.general.itemInfoDisplay.itemRarityBackgrounds": "Item Rarity Backgrounds", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgrounds.@Tooltip": "Displays a colored background behind an item, the color represents the item's rarity.", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgroundsOpacity": "Item Rarity Backgrounds Opacity", + "skyblocker.config.general.itemInfoDisplay.slotText": "Slot Text", + "skyblocker.config.general.itemInfoDisplay.slotText.@Tooltip": "Displays information such as enchantment book level, minion level, pet level, potion level, prehistoric egg blocks walked, rancher's boots speed cap, and skill level", "skyblocker.config.general.itemList": "Item List", "skyblocker.config.general.itemList.enableItemList": "Enable Item List", @@ -251,6 +253,9 @@ "skyblocker.config.general.quiverWarning.enableQuiverWarningInDungeons": "Enable Quiver Warning In Dungeons", "skyblocker.config.general.searchOverlay.historyLabel": "History:", + "skyblocker.config.general.searchOverlay.starsTooltip": "Star count for dungeon items", + "skyblocker.config.general.searchOverlay.maxPet": "Max Pet Level", + "skyblocker.config.general.searchOverlay.maxPet.@Tooltip": "Only show pets that are max level", "skyblocker.config.general.shortcuts": "Shortcuts", "skyblocker.config.general.shortcuts.config": "Shortcuts Config...", @@ -393,6 +398,15 @@ "skyblocker.config.chat.filter.hideToggleSkyMall": "Hide Toggle Sky Mall Messages", "skyblocker.config.chat.filter.hideToggleSkyMall.@Tooltip": "Hides those pesky messages telling you to disable the Sky Mall HOTM perk when you want it enabled!", + "skyblocker.config.eventNotifications": "Event Notifications", + + "skyblocker.config.eventNotifications.criterion": "Notification Criterion", + "skyblocker.config.eventNotifications.monologue": "can you pls log onto skyblock rq pls? that would be cool cuz like if you are seeing dis then it means that ur config either got cleared or that this is ur first time using the mod if so then thanks for choosing it and hopefully you enjoy it! so yea this is where you will be able to set reminders for all events in skyblock they will be added as you encounter so you first need to log onto skyblock so yea hope you enjoy the mod and all that", + "skyblocker.config.eventNotifications.notificationSound": "Notification Sound", + "skyblocker.config.eventNotifications.@Tooltip[0]": "Configure how much time before an event you will be reminded with a notification toast! For example if you set '5m' and '30s' in the list, you will receive a notification 5 minutes before and another 30 seconds before an event starts.", + "skyblocker.config.eventNotifications.@Tooltip[1]": "The order doesn't matter. If you want to disable notifications, just empty the list.", + "skyblocker.config.eventNotifications.@Tooltip[2]": "This list will modify the '%s' event", + "skyblocker.config.mining": "Mining", "skyblocker.config.mining.commissionWaypoints": "Commission Waypoints", @@ -649,6 +663,11 @@ "skyblocker.updateRepository.failed": "§cUpdating the local repository failed. See logs for details.", "skyblocker.updateRepository.error": "§cEncountered an error while deleting and updating the local repository. Try again in a moment or remove the files manually and restart the game. See logs for details.", + "skyblocker.museum.attemptingResync": "Attempting to resync your museum item donations...", + "skyblocker.museum.cannotResync": "Cannot resync your museum item donations! Note that you can only do this once every two days.", + "skyblocker.museum.resyncSuccess": "Successfully resynced your museum item donations!", + "skyblocker.museum.resyncFailure": "Failed to resync your museum item donations!", + "skyblocker.dungeons.secrets.physicalEntranceNotFound": "§cDungeon Entrance Room coordinates not found. Please go back to the green Entrance Room.", "skyblocker.dungeons.secrets.markSecretFound": "§rMarked secret #%d as found.", "skyblocker.dungeons.secrets.markSecretMissing": "§rMarked secret #%d as missing.", @@ -668,6 +687,8 @@ "skyblocker.api.cache.HIT": "This data was cached!\nIt's %d seconds old.", "skyblocker.api.cache.MISS": "This data wasn't cached!", + "skyblocker.api.token.authFailure": "Failed to refresh your Skyblocker API token, some features may not work temporarily!", + "skyblocker.api.token.noProfileKeys": "Failed to get your profile keys! Some features of the mod may not work temporarily :( (Has your game been open for more than 24 hours?). To reactivate these features restart the game.", "skyblocker.exotic.crystal": "CRYSTAL", "skyblocker.exotic.fairy": "FAIRY", @@ -696,6 +717,14 @@ "skyblocker.end.hud.protectorLocations.rightFront": "Right Front", "skyblocker.end.hud.protectorLocations.rightBack": "Right Back", + "skyblocker.events.startsNow": "%s starts now!", + "skyblocker.events.startsSoon": "%s starts soon!", + "skyblocker.events.tab.endsIn": "Ends in %s", + "skyblocker.events.tab.clickToWarp": "Click to warp!", + "skyblocker.events.tab.noMore": "No more this year!", + "skyblocker.events.tab.startsIn": "Starts in %s", + + "skyblocker.garden.hud.mouseLocked": "Mouse locked.", "skyblocker.fishing.reelNow": "Reel in now!", @@ -721,6 +750,28 @@ "skyblocker.shortcuts.new": "New Shortcut", "skyblocker.shortcuts.commandSuggestionTooltip": "Due to limitations of Minecraft, command suggestions will only work after joining a new world.", + "skyblocker.waypoints.config": "Waypoints Config", + "skyblocker.waypoints.newCategory": "New Waypoint Category", + "skyblocker.waypoints.new": "New Waypoint", + "skyblocker.waypoints.share": "Share", + "skyblocker.waypoints.shareWaypoints": "Share Waypoints", + "skyblocker.waypoints.importWaypointsSkytils": "Import Waypoints (Skytils)", + "skyblocker.waypoints.importWaypointsSkytils.tooltip": "Import Waypoints from Clipboard (Skytils Format)", + "skyblocker.waypoints.importWaypointsSnoopy": "Import Waypoints (Snoopy)", + "skyblocker.waypoints.importWaypointsSnoopy.tooltip": "Import Waypoints from Clipboard (Snoopy Format) (Coming Soon)", + "skyblocker.waypoints.exportWaypointsSkytils": "Export Waypoints (Skytils)", + "skyblocker.waypoints.exportWaypointsSkytils.tooltip": "Export Waypoints to Clipboard (Skytils Format)", + "skyblocker.waypoints.importSuccess": "Waypoints Imported", + "skyblocker.waypoints.importSuccessText": "Successfully imported %d waypoints from %d categories.", + "skyblocker.waypoints.importError": "Error Importing Waypoints", + "skyblocker.waypoints.importErrorText": "Failed to import waypoints. See logs for details.", + "skyblocker.waypoints.exportSuccess": "Waypoints Exported", + "skyblocker.waypoints.exportSuccessText": "Successfully exported %d waypoints from %d categories.", + "skyblocker.waypoints.exportError": "Error Exporting Waypoints", + "skyblocker.waypoints.exportErrorText": "Failed to export waypoints. See logs for details.", + "skyblocker.waypoints.deleteQuestion": "Are you sure you want to remove this waypoint?", + "skyblocker.waypoints.deleteWarning": "Waypoint '%s' will be lost forever! (A long time!)", + "skyblocker.customItemNames.removed": "§fRemoved this item's custom name.", "skyblocker.customItemNames.neverHad": "§fThis item doesn't have a custom name set, but why not add one? ;)", "skyblocker.customItemNames.added": "§fSet a custom name for your currently held item!", diff --git a/src/main/resources/assets/skyblocker/lang/es_es.json b/src/main/resources/assets/skyblocker/lang/es_es.json index 84bd1316..e428f943 100644 --- a/src/main/resources/assets/skyblocker/lang/es_es.json +++ b/src/main/resources/assets/skyblocker/lang/es_es.json @@ -6,7 +6,6 @@ "skyblocker.config.general": "General", "skyblocker.config.uiAndVisuals.bars": "Salud, Mana, Defensa & Barra de XP", "skyblocker.config.uiAndVisuals.bars.enableBars": "Habilitar Barras", - "skyblocker.option.general.bars.barpositions": "Configurar Posición de Barras", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "Ver la previsualización de la mochila sin sostener Shift", "skyblocker.config.general.itemTooltip.enableNPCPrice": "Habilitar Precios de NPC", "skyblocker.config.general.itemTooltip.enableAvgBIN": "Habilitar Precios Promedio de BIN", diff --git a/src/main/resources/assets/skyblocker/lang/fr_fr.json b/src/main/resources/assets/skyblocker/lang/fr_fr.json index 4471ddb2..c4a1e415 100644 --- a/src/main/resources/assets/skyblocker/lang/fr_fr.json +++ b/src/main/resources/assets/skyblocker/lang/fr_fr.json @@ -6,7 +6,6 @@ "skyblocker.config.general": "Général", "skyblocker.config.uiAndVisuals.bars": "Barres de vie, de mana, de défense et d'XP", "skyblocker.config.uiAndVisuals.bars.enableBars": "Activer les barres", - "skyblocker.option.general.bars.barpositions": "Configurer Les positions des barres", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "Aperçu du sac à dos sans appuyer sur Maj", "skyblocker.config.general.itemTooltip": "Info-bulles des objets", "skyblocker.config.general.itemTooltip.enableNPCPrice": "Afficher le prix des NPC", @@ -114,7 +113,6 @@ "skyblocker.config.helpers.fairySouls.highlightFoundSouls": "Accentuer les fairy souls trouvées", "skyblocker.config.general.itemTooltip.enableExoticTooltip": "Activer l'info-bulle exotique", "skyblocker.config.general.itemTooltip.enableExoticTooltip.@Tooltip": "Affiche le type d'exotique en dessous du nom de l'item si une pièce d'armure est exotique.", - "skyblocker.option.general.etherwarpOverlay": "Overlay de l'Etherwarp", "skyblocker.config.general.shortcuts.enableCommandArgShortcuts.@Tooltip": "Raccourcis qui remplacent un ou plus de mot(s)/argument(s) d'une commande qui a plusieurs mots/arguments. Edite les raccourcis avec \"/skyblocker shortcuts\". Les raccourcis doivent être activés pour que ceci prenne effet.", "skyblocker.config.helpers.fairySouls.highlightOnlyNearbySouls.@Tooltip": "Lorsque cette option est activée, seules les fairy souls situées dans un rayon de 50 blocs sont mises en évidence", "skyblocker.config.uiAndVisuals.tabHud.plainPlayerNames.@Tooltip": "Activer pour afficher les pseudos des joueurs sans aucune mise en forme sur les îles publiques.", @@ -204,7 +202,6 @@ "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning.@Tooltip": "Préviens quand vous sortez un arc et que vous n'avez pas de flèches toxiques empoisonnées dans votre inventaire. Marche seulement durant la phase de DPS.", "skyblocker.config.quickNav.button": "Bouton %d", "skyblocker.config.quickNav.button.render": "Afficher", - "skyblocker.option.quickNav.button.item": "Item", "skyblocker.config.mining.crystalsHud.screen": "Configuration du placement de la carte...", "skyblocker.config.mining.crystalsHud.mapScaling": "Taille de la Carte", "skyblocker.config.mining.crystalsHud.showLocations.locationSize": "Taille de la Position", @@ -227,7 +224,6 @@ "skyblocker.config.helpers.fishing.hideOtherPlayers.@Tooltip": "Cache les cannes à pêche des autres joueurs", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgroundsOpacity": "Opacité du Fond de Rareté d'Item", "skyblocker.config.uiAndVisuals.flameOverlay.flameOpacity": "Opacité du Feu", - "skyblocker.config.quickNav.button.item.nbt": "NBT", "skyblocker.config.general.itemTooltip.enableAccessoriesHelper.@Tooltip[1]": "Vous avez le plus haut rang d'accessoire dans cette famille.", "skyblocker.config.general.itemTooltip.enableAccessoriesHelper.@Tooltip[2]": "Cet accessoire est une amélioration comparé à celui dans la même famille que vous avez déjà. Montre aussi le rang de cet accessoire dans sa famille.", "skyblocker.config.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]": "Vous n'avez aucun accessoire de cette famille.", @@ -243,7 +239,6 @@ "skyblocker.config.crimsonIsle.kuudra": "Kuudra", "skyblocker.config.crimsonIsle.kuudra.safeSpotWaypoints": "Waypoints des endroits sûrs", "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning": "Avertissement pas de flèches empoisonnées", - "skyblocker.option.locations.spidersDen.relics": "Aide aux reliques cachées", "skyblocker.config.otherLocations.spidersDen.relics.highlightFoundRelics": "Afficher les Reliques Trouvées", "skyblocker.config.farming.garden": "Jardin", "skyblocker.config.general.itemProtection": "Protection d'Item", diff --git a/src/main/resources/assets/skyblocker/lang/it_it.json b/src/main/resources/assets/skyblocker/lang/it_it.json index 898d394a..76ad6635 100644 --- a/src/main/resources/assets/skyblocker/lang/it_it.json +++ b/src/main/resources/assets/skyblocker/lang/it_it.json @@ -13,12 +13,10 @@ "skyblocker.config.general": "Generale", "skyblocker.config.uiAndVisuals.bars": "Barre per la Vita, Mana, Difesa, e XP", "skyblocker.config.uiAndVisuals.bars.enableBars": "Attiva Barre", - "skyblocker.option.general.bars.barpositions": "Modifica la Posizione delle Barre", "skyblocker.config.helpers.experiments": "Risolutore di Esperimenti", "skyblocker.config.helpers.experiments.enableSuperpairsSolver": "Attiva Risolutore per le Superpairs", "skyblocker.config.helpers.experiments.enableUltrasequencerSolver": "Attiva Risolutore per Ultrasequencer", "skyblocker.config.general.acceptReparty": "Accetta automaticamente Reparty", - "skyblocker.option.general.etherwarpOverlay": "Overlay per Etherwarp", "skyblocker.config.helpers.fishing": "Aiutante per la Pesca", "skyblocker.config.helpers.fairySouls": "Aiutante per le Fairy Souls", "skyblocker.config.helpers.fairySouls.enableFairySoulsHelper": "Attiva Aiutante per le Fairy Souls", @@ -50,8 +48,6 @@ "skyblocker.config.misc.richPresence.info.PURSE": "Portafoglio", "skyblocker.config.misc.richPresence.info.BITS": "BIT", "skyblocker.config.misc.richPresence.info.LOCATION": "POSIZIONE", - "skyblocker.option.quickNav.button.item": "Oggetto", - "skyblocker.config.quickNav.button.item.nbt": "NBT", "skyblocker.config.crimsonIsle.kuudra": "Kuudra", "skyblocker.config.chat": "Messaggi", "skyblocker.config.chat.filter.chatFilterResult.PASS": "Disattivo", diff --git a/src/main/resources/assets/skyblocker/lang/ja_jp.json b/src/main/resources/assets/skyblocker/lang/ja_jp.json index da754d4d..ac93084e 100644 --- a/src/main/resources/assets/skyblocker/lang/ja_jp.json +++ b/src/main/resources/assets/skyblocker/lang/ja_jp.json @@ -6,7 +6,6 @@ "skyblocker.config.general": "一般", "skyblocker.config.uiAndVisuals.bars": "体力,マナ,ディフェンス,経験値のバー", "skyblocker.config.uiAndVisuals.bars.enableBars": "バーを有効にする", - "skyblocker.option.general.bars.barpositions": "バーの位置を変える", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "Shiftキーを押さずにバックパックのプレビューを見る", "skyblocker.config.general.itemTooltip": "アイテムの詳細情報", "skyblocker.config.general.itemTooltip.enableNPCPrice": "NPC売りの値段を表示する", @@ -98,7 +97,6 @@ "skyblocker.config.helpers.fairySouls.enableFairySoulsHelper": "フェアリーソウルヘルパーを有効にするか", "skyblocker.config.general.itemTooltip.enableMotesPrice.@Tooltip": "The Riftディメンションにいるときにアイテムの売値をmotesで表示する。", "skyblocker.config.general.itemTooltip.enableMotesPrice": "売値をmotesで表示", - "skyblocker.option.general.etherwarpOverlay": "Etherwarp移動先の表示", "skyblocker.config.general.quiverWarning": "Quiverの通知", "skyblocker.config.general.quiverWarning.enableQuiverWarning": "Quiverの通知を有効にする", "skyblocker.config.general.shortcuts": "ショートカット", @@ -144,7 +142,6 @@ "skyblocker.config.dungeons.map.mapScaling": "地図の大きさ", "skyblocker.config.otherLocations.end.resetText": "リセット", "skyblocker.config.dungeons.dungeonScore": "ダンジョンの得点", - "skyblocker.config.quickNav.button.item.nbt": "NBT", "skyblocker.config.quickNav.button.item.count": "アイテムの数量", "skyblocker.config.farming.garden.enableHud": "農業のHUDを有効にする", "skyblocker.config.farming.garden.farmingHud": "農業のHUDの設定…", @@ -156,5 +153,26 @@ "skyblocker.config.chat.chatRules.screen.ruleScreen.false": "いいえ", "skyblocker.config.chat.chatRules.screen.ruleScreen.regex": "正規表現か:", "skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage": "メッセージを非表示にする:", - "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.none": "なし" + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.none": "なし", + "skyblocker.config.dungeons.doorHighlight.doorHighlightType": "ハイライトのタイプ", + "skyblocker.config.dungeons.map": "地図", + "skyblocker.config.farming": "農業", + "skyblocker.config.general.searchOverlay.historyLabel": "履歷:", + "skyblocker.config.chat.chatRules.screen.ruleName": "ルールの名称", + "skyblocker.config.chat.chatRules.screen.new": "新しいルール", + "skyblocker.config.chat.chatRules.screen.ruleScreen.name": "名称:", + "skyblocker.config.chat.chatRules.screen.ruleScreen.inputs": "入力:", + "skyblocker.config.chat.chatRules.screen.ruleScreen.outputs": "出力:", + "skyblocker.config.chat.chatRules.screen.ruleScreen.name.@Tooltip": "ルールの名称。", + "skyblocker.config.chat.chatRules.screen.ruleScreen.filter.@Tooltip": "チャットメッセージと一致する文字列/正規表現。", + "skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip": "チャットからこのメッセージを削除する。", + "skyblocker.config.chat.chatRules.screen.ruleScreen.filter": "フィルター:", + "text.skyblocker.source": "ソースコード", + "text.skyblocker.website": "ウェブサイト", + "text.skyblocker.translate": "翻訳", + "text.skyblocker.discord": "Discord", + "text.skyblocker.config": "設定画面を開く", + "text.skyblocker.confirm": "確認", + "text.skyblocker.modrinth": "Modrinth", + "skyblocker.config.dungeons.doorHighlight": "ドアをハイライト表示する" } diff --git a/src/main/resources/assets/skyblocker/lang/ko_kr.json b/src/main/resources/assets/skyblocker/lang/ko_kr.json index ff6007ea..79573f5c 100644 --- a/src/main/resources/assets/skyblocker/lang/ko_kr.json +++ b/src/main/resources/assets/skyblocker/lang/ko_kr.json @@ -6,7 +6,6 @@ "skyblocker.config.general": "일반", "skyblocker.config.uiAndVisuals.bars": "체력, 마나, 방어력 및 경험치 바", "skyblocker.config.uiAndVisuals.bars.enableBars": "바 활성화", - "skyblocker.option.general.bars.barpositions": "바 위치 조정", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "Shift 를 누르지 않고 백팩 미리보기", "skyblocker.config.general.itemTooltip": "아이템 툴팁", "skyblocker.config.general.itemTooltip.enableNPCPrice": "NPC 가격 활성화", @@ -84,7 +83,6 @@ "skyblocker.config.helpers.fishing.enableFishingHelper": "낚시 도우미 활성화", "skyblocker.config.helpers.fairySouls": "페어리 소울 도우미", "skyblocker.config.helpers.fairySouls.enableFairySoulsHelper": "페어리 소울 도우미 활성화", - "skyblocker.option.general.etherwarpOverlay": "Etherwarp 오버레이", "skyblocker.config.general.shortcuts": "단축키", "skyblocker.config.general.shortcuts.enableShortcuts": "단축키 활성화", "skyblocker.config.general.shortcuts.enableShortcuts.@Tooltip": "Hypixel 에서만 작동합니다. \"/skyblocker shortcuts\" 로 단축키를 수정하세요. 이 기능을 사용하려면 아래 옵션 중 하나 이상을 활성화해야 합니다.", diff --git a/src/main/resources/assets/skyblocker/lang/nb_no.json b/src/main/resources/assets/skyblocker/lang/nb_no.json index 438cc472..e3a21a4d 100644 --- a/src/main/resources/assets/skyblocker/lang/nb_no.json +++ b/src/main/resources/assets/skyblocker/lang/nb_no.json @@ -2,7 +2,6 @@ "key.wikiLookup": "Wiki-oppslag", "skyblocker.config.title": "Skyblocker-innstillinger", "skyblocker.config.uiAndVisuals.bars.enableBars": "Aktiver barer", - "skyblocker.option.general.bars.barpositions": "Konfigurer barposisjoner", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "Vis forhåndsvisning av ryggsekk uten å holde Shift nede", "skyblocker.config.general.itemTooltip": "Verktøytips for Gjenstad", "skyblocker.config.general.itemTooltip.enableNPCPrice": "Aktiver NPC-pris", diff --git a/src/main/resources/assets/skyblocker/lang/nn_no.json b/src/main/resources/assets/skyblocker/lang/nn_no.json index ed70e702..1387b4a8 100644 --- a/src/main/resources/assets/skyblocker/lang/nn_no.json +++ b/src/main/resources/assets/skyblocker/lang/nn_no.json @@ -5,6 +5,5 @@ "skyblocker.config.general": "Generelt", "key.categories.skyblocker": "SkyBlocker", "skyblocker.config.uiAndVisuals.bars": "Liv, mana, forsvar og XP-bar", - "skyblocker.config.uiAndVisuals.bars.enableBars": "Aktiver bar", - "skyblocker.option.general.bars.barpositions": "Konfigurer barposisjoner" + "skyblocker.config.uiAndVisuals.bars.enableBars": "Aktiver bar" } diff --git a/src/main/resources/assets/skyblocker/lang/pl_pl.json b/src/main/resources/assets/skyblocker/lang/pl_pl.json new file mode 100644 index 00000000..16aec4d7 --- /dev/null +++ b/src/main/resources/assets/skyblocker/lang/pl_pl.json @@ -0,0 +1,87 @@ +{ + "key.hotbarSlotLock": "Blokada slotu (Pasek szybkiego wyboru)", + "key.skyblocker.toggleB": "Przełącz zakładkę HUD na ekran B", + "key.skyblocker.toggleA": "Przełącz zakładkę HUD na ekran A", + "key.wikiLookup": "Wyszukaj w Wiki", + "key.itemProtection": "Chroniony przedmiot", + "text.skyblocker.open": "Otwórz", + "text.skyblocker.quit_config": "Zmiany nie zapisane", + "text.skyblocker.quit_config_sure": "Czy na pewno chcesz zakończyć edycję konfiguracji? Zmiany nie zostaną zapisane!", + "text.skyblocker.quit_discard": "Wyjdź i nie zapisuj zmian", + "text.skyblocker.confirm": "Potwierdź", + "text.skyblocker.source": "Źródło", + "text.skyblocker.translate": "Tłumaczenie", + "text.skyblocker.modrinth": "Modrinth", + "skyblocker.config.title": "Ustawienia Skyblockera", + "skyblocker.config.crimsonIsle": "Crimson Isle", + "skyblocker.config.crimsonIsle.kuudra": "Kuudra", + "text.skyblocker.config": "Otwórz menu konfiguracji...", + "text.skyblocker.discord": "Discord", + "skyblocker.config.crimsonIsle.kuudra.ballistaBuildWaypoints": "Punkty orientacyjne zbudowania balisty", + "skyblocker.config.crimsonIsle.kuudra.fuelWaypoints": "Waypoint dla paliwa", + "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning": "Alert braku zatrutych strzał", + "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning.@Tooltip": "Ostrzega cię, gdy wyciągasz łuk, a w twoim ekwipunku nie ma trujących strzał. Działa tylko w fazie DPS.", + "skyblocker.config.crimsonIsle.kuudra.pearlWaypoints": "Perłowe punkty orientacyjne", + "skyblocker.config.crimsonIsle.kuudra.safeSpotWaypoints": "Bezpieczne punkty orientacyjne", + "skyblocker.config.crimsonIsle.kuudra.suppliesAndFuelWaypointType": "Zaopatrzenie/paliwo Typ punktu trasy", + "skyblocker.config.crimsonIsle.kuudra.supplyWaypoints": "Punkty zaopatrzenia", + "skyblocker.config.dungeons": "Dungeons", + "skyblocker.config.dungeons.croesusHelper.@Tooltip": "Oznacz na szaro skrzynie, które zostały już otwarte.", + "skyblocker.config.dungeons.doorHighlight": "Podświetlanie drzwi", + "skyblocker.config.dungeons.doorHighlight.doorHighlightType": "Typ podświetlania drzwi", + "skyblocker.config.dungeons.doorHighlight.doorHighlightType.secretWaypointsNote": "\n\n\nUwaga: Aby ta funkcja działała, muszą być włączone sekretne punkty orientacyjne w Dungeonach.", + "skyblocker.config.dungeons.dungeonChestProfit": "Kalkulator zysków ze skrzyń z Danguenów", + "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit": "Kalkulator zysku z skrzynnki Croesusa", + "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator": "Włącz kalkulator zysków", + "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "Wyświetla zysk ze skrzyni w lochu w tytule ekranu skrzyni.\nZielony, jeśli jest zysk.\nCzerwony, jeśli nie ma zysku.\nSzary, jeśli nic nie zyskałeś ani nie straciłeś.\nNiebieski, jeśli obliczenia zostały oparte na niekompletnych danych.", + "skyblocker.config.dungeons.dungeonChestProfit.includeEssence.@Tooltip": "Wyłączenie **nie jest to zalecane** , jeśli jesteś osobą zapominalską.", + "skyblocker.config.dungeons.dungeonChestProfit.includeKismet": "Uwzględnij cenę Kismet", + "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor": "niedokończony kolor", + "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor.@Tooltip": "Kolor wyświetlany, gdy dane ceny są niekompletne.", + "skyblocker.config.dungeons.dungeonChestProfit.lossColor": "Kolor straty", + "skyblocker.config.dungeons.dungeonChestProfit.neutralColor": "Neutralny kolor", + "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold": "Próg neutralności", + "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold.@Tooltip": "Próg, poniżej którego zysk jest uznawany za neutralny.", + "skyblocker.config.dungeons.dungeonChestProfit.profitColor": "Kolor zysku", + "skyblocker.config.dungeons.dungeonScore": "Wynik Dungeon", + "skyblocker.config.dungeons.dungeonScore.dungeonCryptsMessage": "Wiadomość o kryptach w Dungeonach", + "skyblocker.config.dungeons.dungeonScore.dungeonCryptsMessage.@Tooltip": "Wiadomość, która zostanie wysłana na czacie po osiągnięciu progu punktowego przed uzyskaniem 5+ krypt. Ciąg „[krypty]” zostanie zastąpiony ilością krypt.", + "skyblocker.config.dungeons.dungeonScore.dungeonCryptsMessageThreshold": "Próg wiadomości krypt w Dungeonach", + "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage": "Wynik Dungeonu %d Wiadomość", + "skyblocker.config.dungeons.dungeonScore.enableDungeonCryptsMessage": "Włącz wiadomość o kryptach w Dungeonie", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage": "Komunikat o włączeniu wyniku dungeon %d", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip": "Wysyła wiadomość na czacie po osiągnięciu %d punktów w Dungeonach.", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound": "Włącz dźwięk wyniku Dungeon %d", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "Odtwarza dźwięk po osiągnięciu %d punktów w dungeonie.", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle": "Włącz wynik Dungeon %d Tytuł", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip": "Wyświetla tytuł po osiągnięciu %d punktów w Dungeonach.", + "skyblocker.config.dungeons.dungeonScore.enableScoreHUD": "Włącz HUD z wynikami", + "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.@Tooltip": "Wyświetla wynik dungeonu w interfejsie HUD.", + "skyblocker.config.dungeons.dungeonScore.scoreScaling": "Skalowanie wyników", + "skyblocker.config.dungeons.fancyPartyFinder": "Fancy Party Finder", + "skyblocker.config.dungeons.hideSoulweaverSkulls": "Ukryj czaszki Soulweaver", + "skyblocker.config.dungeons.hideSoulweaverSkulls.@Tooltip": "Ukrywa Nawiedzone Czaszki, które pojawiają się w wyniku użycia Rękawic Tkacza Dusz.", + "skyblocker.config.dungeons.livid": "Livid (F5/M5)", + "skyblocker.config.dungeons.livid.enableLividColorGlow": "Włącz żywą poświatę kolorów", + "skyblocker.config.dungeons.livid.enableLividColorGlow.@Tooltip": "Stosuje efekt rozświetlenia do właściwego Livid w F5/M5", + "skyblocker.config.dungeons.livid.enableLividColorText": "Włącz żywy kolor tekstu", + "skyblocker.config.dungeons.livid.enableLividColorText.@Tooltip": "Wyślij ożywiony kolor na czacie podczas walki z ożywionym bossem.", + "key.categories.skyblocker": "Skyblocker", + "key.skyblocker.defaultTgl": "Przywróć zakładkę HUD do pierwotnej pozycji", + "text.skyblocker.website": "Strona internetowa", + "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold": "Próg ostrzegawczy o zatruciu strzałą", + "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold.@Tooltip": "Jeśli ilość Toksycznej Trującej Strzały, którą posiadasz w swoim ekwipunku, jest niższa od ustalonego progu, otrzymasz ostrzeżenie.\n\n16 to absolutne minimum.\nChcesz 32 dla „DPS” z przedstrzałem.", + "skyblocker.config.dungeons.allowDroppingProtectedItems": "Włączenie upuszczania przedmiotów chronionych na zablokowanych slotach", + "skyblocker.config.dungeons.allowDroppingProtectedItems.@Tooltip": "Pozwala na używanie zdolności klasowych w Dungeonach na zablokowanym slocie lub podczas trzymania przedmiotu, który został zabezpieczony za pomocą /skyblocker protectItem", + "skyblocker.config.dungeons.doorHighlight.doorHighlightType.@Tooltip": "Podświetlenie: Wyświetla tylko podświetlenie.\n\n\nPodświetlenie konturu: Wyświetla zarówno podświetlenie, jak i kontur.\n\n\nKontur: Wyświetla tylko kontur.", + "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight": "Włącz podświetlenie drzwi", + "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight.@Tooltip": "Podświetla witherowe i zakrwawione drzwi na czerwono, jeśli są zablokowane i na zielono, jeśli są odblokowane.", + "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit.@Tooltip": "Podświetla skrzynie z najlepszym zyskiem w Croesus NPC.\n\n\nNajlepsza skrzynia zostanie podświetlona na zielono.\nJeśli istnieje skrzynia warta użycia klucza lochu, zostanie ona podświetlona na żółto", + "skyblocker.config.dungeons.dungeonChestProfit.includeKismet.@Tooltip": "Po włączeniu tej opcji, jeśli użyto kismeta, jego cena zostanie odjęta od zysku.", + "skyblocker.config.dungeons.dungeonScore.dungeonCryptsMessageThreshold.@Tooltip": "Domyślna wartość 270 jest przeznaczona dla biegów S+, gdy wpisujesz wynik 300+ - co zwykle oznacza F5-F7 i M5-M7. \nJeśli wykonujesz biegi S (wchodzisz z wynikiem 270), powinieneś rozważyć obniżenie tej wartości do 250 lub mniej, w przeciwnym razie będzie ona bezużyteczna.", + "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip": "Wiadomość, która zostanie wysłana na czacie po osiągnięciu %d punktów w dungeonach. Ciąg „[wynik]” zostanie zastąpiony wynikiem dungeonu (%d).", + "skyblocker.config.dungeons.dungeonScore.enableDungeonCryptsMessage.@Tooltip": "Po osiągnięciu progu wysyła wiadomość na czacie, jeśli znaleziono mniej niż 5 krypt.", + "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.deathMessagesNote": "\n\n\nUwaga: Działa to poprawnie tylko wtedy, gdy wiadomości o śmierci są włączone w ustawieniach skyblock. Jeśli chcesz ukryć wiadomości o śmierci, użyj zamiast tego ustawienia Ukryj wiadomości o śmierci gracza, aby umożliwić dalsze przetwarzanie wiadomości o śmierci", + "skyblocker.config.dungeons.livid.enableLividColorTitle": "Włącz tytuł w żywych kolorach", + "skyblocker.config.dungeons.livid.enableLividColorTitle.@Tooltip": "Wyświetlaj żywy kolor w tytule podczas walki z Livid." +} diff --git a/src/main/resources/assets/skyblocker/lang/pt_br.json b/src/main/resources/assets/skyblocker/lang/pt_br.json index 92ecf4c1..da049992 100644 --- a/src/main/resources/assets/skyblocker/lang/pt_br.json +++ b/src/main/resources/assets/skyblocker/lang/pt_br.json @@ -32,7 +32,6 @@ "skyblocker.config.quickNav.enableQuickNav": "Ativar Navegação Rápida", "key.hotbarSlotLock": "Trancar Slot (Hotbar)", "skyblocker.config.title": "Configurações do Skyblocker", - "skyblocker.option.general.bars.barpositions": "Configurar Posições das Barras", "skyblocker.config.helpers.experiments.enableChronomatronSolver": "Ativar Solucionador de Chronomatron", "skyblocker.config.general.acceptReparty": "Aceitar Automaticamente Reparty", "skyblocker.config.general.shortcuts": "Atalhos", @@ -48,7 +47,6 @@ "skyblocker.config.misc.richPresence.info.LOCATION": "LOCALIZAÇÃO", "skyblocker.config.quickNav": "Navegação Rápida", "key.categories.skyblocker": "Skyblocker", - "skyblocker.option.general.etherwarpOverlay": "Overlay de Etherwarp", "skyblocker.config.helpers.fishing": "Assistente de Pesca", "skyblocker.config.helpers.fishing.enableFishingHelper": "Ativar Assistente de Pesca", "skyblocker.config.helpers.fairySouls": "Assistente de Alma das Fadas", @@ -234,7 +232,6 @@ "skyblocker.config.dungeons.secretWaypoints": "Marcadores de segredos da dungeon", "skyblocker.config.dungeons.secretWaypoints.enableChestWaypoints": "Ativar marcadores de baú", "skyblocker.config.general.itemTooltip.enableExoticTooltip.@Tooltip": "Exibe o tipo de item exótico abaixo do nome do item se a peça de armadura for exótica.", - "skyblocker.option.locations.spidersDen.relics": "Guia para relíquias escondidas", "skyblocker.itemProtection.unableToProtect": "§cNão é possível proteger este tem :( (Você está no skyblock?, Você está segurando um item?)", "skyblocker.config.dungeons.secretWaypoints.enableBatWaypoints": "Ativar marcadores de morcego", "skyblocker.config.dungeons.secretWaypoints.enableWitherWaypoints": "Ativar marcadores da essência de wither", @@ -250,10 +247,8 @@ "skyblocker.config.dungeons.secretWaypoints.enableEntranceWaypoints": "Ativar marcadores da entrada", "skyblocker.config.quickNav.button": "Botão %d", "skyblocker.config.quickNav.button.render": "Renderização", - "skyblocker.option.quickNav.button.item": "Item", "skyblocker.config.quickNav.button.item.itemName": "ID do item", "skyblocker.config.quickNav.button.item.count": "Quantidade do item", - "skyblocker.config.quickNav.button.item.nbt": "NBT", "skyblocker.config.quickNav.button.uiTitle": "Título de interface", "skyblocker.itemProtection.added": "§fSeu %s vai estar protegido agora! §o*o seu item estar mais um pouco seguro :')*", "skyblocker.config.dungeons.secretWaypoints.enableStonkWaypoints": "Ativar marcadores de Stonk", @@ -295,5 +290,11 @@ "skyblocker.config.uiAndVisuals.waypoints": "Waypoints", "skyblocker.config.uiAndVisuals.chestValue.enableChestValue.@Tooltip": "Adiciona um botão em contêineres para calcular o valor do mesmo.", "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor.@Tooltip": "A cor para mostrar quando o dado dos preços está incompleta.", - "skyblocker.config.dungeons.secretWaypoints.waypointType.@Tooltip": "Waypoint: Mostra um destaque e um raio de beacon.\n\nWaypoint Contornado: Mostra tanto um waypoint e um contorno.\n\nDestaque: Apenas mostra um destaque.\n\nDestaque Contornado: Mostra tanto um destaque e um contorno.\n\nContorno: Apenas mostra um contorno." + "skyblocker.config.dungeons.secretWaypoints.waypointType.@Tooltip": "Waypoint: Mostra um destaque e um raio de beacon.\n\nWaypoint Contornado: Mostra tanto um waypoint e um contorno.\n\nDestaque: Apenas mostra um destaque.\n\nDestaque Contornado: Mostra tanto um destaque e um contorno.\n\nContorno: Apenas mostra um contorno.", + "text.skyblocker.source": "Fonte", + "text.skyblocker.website": "Site", + "text.skyblocker.translate": "Traduzir", + "text.skyblocker.modrinth": "Modrinth", + "text.skyblocker.discord": "Discord", + "text.skyblocker.confirm": "Confirmar" } diff --git a/src/main/resources/assets/skyblocker/lang/ru_ru.json b/src/main/resources/assets/skyblocker/lang/ru_ru.json index f015308b..dc0e2d0c 100644 --- a/src/main/resources/assets/skyblocker/lang/ru_ru.json +++ b/src/main/resources/assets/skyblocker/lang/ru_ru.json @@ -45,7 +45,6 @@ "key.wikiLookup": "Быстрый переход на вики", "skyblocker.config.general.itemTooltip": "Описание предмета", "skyblocker.config.general.itemTooltip.enableNPCPrice": "Показать цены у NPC", - "skyblocker.option.general.bars.barpositions": "Изменить расположение полосок", "skyblocker.config.general.itemTooltip.avg.THREE_DAY": "Цена за 3 дня", "skyblocker.config.general.hitbox.oldFarmlandHitbox": "Использовать хитбокс пашни из 1.8", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "Просматривать содержимое рюкзаков без удержания кнопки Shift", @@ -124,7 +123,6 @@ "skyblocker.config.slayer.vampireSlayer.holyIceIndicatorTickDelay": "Задержка Индикатора Holy Ice (в тиках)", "skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency": "Частота Обновления Индикатора Holy Ice (в тиках)", "skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency.@Tooltip": "Чем ниже значение, тем чаще происходит обновление, это может вызвать лаги.", - "skyblocker.option.general.etherwarpOverlay": "Окрасить Выбранный Блок Etherwarp", "skyblocker.config.general.shortcuts.enableCommandShortcuts": "Включить сокращения команд", "skyblocker.config.general.shortcuts": "Сокращения", "skyblocker.config.general.shortcuts.enableShortcuts": "Включить сокращения", @@ -156,7 +154,6 @@ "skyblocker.config.general.itemTooltip.enableMuseumInfo": "Включить музейную информацию", "skyblocker.config.uiAndVisuals.tabHud.nameSorting.@Tooltip": "Алфавитная сортировка сортирует имена в алфавитном порядке, в то время как сортировка по умолчанию не имеет определенного порядка.", "skyblocker.config.quickNav.button.item.count": "Кол-во предметов", - "skyblocker.config.quickNav.button.item.nbt": "NBT-информация", "skyblocker.config.helpers.fairySouls.highlightOnlyNearbySouls": "Подсвечивать только ближайшие fairy souls", "skyblocker.config.helpers.fairySouls.highlightOnlyNearbySouls.@Tooltip": "Когда включено, Fairy Souls в радиусе 50 блоков подсвечиваются", "skyblocker.config.crimsonIsle.kuudra.suppliesAndFuelWaypointType": "Тип Пополняющих/Заправочных меток", @@ -190,7 +187,6 @@ "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound": "Включить звуковое оповещение о достижении %d очков", "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "Проигрывает звук при получении %d очков.", "skyblocker.config.otherLocations.spidersDen": "Spidеr's Dеn", - "skyblocker.option.locations.spidersDen.relics": "Помощник по спрятанным реликвиям", "skyblocker.config.general.specialEffects": "Спецэффекты", "skyblocker.config.general.specialEffects.rareDungeonDropEffects": "Показ редкости данж-дропа эффектом", "skyblocker.config.dungeons.secretWaypoints.enableRoomMatching.@Tooltip": "Отключение сэкономит 20 МБ ОЗУ, но метки секретов и некоторые пазлы требуют включение этой функции.", @@ -228,7 +224,6 @@ "key.itemProtection": "Защита предмета", "skyblocker.config.quickNav.button": "Кнопка %d", "skyblocker.config.quickNav.button.render": "Прорисовка", - "skyblocker.option.quickNav.button.item": "Предмет", "skyblocker.config.uiAndVisuals.chestValue.color": "Цвет счётчика хранилища", "skyblocker.config.uiAndVisuals.chestValue.enableChestValue.@Tooltip": "Добавляет кнопку для подсчёта стоимости хранилища.", "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor": "Цвет неполного подсчёта", @@ -661,5 +656,27 @@ "skyblocker.bars.config.showValue": "Показываемое значение", "skyblocker.bars.config.icon": "Иконка", "skyblocker.config.uiAndVisuals.bars.openScreen": "Экран настройки полос эффектов", - "skyblocker.bars.config.explanationTitle": "Что это?" + "skyblocker.bars.config.explanationTitle": "Что это?", + "skyblocker.config.dungeons.professor": "Профессор (Этаж3/Мастер3)", + "skyblocker.config.farming": "Фермерство", + "skyblocker.config.chat.filter": "Фильтр", + "skyblocker.config.mining.crystalHollows": "Crуstal Hollows", + "skyblocker.config.mining.dwarvenMines": "Гномьи Шахты", + "skyblocker.config.misc": "Разное", + "skyblocker.config.otherLocations.end.protectorLocationEnable": "Показать местоположение Protector'а", + "skyblocker.config.otherLocations.end.zealotKillsEnabled": "Показать инфо о убийствах Зеалотов", + "skyblocker.config.dungeons.map": "Карта", + "skyblocker.config.dungeons.puzzle": "Решатель головоломок", + "skyblocker.config.helpers": "Помощники", + "skyblocker.config.mining.commissionWaypoints.mode": "Включить точки приёма комиссий", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[1]": "\nDwarvеn: Показывает пункты приёма только в шахтах Дварфов.", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[2]": "\nGlаcite: Показывает пункты приёма только в ледяных шахтах.", + "skyblocker.config.mining.commissionWaypoints.useColor": "Цветные точки", + "skyblocker.config.mining.commissionWaypoints.useColor.@Tooltip": "Цвет текста точки, предназначенный для конкретного блока.", + "skyblocker.config.mining.commissionWaypoints.textScale": "Размер текста", + "skyblocker.config.mining.commissionWaypoints.textScale.@Tooltip": "Укажите размер текста-подсказки о комиссиях.", + "skyblocker.config.mining.commissionWaypoints": "Точки приёма комиссий", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[0]": "Выкл: Не показывает точки приёма комиссий.", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[3]": "\nBoth: Показывает точки приёма и в шахтах Дварфов, и в ледяных шахтах.", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[4]": "\n(изменения вступят в силу, когда обновятся комиссии)" } diff --git a/src/main/resources/assets/skyblocker/lang/tr_tr.json b/src/main/resources/assets/skyblocker/lang/tr_tr.json index 6a24529d..208c2456 100644 --- a/src/main/resources/assets/skyblocker/lang/tr_tr.json +++ b/src/main/resources/assets/skyblocker/lang/tr_tr.json @@ -48,7 +48,6 @@ "skyblocker.config.general": "Genel", "skyblocker.config.uiAndVisuals.bars": "Can, Mana, Defans ve XP Barları", "skyblocker.config.uiAndVisuals.bars.enableBars": "Barları Etkinleştir", - "skyblocker.option.general.bars.barpositions": "Çubuk Konumlarını Ayarla", "key.categories.skyblocker": "Skyblocker", "skyblocker.updateRepository.error": "§cYerel depo güncellenemedi. Dosyaları manuel olarak silip oyunu tekrar başlatın.", "skyblocker.config.helpers.fishing": "Balık Tutma Yardımcısı", @@ -75,7 +74,6 @@ "skyblocker.config.general.shortcuts.enableCommandShortcuts": "Komut Kısayollarını Etkinleştir", "skyblocker.config.general.shortcuts.enableShortcuts": "Kısayolları Etkinleştir", "skyblocker.config.general.shortcuts.enableCommandShortcuts.@Tooltip": "Sadece bir kelimeden oluşan komutlar için kısayollar. Kısayolları \"/skyblocker shortcuts\" ile düzenleyebilirsiniz. Bu seçeneğin etki göstermesi için kısayolların etkinleştirilmiş olması gerekir.", - "skyblocker.option.general.etherwarpOverlay": "Etherwarp Kaplaması", "skyblocker.config.helpers.fairySouls.enableFairySoulsHelper": "Peri Ruhları Yardımcısını Etkinleştir", "skyblocker.config.general.shortcuts.enableCommandArgShortcuts": "Komut Argüman Kısayollarını Etkinleştir", "skyblocker.config.uiAndVisuals.waypoints": "İşaret Noktaları", diff --git a/src/main/resources/assets/skyblocker/lang/zh_cn.json b/src/main/resources/assets/skyblocker/lang/zh_cn.json index dc669eda..e4f59c09 100644 --- a/src/main/resources/assets/skyblocker/lang/zh_cn.json +++ b/src/main/resources/assets/skyblocker/lang/zh_cn.json @@ -6,7 +6,6 @@ "skyblocker.config.general": "通用", "skyblocker.config.uiAndVisuals.bars": "生命,法力,防御以及经验条", "skyblocker.config.uiAndVisuals.bars.enableBars": "启用属性条", - "skyblocker.option.general.bars.barpositions": "设置属性条位置", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "查看背包预览无需按住shift键", "skyblocker.config.general.itemTooltip": "物品提示信息", "skyblocker.config.general.itemTooltip.enableNPCPrice": "显示NPC价格", @@ -56,7 +55,7 @@ "skyblocker.config.mining.dwarvenMines.solveFetchur": "解决Fetchur的谜题", "skyblocker.config.mining.dwarvenMines.solvePuzzler": "解决Puzzler的谜题", "skyblocker.config.mining.dwarvenHud": "矮人矿井HUD", - "skyblocker.config.chat": "消息", + "skyblocker.config.chat": "聊天消息", "skyblocker.config.chat.filter.chatFilterResult.PASS": "已禁用", "skyblocker.config.chat.filter.chatFilterResult.FILTER": "启用", "skyblocker.config.chat.filter.chatFilterResult.ACTION_BAR": "移动到动作栏", @@ -78,13 +77,13 @@ "skyblocker.config.helpers.fishing.enableFishingHelper": "启用钓鱼助手", "skyblocker.fishing.reelNow": "收竿!", "skyblocker.config.dungeons.livid.enableLividColorText": "启用真Livid的颜色提示文本", - "skyblocker.config.dungeons.livid": "提示真Livid的颜色", + "skyblocker.config.dungeons.livid": "提示 真Livid 的颜色", "skyblocker.config.dungeons.livid.enableLividColorText.@Tooltip": "将真Livid的颜色发送到聊天栏。", "skyblocker.config.dungeons.livid.lividColorText": "真Livid颜色提示信息文本", "skyblocker.config.dungeons.livid.lividColorText.@Tooltip": "Livid Boss战时发送到聊天栏的信息, 字段 “[color]” 将被替换为真Livid的颜色。", "key.skyblocker.defaultTgl": "将tab键所显示的列表改为默认空岛生存列表", "skyblocker.config.uiAndVisuals.tabHud.tabHudEnabled": "启用更好的Tab HUD", - "skyblocker.config.uiAndVisuals.tabHud": "更好的Tab HUD(在地牢外临时禁用)", + "skyblocker.config.uiAndVisuals.tabHud": "更好的Tab HUD(在地下城外临时禁用)", "skyblocker.config.uiAndVisuals.tabHud.tabHudScale": "更好的Tab HUD缩放大小", "skyblocker.config.uiAndVisuals.tabHud.tabHudScale.@Tooltip": "相对于原版GUI的百分比大小", "skyblocker.config.mining.dwarvenHud.style": "HUD风格", @@ -92,7 +91,7 @@ "skyblocker.config.mining.dwarvenHud.style.@Tooltip[1]": "\n精致:显示委托名,进度百分比与进度条以及图标。", "skyblocker.config.mining.dwarvenHud.style.@Tooltip[2]": "\n极简:仅在简单的方框内显示委托及其进度。", "skyblocker.config.general.itemTooltip.enableMotesPrice": "启用Motes价格显示", - "skyblocker.config.general.itemTooltip.enableMotesPrice.@Tooltip": "显示Rift中的物品售价,以Mote为单位。", + "skyblocker.config.general.itemTooltip.enableMotesPrice.@Tooltip": "显示裂域中的物品售价,以Mote为单位。", "skyblocker.config.helpers.experiments": "实验台助手", "skyblocker.config.helpers.experiments.enableSuperpairsSolver": "启用超级配对实验助手", "skyblocker.config.helpers.experiments.enableChronomatronSolver": "启用序列记忆实验助手", @@ -115,7 +114,6 @@ "skyblocker.config.general.shortcuts.enableCommandShortcuts": "启用命令别名", "skyblocker.config.general.shortcuts.enableCommandArgShortcuts": "启用带参命令别名", "skyblocker.config.uiAndVisuals.tabHud.nameSorting": "玩家名排列顺序", - "skyblocker.option.general.etherwarpOverlay": "Etherwarp技能目标位置显示", "skyblocker.config.dungeons.puzzle.blazeSolver.@Tooltip": "以绿色边框标记正确的烈焰人,并将下一个烈焰人以白色线条与边框一同标记。", "skyblocker.config.uiAndVisuals.tabHud.plainPlayerNames.@Tooltip": "开启后在公共岛屿显示玩家名时不显示任何特殊效果。", "skyblocker.config.chat.filter.hideShowOff.@Tooltip": "过滤来自 /show 命令的消息", @@ -124,9 +122,9 @@ "skyblocker.config.dungeons.puzzle.solveTicTacToe": "井字棋谜题助手", "skyblocker.config.dungeons.secretWaypoints": "地牢秘密路径点", "skyblocker.config.dungeons.secretWaypoints.enableSecretWaypoints": "启用地牢秘密路径点", - "skyblocker.config.uiAndVisuals.tabHud.nameSorting.@Tooltip": "“Alphabetical”以词典序排列玩家,而“Default”以Hypixel默认顺序排列", + "skyblocker.config.uiAndVisuals.tabHud.nameSorting.@Tooltip": "“Alphabetical”以词典序排列玩家,而“Default”以Hypixel默认顺序排列。", "skyblocker.config.dungeons.puzzle.solveTicTacToe.@Tooltip": "以红色方块标记井字棋的下一步!", - "skyblocker.config.general.quiverWarning.enableQuiverWarningInDungeons": "在地牢内启用箭袋提示", + "skyblocker.config.general.quiverWarning.enableQuiverWarningInDungeons": "在地下城内启用箭袋提示", "skyblocker.config.general.quiverWarning": "箭袋提示", "skyblocker.config.general.quiverWarning.enableQuiverWarning": "启用箭袋提示", "skyblocker.config.general.itemInfoDisplay": "物品信息显示", @@ -138,24 +136,23 @@ "text.skyblocker.quit_config": "修改未保存", "text.skyblocker.quit_config_sure": "确定退出吗?你的修改将不会被保存!", "skyblocker.config.general.wikiLookup": "查阅wiki", - "skyblocker.config.general.wikiLookup.officialWiki": "使用Hypixel官方维基", - "skyblocker.config.general.wikiLookup.officialWiki.@Tooltip": "在查阅时使用Hypixel官方维基代替Fandom维基。", + "skyblocker.config.general.wikiLookup.officialWiki": "使用Hypixel官方wiki", + "skyblocker.config.general.wikiLookup.officialWiki.@Tooltip": "在查阅时使用Hypixel官方 wiki 代替Fandom wiki", "skyblocker.config.general.itemTooltip.enableObtainedDate": "显示获取时间", - "skyblocker.config.general.shortcuts.enableCommandShortcuts.@Tooltip": "此功能可用较短的别名替换较长的原始命令,使用“/skyblocker shortcuts”以编辑。 (“更精简的命令”功能必须启用才可生效)", + "skyblocker.config.general.shortcuts.enableCommandShortcuts.@Tooltip": "此功能可用较短的别名替换较长的原始命令,使用“/skyblocker shortcuts”以编辑。 “更精简的命令”功能必须启用才可生效。", "skyblocker.config.uiAndVisuals.waypoints": "路径点", "skyblocker.config.uiAndVisuals.waypoints.enableWaypoints": "启用路径点", "skyblocker.config.dungeons.secretWaypoints.waypointType": "路径点类型", - "text.skyblocker.open": "开启", + "text.skyblocker.open": "打开", "skyblocker.config.general.wikiLookup.enableWikiLookup": "启用wiki查阅功能", "skyblocker.config.general.wikiLookup.enableWikiLookup.@Tooltip": "指针悬停于物品上,按F4键打开wiki中的条目。", - "skyblocker.config.helpers.mythologicalRitual": "神话仪式助手", - "skyblocker.config.helpers.mythologicalRitual.enableMythologicalRitualHelper": "开启神话仪式助手", + "skyblocker.config.helpers.mythologicalRitual": "Mythological Ritual助手", + "skyblocker.config.helpers.mythologicalRitual.enableMythologicalRitualHelper": "启用Mythological Ritual助手", "skyblocker.config.dungeons.secretWaypoints.enablePearlWaypoints": "启用珍珠路径点", - "skyblocker.config.dungeons.secretWaypoints.enableAotvWaypoints": "启用AOTV路径点", + "skyblocker.config.dungeons.secretWaypoints.enableAotvWaypoints": "启用瞬息之刃路径点", "skyblocker.config.dungeons.secretWaypoints.enableFairySoulWaypoints": "启用仙女之魂路径点", - "skyblocker.option.locations.spidersDen.relics": "遗物助手", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage": "启用地牢%d分数提示信息", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip": "在地牢中达到%d分时发送提示信息。", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage": "启用地牢 %d 分提示信息", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip": "在地牢中达到 %d 分时发送提示信息。", "skyblocker.config.dungeons.secretWaypoints.enableEntranceWaypoints": "启用入口路径点", "skyblocker.config.dungeons.secretWaypoints.enableChestWaypoints": "启用箱子路径点", "skyblocker.config.uiAndVisuals.itemCooldown": "物品冷却提示", @@ -173,7 +170,7 @@ "skyblocker.config.dungeons.fancyPartyFinder": "更好的组队查找", "skyblocker.config.dungeons.secretWaypoints.enableRoomMatching.@Tooltip": "关闭此选项可节省约20MB左右的内存,但是秘密路径点和§l部分谜题助手功能§r需要启用该选项。", "skyblocker.config.dungeons.secretWaypoints.enableItemWaypoints": "启用物品路径点", - "skyblocker.config.general.quiverWarning.enableQuiverWarningAfterDungeon": "在地牢结束后启用箭袋提示", + "skyblocker.config.general.quiverWarning.enableQuiverWarningAfterDungeon": "在地下城结束后启用箭袋提示", "skyblocker.config.dungeons.secretWaypoints.enableLeverWaypoints": "启用拉杆路径点", "key.skyblocker.toggleA": "将HUD标签页切换至显示屏A", "key.skyblocker.toggleB": "将HUD标签页切换至显示屏B", @@ -193,7 +190,7 @@ "skyblocker.config.dungeons.doorHighlight.doorHighlightType.secretWaypointsNote": "\n\n\n注意:若使此功能生效,必须启用地牢秘密路径点。", "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.@Tooltip": "在状态栏显示地牢的分数。", "skyblocker.config.general.itemTooltip.dungeonQuality": "地牢物品品质", - "skyblocker.config.dungeons.secretWaypoints.enableStonkWaypoints": "启用Stonk路径点", + "skyblocker.config.dungeons.secretWaypoints.enableStonkWaypoints": "启用末地金镐路径点", "skyblocker.config.chat.filter.hideToggleSkyMall": "隐藏Sky Mall切换的消息", "skyblocker.config.mining.dwarvenHud.screen": "矮人矿井HUD配置", "skyblocker.config.chat.filter.hideAOTE.@Tooltip": "隐藏烦人的“路径被阻挡”消息。", @@ -201,7 +198,7 @@ "skyblocker.config.dungeons.playerSecretsTracker": "玩家解谜追踪器", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgroundStyle": "物品稀有度背景样式", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgroundStyle.@Tooltip": "选择圆形或方形背景样式!", - "skyblocker.config.dungeons.livid.enableLividColorGlow": "启用Livid高亮", + "skyblocker.config.dungeons.livid.enableLividColorGlow": "启用 Livid高亮", "skyblocker.config.crimsonIsle.kuudra.pearlWaypoints": "末影珍珠路径点", "skyblocker.config.dungeons.puzzle.creeperSolver": "苦力怕光束谜题助手", "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight": "启用门的高亮", @@ -214,33 +211,31 @@ "skyblocker.config.uiAndVisuals.flameOverlay.flameHeight": "火焰高度", "skyblocker.config.dungeons.mimicMessage.sendMimicMessage": "启用Mimic消息", "skyblocker.config.dungeons.mimicMessage": "Mimic消息", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound": "启用地牢分数%d的音效", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound": "启用地牢 %d 提示音效", "skyblocker.config.general.chestValue.@Tooltip": "计算这个容器中的物品价值。", "skyblocker.config.uiAndVisuals.chestValue": "箱子价值", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle": "启用地牢分数%d的标题", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle": "启用地牢 %d 分提示标题", "skyblocker.config.dungeons.livid.enableLividColorTitle.@Tooltip": "在Livid的Boss战中显示Livid颜色的标题。", "skyblocker.config.dungeons.livid.enableLividColorTitle": "启用Livid颜色标题", "skyblocker.config.dungeons.dungeonChestProfit.lossColor": "亏损的颜色", - "skyblocker.config.quickNav.button.item.itemName": "物品ID", + "skyblocker.config.quickNav.button.item.itemName": "物品", "skyblocker.config.quickNav.button.item.count": "物品数量", "skyblocker.config.farming.garden": "花园", "skyblocker.config.farming.garden.dicerTitlePrevent": "阻止Dicer显示标题", - "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight.@Tooltip": "分别用红色和绿色高亮上锁和已解锁的地牢大门。", + "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight.@Tooltip": "分别用红色和绿色高亮上锁和已解锁的地下城大门。", "skyblocker.config.dungeons.doorHighlight.doorHighlightType": "高亮类型", "skyblocker.config.dungeons.mimicMessage.mimicMessage": "Mimic消息", - "skyblocker.config.dungeons.livid.enableLividColorGlow.@Tooltip": "为F5/M5的真Livid启用发光效果。", - "skyblocker.config.dungeons.dungeonChestProfit.neutralColor": "中性的颜色", - "skyblocker.config.dungeons.dungeonChestProfit.profitColor": "获利的颜色", + "skyblocker.config.dungeons.livid.enableLividColorGlow.@Tooltip": "为F5/M5的真 Livid 启用发光效果。", + "skyblocker.config.dungeons.dungeonChestProfit.neutralColor": "持平颜色", + "skyblocker.config.dungeons.dungeonChestProfit.profitColor": "盈利颜色", "skyblocker.config.dungeons.secretWaypoints.showSecretText": "显示秘密文本", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgrounds": "物品稀有度背景", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgroundsOpacity": "物品稀有度背景不透明度", - "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage": "地牢分数%d消息", - "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip": "当在地牢达到%d分数时,在聊天栏内发送信息。字符串“[score]”将替换为地牢分数(%d)。", - "skyblocker.option.quickNav.button.item": "物品", + "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage": "地牢 %d 分消息", + "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip": "当在地牢中达到%d分时,在聊天栏内发送信息。字符串“[score]”将替换为地牢分数(%d)。", "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor.@Tooltip": "价格数据不完整时显示的颜色。", - "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor": "不完整的颜色", + "skyblocker.config.dungeons.dungeonChestProfit.incompleteColor": "存在物品价格信息缺失时的颜色", "skyblocker.config.dungeons.dungeonScore.enableScoreHUD": "启用分数状态栏", - "skyblocker.config.quickNav.button.item.nbt": "NBT", "skyblocker.config.mining.dwarvenHud.enabledCommissions": "启用任务栏", "skyblocker.config.mining.dwarvenHud.enabledPowder": "启用粉末栏", "skyblocker.config.mining.crystalsHud": "水晶洞窟地图", @@ -258,7 +253,7 @@ "skyblocker.config.chat.filter.hideShowOff": "隐藏炫耀信息", "skyblocker.config.chat.filter.hideDeath.@Tooltip": "过滤聊天栏中玩家死亡的信息。", "skyblocker.config.general.itemTooltip.enableMuseumInfo.@Tooltip": "如果该物品可捐赠给博物馆,则会显示该物品在博物馆中的类别。它还会显示一个标记,指示您是否已将该物品捐赠给您的博物馆(尚不支持免费赠品)。\n\n为了获得准确信息,请启用博物馆API!", - "skyblocker.config.dungeons.dungeonChestProfit": "地牢宝箱利润计算器", + "skyblocker.config.dungeons.dungeonChestProfit": "地下城宝箱利润计算器", "skyblocker.config.mining.crystalsWaypoints": "水晶洞窟路径点", "skyblocker.config.mining.crystalsWaypoints.enabled": "启用路径点", "skyblocker.config.dungeons.secretWaypoints.waypointType.@Tooltip": "路径点:显示高亮和信标光束。\n\n边框路径点:显示路径点和边框。\n\n高亮:只显示高亮。\n\n边框高亮:显示高亮和边框。\n\n边框:只显示边框。", @@ -276,8 +271,8 @@ "skyblocker.config.uiAndVisuals.searchOverlay.enableCommands.@Tooltip": "使用“/bzs”打开集市搜索,使用“/ahs”打开拍卖行。需要重新登录才能更新此设置。", "skyblocker.config.general.searchOverlay.historyLabel": "历史记录:", "skyblocker.config.dungeons.doorHighlight.doorHighlightType.@Tooltip": "高亮:只显示高亮。\n\n边框+高亮:显示高亮与边框。\n\n边框:只显示边框。", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip": "当在地牢达到%d分数时显示标题。", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "当在地牢达到%d分数时播放音效。", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip": "当在地牢之中达到 %d 分时显示提示标题。", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "当在地牢中达到 %d 分时播放音效。", "skyblocker.config.dungeons.mimicMessage.sendMimicMessage.@Tooltip": "杀死Mimic后在聊天中发送一条消息,以供其他玩家的分数计算模块使用。", "skyblocker.config.chat.chatRules.screen": "聊天栏规则配置", "skyblocker.config.chat.chatRules.screen.modify": "修改", @@ -333,7 +328,7 @@ "skyblocker.shortcuts.commandArg.target": "目标命令参数", "skyblocker.tips.clickNextTip": "§a[点击查看下一条提示]", "skyblocker.tips.modMenuUpdate": "ModMenu会通知你是否有适用于你的游戏版本的Skyblocker更新。", - "skyblocker.partyFinder.partyCard.minDungeonLevel": "最低地牢等级要求:%d", + "skyblocker.partyFinder.partyCard.minDungeonLevel": "最低地下城等级要求:%d", "skyblocker.partyFinder.noParties": "未找到队伍。哭哭:(", "skyblocker.end.hud.location": "位置:%s", "skyblocker.customItemNames.noItemUuid": "§c你必须手持一个有uuid的物品才能为其设置自定义名称!", @@ -401,7 +396,7 @@ "skyblocker.end.hud.avgKillsPerEye": "平均每只眼所需击杀:%d", "skyblocker.dungeons.puzzle.boulder.noSolution": "没有找到解法!", "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit": "Croesus宝箱利润计算器", - "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit.@Tooltip": "高亮显示NPC Croesus处利润最高的宝箱。\n\n\n利润最高的宝箱将用绿色标识。\n如果有宝箱值得使用地牢钥匙,将使用黄色标识", + "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit.@Tooltip": "高亮显示NPC Croesus处利润最高的宝箱。\n\n\n利润最高的宝箱将用绿色标识。\n如果有宝箱值得使用地下城钥匙,将使用黄色标识", "skyblocker.dungeons.secrets.markSecretFound": "§r将秘密 #%d 标记为已找到。", "skyblocker.config.dungeons.puzzle.solveSilverfish": "蠹虫谜题助手", "skyblocker.config.dungeons.puzzle.solveIceFill": "冰块填充谜题助手", @@ -413,14 +408,14 @@ "skyblocker.config.crimsonIsle.kuudra.fuelWaypoints": "燃料路径点", "skyblocker.config.crimsonIsle.kuudra.safeSpotWaypoints": "安全点路径", "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning": "箭毒耗尽警告", - "skyblocker.config.dungeons.dungeonChestProfit.includeKismet.@Tooltip": "启用后,如果您使用kismet,则将从利润中减去kismet的价格", + "skyblocker.config.dungeons.dungeonChestProfit.includeKismet.@Tooltip": "启用后,如果您使用了kismet,则将从利润中减去kismet的价格", "skyblocker.config.uiAndVisuals.titleContainer": "标题容器", "skyblocker.config.uiAndVisuals.titleContainer.titleContainerScale": "标题容器缩放", "skyblocker.dungeons.secrets.markSecretMissing": "§r将秘密 #%d 标记为已忽略。", - "skyblocker.dungeons.secrets.physicalEntranceNotFound": "§c未找到地牢入口房间坐标。请返回绿色的入口房间。", + "skyblocker.dungeons.secrets.physicalEntranceNotFound": "§c未找到地下城入口房间坐标。请返回绿色的入口房间。", "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning.@Tooltip": "在使用弓箭但没有箭毒时发出警告。只在DPS阶段有效。", "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold": "箭毒警告阈值", - "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold.@Tooltip": "当背包中箭毒的数量低于设定的值时收到警告。", + "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold.@Tooltip": "当背包中箭毒的数量低于设定的值时收到警告。\n\n16是最小值。\n如果需要平滑的DPS阶段,应设置为32。", "skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency.@Tooltip": "值越低,更新越频繁,可能会导致卡顿。", "skyblocker.config.dungeons.professor.floor3GuardianHealthDisplay.@Tooltip": "在F3/M3的Boss战中,在守卫者下方显示它们的生命值。", "skyblocker.config.dungeons.secretWaypoints.enableSuperboomWaypoints": "启用爆破路径点", @@ -439,7 +434,7 @@ "skyblocker.config.dungeons.dungeonChestProfit.includeEssence.@Tooltip": "如果你健忘,**不建议**禁用。", "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.deathMessagesNote": "\n\n\n注意:仅当skyblock设置中启用了死亡消息时,此功能才会生效。如果想隐藏死亡消息,请使用此模组的隐藏玩家死亡消息设置来允许进一步处理死亡消息。", "skyblocker.config.dungeons.dungeonScore.scoreScaling": "分数缩放", - "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "在宝箱屏幕的标题中显示地牢宝箱的利润。\n如果获利则显示绿色。\n如果亏损则为红色。\n灰色表示没有获利也没有亏损。\n如果计算基于不完整的数据,则显示为蓝色。", + "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "在宝箱屏幕的标题中显示地下城宝箱的利润。\n如果获利则显示绿色。\n如果亏损则为红色。\n灰色表示没有获利也没有亏损。\n如果计算基于不完整的数据,则显示为蓝色。", "skyblocker.config.dungeons.dungeonChestProfit.includeKismet": "包含Kismet价格", "skyblocker.config.otherLocations.rift.enigmaSoulWaypoints": "启用Enigma之魂路径点", "skyblocker.config.otherLocations.rift.enigmaSoulWaypoints.@Tooltip": "注意:许多Enigma之魂需要一些前置任务方能获得,所以建议参考网络视频攻略。", @@ -447,9 +442,9 @@ "skyblocker.config.crimsonIsle.kuudra.ballistaBuildWaypoints": "弩炮建造点路径点", "skyblocker.config.otherLocations.rift.highlightFoundEnigmaSouls": "高亮已找到的Enigma之魂", "skyblocker.config.slayer.vampireSlayer": "吸血鬼杀手", - "skyblocker.config.dungeons.professor.fireFreezeStaffTimer": "开火冻结计时器(F3/M3)", - "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold": "中性的临界点", - "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold.@Tooltip": "低于这个数值的利润将被视为中性。", + "skyblocker.config.dungeons.professor.fireFreezeStaffTimer": "Fire Freeze Staff计时器(F3/M3)", + "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold": "持平阈值", + "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold.@Tooltip": "低于这个数值的利润将被视为持平。", "skyblocker.config.uiAndVisuals.hideStatusEffectOverlay": "隐藏状态效果动画", "skyblocker.tips.fancyTabExtraInfo": "你知道吗?在拿着N或M时可以在精致菜单中看到额外的信息。", "skyblocker.config.dungeons.map.mapScreen": "地牢地图和分数的位置设置...", @@ -459,9 +454,9 @@ "skyblocker.config.uiAndVisuals.flameOverlay.flameOpacity.@Tooltip": "100% 默认透明度\n0% 完全关闭", "skyblocker.config.mining.crystalsHud.screen": "水晶洞窟地图位置设置…", "skyblocker.config.mining.crystalsHud.showLocations.@Tooltip": "以方块显示水晶洞窟的重要区域,如丛林神庙和仙女石窟。", - "skyblocker.config.dungeons.secretWaypoints.enablePearlWaypoints.@Tooltip": "通过这些路径点,你可以通过同时使用末影珍珠和AOTV实现穿墙。", + "skyblocker.config.dungeons.secretWaypoints.enablePearlWaypoints.@Tooltip": "通过这些路径点,你可以通过同时使用末影珍珠和瞬息之刃实现穿墙。", "skyblocker.config.dungeons.allowDroppingProtectedItems.@Tooltip": "允许在地牢中通过被锁定的物品栏或受/skyblocker protectItem命令保护的物品施放职业能力。", - "skyblocker.config.dungeons.professor.fireFreezeStaffTimer.@Tooltip": "在F3/M3的Boss战中使用受到开火冻结影响的武器时显示计时器。", + "skyblocker.config.dungeons.professor.fireFreezeStaffTimer.@Tooltip": "在F3/M3的Boss战中显示Fire Frzeeze Staff的计时器", "skyblocker.config.mining.crystalsWaypoints.enabled.@Tooltip": "为丛林神庙和仙女石窟等水晶洞窟中的重要区域添加路径点(在常规/路径点中所选择的路径点)。 ", "skyblocker.config.chat.chatRules.screen.ruleScreen.replace.@Tooltip": "输入要输出的新消息(可以使用Minecraft颜色代码进行格式化)。", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.zombie": "僵尸", @@ -483,7 +478,7 @@ "skyblocker.customArmorTrims.noItemUuid": "§c你必须手持一个有uuid的物品才能为其设置自定义护甲装饰!", "skyblocker.tips.flameOverlay": "感觉火焰动画占据了太多屏幕空间?检查设置把它调小", "skyblocker.customDyeColors.neverHad": "§f该物品还没有自定义染料颜色,为什么不设置一个呢?;)", - "skyblocker.config.dungeons.secretWaypoints.waypointType.generalNote": "\n\n\n该选项不会对所有路径点都生效。地牢秘密路径点等有它们自己的设置。", + "skyblocker.config.dungeons.secretWaypoints.waypointType.generalNote": "\n\n\n该选项不会对所有路径点都生效。地下城秘密路径点等有它们自己的设置。", "skyblocker.customDyeColors.removed": "§f删除了该物品的自定义染料颜色。", "skyblocker.customDyeColors.notDyeable": "§c该物品不是可染色的护甲!", "skyblocker.customArmorTrims.neverHad": "§f该物品还没有自定义护甲装饰,为什么不设置一个呢?;)", @@ -582,5 +577,146 @@ "skyblocker.config.uiAndVisuals.bars.openScreen": "状态栏配置屏幕", "skyblocker.bars.config.explanationTitle": "这是什么?", "skyblocker.config.dungeons.starredMobBoundingBoxes": "星标怪物碰撞箱", - "skyblocker.config.dungeons.starredMobBoundingBoxes.@Tooltip": "绘制星标怪物的碰撞箱。" + "skyblocker.config.dungeons.starredMobBoundingBoxes.@Tooltip": "绘制星标怪物的碰撞箱。", + "skyblocker.config.dungeons.map": "地图", + "skyblocker.config.dungeons.puzzle": "谜题助手", + "skyblocker.config.general.itemTooltip.enableExoticTooltip": "启用异域风情提示", + "skyblocker.config.mining.dwarvenMines": "矮人矿井", + "skyblocker.config.otherLocations.end.zealotKillsEnabled": "显示Zealot击杀信息", + "skyblocker.config.otherLocations.rift.mcGrubberStacks.@Tooltip": "用于计算Motes销售价格。", + "skyblocker.config.otherLocations.rift.mirrorverseWaypoints": "启用Mirrorverse路径点", + "skyblocker.config.farming": "农业", + "skyblocker.config.helpers": "助手", + "skyblocker.config.mining.crystalHollows": "水晶洞窟", + "skyblocker.config.otherLocations.end.protectorLocationEnable": "启用保护者位置", + "skyblocker.config.uiAndVisuals.waypoints.waypointType": "路径点类型", + "skyblocker.config.uiAndVisuals.chestValue.incompleteColor": "不完整的颜色", + "skyblocker.config.uiAndVisuals.chestValue.incompleteColor.@Tooltip": "当价格数据不完整时显示的颜色。", + "skyblocker.config.uiAndVisuals.waypoints.waypointType.@Tooltip": "路径点:高亮显示并使用信标光束。\n\n边框路径点:显示路径点和边框。\n\n高亮:只显示高亮。\n\n边框+高亮:显示高亮与边框。\n\n边框:只显示边框。", + "skyblocker.config.general.itemTooltip.enableExoticTooltip.@Tooltip": "当护甲是异域风情时,在物品名称下方显示异域风情的类型。", + "skyblocker.config.slayer.vampireSlayer.enableHealingMelonIndicator": "启用治疗西瓜指示器", + "skyblocker.config.slayer.vampireSlayer.enableHolyIceIndicator": "启用神圣冰块指示器", + "skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency": "神圣冰块指示器更新频率(以游戏刻为单位)", + "skyblocker.shortcuts.commandSuggestionTooltip": "由于Minecraft的限制,命令建议只有在加入新世界后才会起作用。", + "skyblocker.config.uiAndVisuals": "UI&视觉效果", + "skyblocker.config.uiAndVisuals.dontStripSkinAlphaValues": "修正透明皮肤像素", + "skyblocker.config.slayer.vampireSlayer.holyIceIndicatorTickDelay": "神圣冰块指示器延迟(以游戏刻为单位)", + "skyblocker.config.mining.commissionWaypoints": "任务路径点", + "skyblocker.config.mining.commissionWaypoints.mode": "启用任务路径点", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[1]": "\nDwarven:仅在矮人矿井显示路径点。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[0]": "Off:不显示任务路径点。", + "skyblocker.config.dungeons.hideSoulweaverSkulls": "隐藏 Soulweaver 的头颅", + "skyblocker.config.dungeons.hideSoulweaverSkulls.@Tooltip": "隐藏使用 Soulweaver Gloves 时生成的头颅。", + "skyblocker.config.chat.filter": "过滤器", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[2]": "\nGlacite:仅在极寒隧道中显示路径点。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[3]": "\nBoth:显示矮人矿井和极寒隧道的路径点。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[4]": "\n(任务更新后生效)", + "skyblocker.config.mining.commissionWaypoints.useColor.@Tooltip": "为路径点文本着色以匹配其所在的方块。", + "skyblocker.config.mining.commissionWaypoints.textScale": "文本缩放", + "skyblocker.config.mining.commissionWaypoints.textScale.@Tooltip": "任务栏的缩放尺寸。", + "skyblocker.config.mining.commissionWaypoints.showBaseCamp.@Tooltip": "在极寒隧道中显示营地的路径点(任务更新后生效)。", + "skyblocker.config.mining.commissionWaypoints.showBaseCamp": "显示营地路径点", + "skyblocker.config.mining.commissionWaypoints.showEmissary": "显示使者", + "skyblocker.config.mining.commissionWaypoints.showEmissary.@Tooltip": "当矮人矿井中的任务完成时,显示使者的位置(任务更新后生效)。", + "skyblocker.config.mining.commissionWaypoints.useColor": "路径点着色", + "skyblocker.config.dungeons.terminals.blockIncorrectClicks": "阻止错误的点击", + "skyblocker.config.uiAndVisuals.dontStripSkinAlphaValues.@Tooltip": "启用后,在Skyblock中,皮肤纹理中像素的Alpha值不再被剥离。\n\n这导致使用玩家头的物品上的“填充”像素现在完全透明,尽管这在奇怪的情况下可能会产生一些副作用。", + "skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals": "仅当“=”时显示。", + "skyblocker.config.uiAndVisuals.inputCalculator": "输入计算器", + "skyblocker.config.uiAndVisuals.inputCalculator.enabled": "启用告示牌计算器", + "skyblocker.config.uiAndVisuals.inputCalculator.enabled.@Tooltip": "使你可以在输入诸如拍卖价格之类的值时进行计算。\n 单位:\n S = 64\n E = 160\n K = 1,000\n M = 1,000,000\n B = 1,000,000,000\n\npurse/P = 当前钱包中的硬币数", + "skyblocker.config.uiAndVisuals.compactDamage": "简洁的伤害数值", + "skyblocker.config.uiAndVisuals.compactDamage.enabled": "启用", + "skyblocker.config.uiAndVisuals.compactDamage.precision": "精确度", + "skyblocker.config.uiAndVisuals.compactDamage.precision.@Tooltip": "小数点后的位数。", + "skyblocker.config.uiAndVisuals.compactDamage.normalDamageColor": "常规伤害颜色", + "skyblocker.config.uiAndVisuals.compactDamage.critDamageGradientStart": "暴击伤害渐变颜色的起始色", + "skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals.@Tooltip": "仅当信息以“=”开头时才显示计算器。", + "skyblocker.config.uiAndVisuals.inputCalculator.invalidEquation": "无效方程", + "skyblocker.config.uiAndVisuals.compactDamage.critDamageGradientEnd": "暴击伤害渐变颜色的结束色", + "skyblocker.config.uiAndVisuals.waypoints.waypointType.generalNote": "\n\n\n此选项并不适用于所有路径点。某些路径点(例如地下城秘密路径点)有自己的路径点类型选项。", + "skyblocker.config.foraging.hunting": "狩猎", + "skyblocker.config.foraging": "林业", + "skyblocker.config.helpers.chocolateFactory": "巧克力工厂", + "skyblocker.config.helpers.chocolateFactory.enableChocolateFactoryHelper": "启用巧克力工厂助手", + "skyblocker.config.helpers.chocolateFactory.enableChocolateFactoryHelper.@Tooltip": "启用时,高亮显示最合适的升级。\n\n最合适的升级将以绿色显示,但如果你没有足够的巧克力,则将以黄色显示,并将你能购买的最佳升级标记为绿色。", + "skyblocker.config.helpers.chocolateFactory.waypointType.@Tooltip": "路径点:高亮显示以及信标光束。\n\n边框+路径点:显示路径点和边框。\n\n高亮:只显示高亮。\n\n边框+高亮:显示高亮与边框。\n\n边框:只显示边框。", + "skyblocker.config.helpers.chocolateFactory.enableEggFinder": "启用寻蛋助手", + "skyblocker.config.helpers.chocolateFactory.enableEggFinder.@Tooltip": "高亮显示Hoppity的寻蛋活动期间的彩蛋。", + "skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder": "启用时间塔提醒", + "skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages": "发送彩蛋已找到的信息", + "skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages.@Tooltip": "当在当前岛屿中发现彩蛋时在聊天栏中发送提示信息。", + "skyblocker.config.helpers.chocolateFactory.waypointType": "彩蛋路径点类型", + "skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder.@Tooltip": "当时间塔停止运作时,在聊天栏发送一条提示信息。", + "skyblocker.config.mining.glacite.coldOverlay": "寒冷效果动画修改", + "skyblocker.config.mining.glacite": "极寒隧道", + "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "显示极寒隧道中的霜冻效果,随着温度的降低,霜冻效果会变得更强。", + "skyblocker.config.misc": "杂项", + "skyblocker.config.general.searchOverlay.maxPet.@Tooltip": "只显示最高等级的宠物", + "skyblocker.config.general.searchOverlay.starsTooltip": "地下城物品的星星数量", + "skyblocker.config.general.searchOverlay.maxPet": "最高宠物等级", + "skyblocker.config.eventNotifications": "活动通知", + "skyblocker.config.eventNotifications.notificationSound": "通知声音", + "skyblocker.config.eventNotifications.@Tooltip[0]": "设置在活动开始前多久你将收到通知!例如,如果在列表中设置“5m”和“30s”,你将分别在活动开始前5分钟和30秒前收到通知。", + "skyblocker.config.eventNotifications.@Tooltip[2]": "该列表将修改“%s”活动", + "skyblocker.config.eventNotifications.@Tooltip[1]": "顺序并不重要。如果想禁用通知,清空列表即可。", + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.anvil": "铁砧", + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.arrowHit": "箭:击中玩家", + "skyblocker.config.quickNav.button.render": "启用", + "skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency": "Effigy路径点更新频率(以游戏刻为单位)", + "skyblocker.config.slayer.vampireSlayer.enableEffigyWaypoints": "启用Effigy路径点", + "skyblocker.config.slayer.vampireSlayer.healingMelonHealthThreshold": "疗伤西瓜指示器阈值(单位:心)", + "skyblocker.events.startsSoon": "%s即将开始!", + "skyblocker.customAnimatedDyes.noItemUuid": "§c你必须手持一个有UUID的物品才能设置自定义动态染色。", + "skyblocker.customAnimatedDyes.removed": "移除了该物品的自定义动态染色。", + "skyblocker.config.slayer.vampireSlayer.enableSteakStakeIndicator": "启用Steak Stake指示器", + "skyblocker.config.quickNav.button.item.components": "物品组件", + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.pling": "音符盒-电钢琴", + "skyblocker.customAnimatedDyes.added": "成功为你当前所持物品设置了自定义动态染色!", + "skyblocker.config.general.itemTooltip.dungeonQuality.@Tooltip": "显示地牢怪物掉落物的品质和等级。\n\n\n提醒:\nT1-3 来自 F1-F3\nT4-7 来自 F4-F7 或 M1-M4\nT8-10 来自 M5-M7", + "skyblocker.config.quickNav.button.item.components.@Tooltip": "用方括号括起来的项目组件字符串,其格式与/give命令相同。\n\n例如:[minecraft:enchantment_glint_override=true]", + "skyblocker.config.slayer.vampireSlayer.maniaUpdateFrequency": "Mania指示器更新频率(以游戏刻为单位)", + "skyblocker.customAnimatedDyes.neverHad": "该物品还没有自定义动态染色,为什么不设置一个呢? :)", + "skyblocker.config.slayer.vampireSlayer.steakStakeUpdateFrequency": "Steak Stake指示器更新频率(以游戏刻为单位)", + "skyblocker.config.crimsonIsle": "绯红岛", + "skyblocker.dungeons.secrets.notMatched": "§c当前房间不匹配!(你在地下城房间中吗?)", + "skyblocker.config.quickNav.button.clickEvent": "点击事件", + "skyblocker.dungeons.secrets.noTarget": "§c没有发现目标方块!(目标方块是否在范围内?)", + "skyblocker.events.tab.endsIn": "%s后结束", + "skyblocker.events.startsNow": "%s开始了!", + "skyblocker.events.tab.clickToWarp": "点击传送!", + "skyblocker.events.tab.startsIn": "%s后开始", + "skyblocker.waypoints.newCategory": "新路径点分类", + "skyblocker.waypoints.new": "新路径点", + "skyblocker.waypoints.share": "分享", + "skyblocker.waypoints.shareWaypoints": "分享路径点", + "skyblocker.waypoints.importWaypointsSkytils.tooltip": "从剪贴板导入路径点(Skytils格式)", + "skyblocker.waypoints.importWaypointsSnoopy": "导入路径点(Snoopy)", + "skyblocker.waypoints.exportWaypointsSkytils": "导出路径点(Skytils)", + "skyblocker.waypoints.exportWaypointsSkytils.tooltip": "将路径点导出至剪贴板(Skytils格式)", + "skyblocker.waypoints.importSuccess": "路径点已导入", + "skyblocker.waypoints.importError": "导入路径点失败", + "skyblocker.waypoints.importErrorText": "导入路径点失败。详细信息请参阅日志。", + "skyblocker.waypoints.importSuccessText": "成功从%d个分类导入%d个路径点。", + "skyblocker.waypoints.exportSuccess": "路径点已导出", + "skyblocker.waypoints.exportError": "导出路径点失败", + "skyblocker.waypoints.exportErrorText": "导出路径点失败。详细信息请参阅日志。", + "skyblocker.waypoints.deleteQuestion": "你确定要删除该路径点吗?", + "skyblocker.config.general.itemInfoDisplay.slotText.@Tooltip": "显示附魔书等级、Minion等级、宠物等级、药水等级、史前蛋的行走步数、牧场主靴子速度上限和技能等级等信息", + "skyblocker.waypoints.config": "路径点设置", + "skyblocker.waypoints.importWaypointsSkytils": "导入路径点(Skytils)", + "skyblocker.waypoints.importWaypointsSnoopy.tooltip": "从剪贴板导入路径点(Snoopy格式)(即将到来)", + "skyblocker.waypoints.exportSuccessText": "成功从%d个分类导出%d个路径点。", + "skyblocker.waypoints.deleteWarning": "路径点“%s”将永远消失!(真的很久!)", + "skyblocker.config.slayer.vampireSlayer.enableManiaIndicator": "启用Mania Block指示器", + "skyblocker.events.tab.noMore": "今年没有更多活动了!", + "skyblocker.customAnimatedDyes.unableToSetDye": "§c无法设置自定义动态染料 :( (请检查自己是否在Skyblock中,以及是否持有物品)", + "skyblocker.tips.customAnimatedDyes": "你可以使用 /skyblocker custom animatedDye 将自定义动态染料应用到你的皮革护甲上!", + "skyblocker.tips.customDungeonSecretWaypoints": "你可以使用 /skyblocker dungeons secrets addWaypoint 将自定义秘密路径点添加到任何地下城房间。", + "skyblocker.config.general.itemInfoDisplay.slotText": "物品栏文本", + "skyblocker.shortcuts.commandArg.tooltip": "替换具有多个单词/参数的命令的一个或多个单词/参数,而不是匹配整个命令。", + "skyblocker.waypoints.ordered.export.success": "成功将路径点复制到剪贴板!", + "skyblocker.waypoints.ordered.export.fail": "§c路径点导出失败,请检查latest.log以获取更多信息。", + "skyblocker.config.general.itemInfoDisplay.attributeShardInfo.@Tooltip": "以物品堆叠数的形式显示 Attribute Ahard 的等级及词头", + "skyblocker.config.otherLocations.rift": "裂域" } diff --git a/src/main/resources/assets/skyblocker/lang/zh_tw.json b/src/main/resources/assets/skyblocker/lang/zh_tw.json index f5318f25..b0bba930 100644 --- a/src/main/resources/assets/skyblocker/lang/zh_tw.json +++ b/src/main/resources/assets/skyblocker/lang/zh_tw.json @@ -17,7 +17,6 @@ "skyblocker.config.chat": "訊息", "skyblocker.config.general.itemTooltip.avg": "平均類型", "skyblocker.config.uiAndVisuals.bars.enableBars": "啟用計量條", - "skyblocker.option.general.bars.barpositions": "設定計量條位置", "skyblocker.config.general.itemTooltip.enableNPCPrice": "顯示NPC價格", "skyblocker.config.general.itemTooltip.enableAvgBIN": "顯示平均BIN(立即購買)價格", "skyblocker.config.uiAndVisuals.backpackPreviewWithoutShift": "檢視揹包預覽無需按住shift鍵", @@ -58,9 +57,7 @@ "skyblocker.config.helpers.fishing": "釣魚助手", "key.skyblocker.toggleB": "將HUD分頁切換至顯示屏B", "key.skyblocker.toggleA": "將HUD分頁切換至顯示屏A", - "skyblocker.config.quickNav.button.item.itemName": "物品ID", - "skyblocker.option.quickNav.button.item": "物品", - "skyblocker.config.quickNav.button.item.nbt": "NBT", + "skyblocker.config.quickNav.button.item.itemName": "物品", "skyblocker.config.quickNav.button.uiTitle": "UI標題", "skyblocker.config.quickNav.button.item.count": "物品數量", "skyblocker.config.crimsonIsle.kuudra.pearlWaypoints": "末影珍珠路徑點", @@ -78,7 +75,7 @@ "skyblocker.dungeons.secretsTracker.feedback": "%s§f找到了%s§f個秘密。 %s", "skyblocker.partyFinder.noParties": "未找到隊伍。 哭哭:(", "skyblocker.partyFinder.error.name": "隊伍查找器錯誤!", - "skyblocker.config.dungeons": "地牢", + "skyblocker.config.dungeons": "地下城", "skyblocker.config.chat.chatRules": "自訂聊天規則", "skyblocker.config.chat.chatRules.screen.new": "新聊天規則", "skyblocker.config.chat.chatRules.screen": "聊天欄規則配置…", @@ -91,7 +88,7 @@ "skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch.@Tooltip": "過濾器是否可以匹配部分聊天訊息。", "skyblocker.config.chat.chatRules.screen.ruleScreen.outputs": "輸出:", "skyblocker.config.helpers.mythologicalRitual": "神話儀式助手", - "skyblocker.config.helpers.mythologicalRitual.enableMythologicalRitualHelper": "開啟神話儀式助手", + "skyblocker.config.helpers.mythologicalRitual.enableMythologicalRitualHelper": "啟用Mythological Ritual助手", "skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip": "從聊天中移除這則訊息。", "skyblocker.config.chat.chatRules.screen.ruleScreen.finish": "完成", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds": "播放音效:", @@ -117,7 +114,7 @@ "skyblocker.customDyeColors.removed": "§f刪除了該物品的自訂染料顏色。", "skyblocker.config.general.wikiLookup.enableWikiLookup": "啟用wiki查閲功能", "skyblocker.config.general.wikiLookup": "查閲wiki", - "skyblocker.config.dungeons.playerSecretsTracker.@Tooltip": "追蹤玩家在地牢內找到的秘密數量。", + "skyblocker.config.dungeons.playerSecretsTracker.@Tooltip": "追蹤玩家在地下城內找到的秘密數量。", "skyblocker.config.dungeons.playerSecretsTracker": "玩家解謎追蹤器", "skyblocker.config.uiAndVisuals.compactorDeletorPreview": "啟用個人壓縮器/刪除器預覽", "skyblocker.config.mining.crystalsHud.showLocations": "顯示路徑點", @@ -150,7 +147,7 @@ "skyblocker.tips.clickDisable": "§a[點擊禁用提示]", "skyblocker.tips.tip": "§a提示:%s\n", "skyblocker.tips.shortcuts": "使用 /skyblocker shortcuts 建立和編輯指令和訊息快捷方式。", - "skyblocker.config.general.specialEffects.rareDungeonDropEffects.@Tooltip": "為地牢稀有掉落添加特殊的視覺效果!", + "skyblocker.config.general.specialEffects.rareDungeonDropEffects.@Tooltip": "為地下城稀有掉落添加特殊的視覺效果!", "skyblocker.config.general.wikiLookup.enableWikiLookup.@Tooltip": "滑鼠指針懸停在物品上,按F4鍵打開wiki中的條目。", "skyblocker.config.general.wikiLookup.officialWiki": "使用Hypixel官方維基", "skyblocker.config.general.wikiLookup.officialWiki.@Tooltip": "在查閲時使用Hypixel官方維基代替Fandom維基。", @@ -159,7 +156,7 @@ "skyblocker.config.dungeons.livid.enableLividColorGlow": "啟用Livid高亮", "skyblocker.config.uiAndVisuals.teleportOverlay.enableSinrecallTransmission": "啟用Sinrecall Transmission技能目標位置顯示", "skyblocker.config.misc.richPresence": "Discord活動狀態", - "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "在寶箱畫面的標題中顯示地牢寶箱的利潤。\n如果獲利則顯示綠色。\n如果虧損則為紅色。\n灰色表示沒有獲利也沒有虧損。\n如果計算是基於不完整的數據,則顯示為藍色。", + "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "在寶箱畫面的標題中顯示地下城寶箱的利潤。\n如果獲利則顯示綠色。\n如果虧損則為紅色。\n灰色表示沒有獲利也沒有虧損。\n如果計算是基於不完整的數據,則顯示為藍色。", "skyblocker.config.dungeons.dungeonChestProfit.includeKismet": "包含Kismet價格", "skyblocker.config.slayer.endermanSlayer": "終界使者杀手", "text.skyblocker.quit_config_sure": "確定退出嗎?你的修改將不會被保存!", @@ -180,12 +177,12 @@ "skyblocker.config.general.itemTooltip.enableMuseumInfo.@Tooltip": "如果該物品可捐贈給博物館,則會顯示該物品在博物館中的類別。 它還會顯示一個標記,指示您是否已將該物品捐贈給您的博物館(尚不支持免費贈品)。\n\n為了獲得準確資訊,請啟用博物館API!", "skyblocker.config.dungeons.dungeonChestProfit.neutralThreshold": "中性的臨界值", "skyblocker.config.helpers.fishing.enableFishingHelper": "啟用釣魚助手", - "skyblocker.config.general.shortcuts.enableCommandShortcuts.@Tooltip": "此功能可用較短的別名替換較長的原始指令,使用「/skyblocker shortcuts」以編輯。(需要啟用快捷指令功能才可生效)", + "skyblocker.config.general.shortcuts.enableCommandShortcuts.@Tooltip": "此功能可用較短的別名替換較長的原始指令,使用「/skyblocker shortcuts」以編輯。需要啟用快捷指令功能才可生效。", "skyblocker.config.dungeons.secretWaypoints.enableAotvWaypoints": "啟用AOTV路徑點", "skyblocker.config.dungeons.puzzle.solveWaterboard.@Tooltip": "點擊帶有綠色框的控制桿來解決謎題。", "skyblocker.config.dungeons.puzzle.solveSilverfish": "蠹蟲謎題助手", "skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch": "允許部分匹配:", - "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight.@Tooltip": "分別用紅色和綠色高亮上鎖和解鎖的地牢大門。", + "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight.@Tooltip": "分別用紅色和綠色高亮上鎖和解鎖的地下城大門。", "skyblocker.config.chat.chatRules.screen.ruleScreen.locations": "生效地點:", "skyblocker.config.dungeons.doorHighlight": "高亮門", "skyblocker.config.dungeons.doorHighlight.enableDoorHighlight": "啟用門的高亮", @@ -244,23 +241,22 @@ "skyblocker.fishing.reelNow": "收竿!", "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold": "箭毒警告閾值", "skyblocker.config.dungeons.secretWaypoints.enableEntranceWaypoints": "啟用入口路徑點", - "skyblocker.config.dungeons.secretWaypoints": "地牢秘密路徑點", + "skyblocker.config.dungeons.secretWaypoints": "地下城秘密路徑點", "skyblocker.config.general.itemTooltip.enableMotesPrice": "顯示Motes價格", "skyblocker.customArmorTrims.neverHad": "§f該物品還沒有自訂護甲裝飾,為什麼不設置一個呢? ;)", "skyblocker.config.dungeons.livid.lividColorText": "真Livid顏色提示訊息文本", - "skyblocker.config.dungeons.map.mapScreen": "地牢地圖和分數的位置設定...", + "skyblocker.config.dungeons.map.mapScreen": "地下城地圖和分數的位置設定...", "skyblocker.partyFinder.partyCard.minClassLevel": "最低職業等級要求:%d", "skyblocker.tips.modMenuUpdate": "ModMenu會通知你是否有適用於你的遊戲版本的Skyblocker更新。", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip": "在地牢中達到%d分時發送提示訊息。", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle": "啟用地牢分數%d的標題", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage": "啟用地牢%d分數提示訊息", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip": "當在地牢達到%d分數時顯示標題。", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip": "在地下城中達到%d分時發送提示訊息。", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle": "啟用地下城分數%d的標題", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreMessage": "啟用地下城%d分數提示訊息", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip": "當在地下城達到%d分數時顯示標題。", "skyblocker.config.dungeons.livid.enableLividColorText.@Tooltip": "將真Livid的顏色傳送到聊天欄。", "skyblocker.config.dungeons.terminals.solveColor": "選色終端助手", "skyblocker.config.mining.dwarvenHud.style.@Tooltip[1]": "\n精緻:顯示委託名,進度百分比與進度條以及圖示。", "skyblocker.config.mining.dwarvenHud.style.@Tooltip[2]": "\n極簡:僅在簡單的方框內顯示委託及其進度。", "skyblocker.tips.customArmorDyeColors": "使用 /skyblocker custom dyeColor 將自訂染料顏色應用到你的皮革護甲上", - "skyblocker.option.general.etherwarpOverlay": "Etherwarp技能目標位置顯示", "skyblocker.config.uiAndVisuals.tabHud.plainPlayerNames": "簡潔的玩家名稱", "skyblocker.config.uiAndVisuals.tabHud.nameSorting": "玩家名排列順序", "skyblocker.config.uiAndVisuals.tabHud.plainPlayerNames.@Tooltip": "開啟後在公共島嶼顯示玩家名稱時不顯示任何特殊效果。", @@ -270,20 +266,20 @@ "skyblocker.config.uiAndVisuals.waypoints": "路徑點", "skyblocker.config.uiAndVisuals.waypoints.enableWaypoints": "啟用路徑點", "skyblocker.config.dungeons.secretWaypoints.waypointType.@Tooltip": "路徑點:顯示高亮和信標光束。\n\n邊框路徑點:顯示路徑點和邊框。\n\n高亮:只顯示高亮。\n\n邊框高亮:顯示高亮和邊框。\n\n邊框:只顯示邊框。", - "skyblocker.config.dungeons.secretWaypoints.waypointType.generalNote": "\n\n\n此選項不會對所有路徑點都生效。 地牢秘密路徑點等有它們自己的設定。", + "skyblocker.config.dungeons.secretWaypoints.waypointType.generalNote": "\n\n\n此選項不會對所有路徑點都生效。 地下城秘密路徑點等有它們自己的設定。", "skyblocker.config.dungeons.secretWaypoints.waypointType": "路徑點類型", - "skyblocker.config.dungeons.professor.fireFreezeStaffTimer": "開火凍結計時器(F3/M3)", + "skyblocker.config.dungeons.professor.fireFreezeStaffTimer": "Fire Freeze Staff計時器(F3/M3)", "skyblocker.config.otherLocations.spidersDen": "蜘蛛巢穴", "skyblocker.relics.markAllMissing": "§r將全部遺物標記為已忽略", - "skyblocker.config.general.specialEffects.rareDungeonDropEffects": "稀有地牢掉落效果", + "skyblocker.config.general.specialEffects.rareDungeonDropEffects": "稀有地下城掉落效果", "skyblocker.config.uiAndVisuals.tabHud.nameSorting.@Tooltip": "「Alphabetical」以字典序排列玩家,而「Default」以Hypixel預設順序排列。", "skyblocker.config.otherLocations.spidersDen.relics.highlightFoundRelics": "突顯已發現的遺物", - "skyblocker.config.dungeons.secretWaypoints.enableSecretWaypoints": "啟用地牢秘密路徑點", + "skyblocker.config.dungeons.secretWaypoints.enableSecretWaypoints": "啟用地下城秘密路徑點", "skyblocker.config.dungeons.dungeonChestProfit.profitColor": "獲利的顔色", "skyblocker.config.dungeons.dungeonChestProfit.neutralColor": "中性的顏色", "skyblocker.config.dungeons.croesusHelper.@Tooltip": "將打開過的箱子標記為灰色。", "skyblocker.config.dungeons.starredMobGlow": "使星標怪物發光", - "skyblocker.config.dungeons.allowDroppingProtectedItems.@Tooltip": "允許在地牢中透過被鎖定的物品欄或受/skyblocker protectItem指令保護的物品施放職業能力。", + "skyblocker.config.dungeons.allowDroppingProtectedItems.@Tooltip": "允許在地下城中透過被鎖定的物品欄或受/skyblocker protectItem指令保護的物品施放職業能力。", "skyblocker.config.general.specialEffects": "特效", "skyblocker.config.general.itemInfoDisplay": "物品資訊顯示", "skyblocker.itemTooltip.noData": "§c沒有數據", @@ -295,20 +291,19 @@ "skyblocker.config.uiAndVisuals.itemCooldown": "物品冷卻提示", "skyblocker.config.uiAndVisuals.itemCooldown.enableItemCooldowns": "啟用物品冷卻提示", "skyblocker.config.general.quiverWarning.enableQuiverWarning": "啟用箭袋提示", - "skyblocker.config.general.quiverWarning.enableQuiverWarningInDungeons": "在地牢内啟用箭袋提示", - "skyblocker.config.general.quiverWarning.enableQuiverWarningAfterDungeon": "在地牢結束後啟用箭袋提示", - "skyblocker.option.locations.spidersDen.relics": "遺物助手", + "skyblocker.config.general.quiverWarning.enableQuiverWarningInDungeons": "在地下城内啟用箭袋提示", + "skyblocker.config.general.quiverWarning.enableQuiverWarningAfterDungeon": "在地下城結束後啟用箭袋提示", "skyblocker.config.dungeons.secretWaypoints.enableChestWaypoints": "啟用箱子路徑點", "skyblocker.customArmorTrims.removed": "§f移除了該物品的自訂護甲裝飾。", "skyblocker.config.otherLocations.spidersDen.relics.enableRelicsHelper": "啟用遺物助手", "skyblocker.config.farming.garden": "花園", "skyblocker.config.farming.garden.dicerTitlePrevent": "阻止Dicer顯示標題", "skyblocker.config.dungeons.doorHighlight.doorHighlightType.@Tooltip": "高亮:只顯示高亮。\n\n邊框+高亮:顯示高亮與邊框。\n\n邊框:只顯示邊框。", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound": "啟用地牢分數%d的音效", - "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "當在地牢達到%d分數時播放音效。", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound": "啟用地下城分數%d的音效", + "skyblocker.config.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "當在地下城達到%d分數時播放音效。", "skyblocker.config.dungeons.dungeonChestProfit.lossColor": "虧損的顔色", "skyblocker.config.dungeons.terminals.solveStartsWith": "首字母謎題終端助手", - "skyblocker.config.dungeons.dungeonChestProfit": "地牢寶箱利潤計算器", + "skyblocker.config.dungeons.dungeonChestProfit": "地下城寶箱利潤計算器", "skyblocker.config.dungeons.dungeonChestProfit.includeEssence.@Tooltip": "如果你健忘,**不建議**禁用。", "skyblocker.config.dungeons.starredMobGlow.@Tooltip": "為玩家可見的星標怪物添加發光效果。\n\n注意:此功能是實驗性的,玩家在使用時可能會遇到一些問題。", "skyblocker.config.dungeons.puzzle.solveThreeWeirdos": "解決三個怪人的謎題", @@ -329,11 +324,11 @@ "skyblocker.config.general.itemInfoDisplay.itemRarityBackgrounds.@Tooltip": "將物品背景顏色顯示為其稀有度所對應的顔色。", "skyblocker.config.general.itemInfoDisplay.itemRarityBackgroundsOpacity": "物品稀有度背景不透明度", "skyblocker.config.mining.dwarvenMines.solvePuzzler": "解開Puzzler的謎題", - "text.skyblocker.open": "開啟", + "text.skyblocker.open": "打開", "skyblocker.dungeons.secrets.markSecretFound": "§r將秘密 #%d 標記為已找到。", - "skyblocker.config.dungeons.dungeonScore": "地牢分數", - "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage": "地牢分數%d訊息", - "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip": "當在地牢達到%d分數時,在聊天欄內發送訊息。 字串「[score]」將替換為地牢分數(%d)。", + "skyblocker.config.dungeons.dungeonScore": "地下城分數", + "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage": "地下城分數%d訊息", + "skyblocker.config.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip": "當在地下城達到%d分數時,在聊天欄內發送訊息。 字串「[score]」將替換為地下城分數(%d)。", "skyblocker.quiverWarning.50Left": "你的箭袋裡只剩50支箭了!", "skyblocker.wikiLookup.noArticleFound": "§r無法找到此物品的維基條目...", "skyblocker.config.dungeons.secretWaypoints.enableFairySoulWaypoints": "啟用仙女之魂路徑點", @@ -347,7 +342,7 @@ "skyblocker.config.dungeons.secretWaypoints.enableDefaultWaypoints": "啟用預設路徑點", "skyblocker.fairySouls.markAllFound": "§r將目前島嶼上的全部仙女之魂標記為已發現", "skyblocker.config.dungeons.dungeonScore.enableScoreHUD": "啟用分數狀態欄", - "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.@Tooltip": "在狀態欄中顯示地牢的分數。", + "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.@Tooltip": "在狀態欄中顯示地下城的分數。", "skyblocker.config.dungeons.dungeonScore.enableScoreHUD.deathMessagesNote": "\n\n\n注意:僅當skyblock設定中啟用了死亡訊息時,此功能才會生效。 如果想要隱藏死亡訊息,請使用此模組的隱藏玩家死亡訊息設定來允許進一步處理死亡訊息。", "skyblocker.config.dungeons.puzzle.solveTicTacToe.@Tooltip": "以紅色方塊標記井字棋的下一步!", "skyblocker.config.dungeons.mimicMessage": "Mimic訊息", @@ -356,7 +351,7 @@ "skyblocker.config.dungeons.mimicMessage.mimicMessage": "Mimic訊息", "skyblocker.config.dungeons.livid": "提示真Livid的顏色", "skyblocker.config.dungeons.mimicMessage.mimicMessage.@Tooltip": "殺死Mimic後將在聊天中發送的訊息。 建議保留預設值。", - "skyblocker.config.dungeons.professor.fireFreezeStaffTimer.@Tooltip": "在F3/M3的Boss戰中使用受到開火凍結影響的武器時顯示計時器。", + "skyblocker.config.dungeons.professor.fireFreezeStaffTimer.@Tooltip": "在F3/M3的Boss戰中顯示Fire Freeze Staff的計時器。", "skyblocker.config.mining": "矮人礦井", "skyblocker.config.chat.filter.hideDeath.@Tooltip": "過濾聊天欄中玩家死亡的訊息。", "skyblocker.config.chat.filter.hideDeath": "隱藏玩家死亡訊息", @@ -387,7 +382,7 @@ "skyblocker.config.dungeons.puzzle.solveBoulder.@Tooltip": "繪製路線並高亮按鈕", "skyblocker.config.dungeons.livid.enableLividColorTitle": "啟用Livid顏色標題", "skyblocker.config.mining.crystalsWaypoints.findInChat": "在聊天欄中搜尋路徑點", - "skyblocker.config.general.itemTooltip.dungeonQuality": "地牢物品品質", + "skyblocker.config.general.itemTooltip.dungeonQuality": "地下城物品品質", "skyblocker.config.uiAndVisuals.searchOverlay.historyLength.@Tooltip": "介面顯示的搜尋歷史的最大數量。", "skyblocker.config.uiAndVisuals.searchOverlay.enableCommands": "從指令打開", "skyblocker.tips.issues": "在 https://github.com/SkyblockerMod/Skyblocker 提交錯誤報告和功能請求。", @@ -401,7 +396,7 @@ "skyblocker.partyFinder.yourParty": "你的隊伍", "skyblocker.crimson.kuudra.noArrowPoison": "箭毒已耗盡!", "skyblocker.crimson.kuudra.lowArrowPoison": "箭毒即將耗盡!", - "skyblocker.config.uiAndVisuals.tabHud": "更好的Tab HUD(在地牢外臨時禁用)", + "skyblocker.config.uiAndVisuals.tabHud": "更好的Tab HUD(在地下城外臨時禁用)", "skyblocker.config.general.itemTooltip.enableMotesPrice.@Tooltip": "顯示Rift中的物品價格,以Mote為計量單位。", "skyblocker.config.uiAndVisuals.tabHud.tabHudEnabled": "啟用更好的Tab HUD", "skyblocker.config.uiAndVisuals.teleportOverlay": "傳送類型技能目標位置顯示", @@ -424,7 +419,7 @@ "skyblocker.config.crimsonIsle.kuudra.noArrowPoisonWarning.@Tooltip": "使用弓箭但沒有箭毒時發出警告。 只在DPS階段有效。", "skyblocker.config.dungeons.puzzle.blazeSolver": "烈焰人謎題助手", "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit": "Croesus寶箱利潤計算器", - "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit.@Tooltip": "高亮顯示NPC Croesus處利潤最高的寶箱。\n\n\n利潤最高的寶箱將以綠色標示。\n如果有寶箱值得使用地牢鑰匙,將使用黃色標識", + "skyblocker.config.dungeons.dungeonChestProfit.croesusProfit.@Tooltip": "高亮顯示NPC Croesus處利潤最高的寶箱。\n\n\n利潤最高的寶箱將以綠色標示。\n如果有寶箱值得使用地下城鑰匙,將使用黃色標識", "skyblocker.config.mining.crystalsHud.showLocations.@Tooltip": "以方塊顯示水晶洞窟的重要區域,如叢林遺跡和仙女石窟。", "skyblocker.config.mining.crystalsWaypoints.findInChat.@Tooltip": "在水晶洞窟時,讀取聊天內容以查看是否有座標,並將其提取出顯示為路徑點或在地圖中標出", "skyblocker.config.otherLocations.end.waypoint": "終界守護者路徑點", @@ -445,7 +440,7 @@ "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.none": "無", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.cave": "洞窟", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.zombie": "僵尸", - "skyblocker.dungeons.secrets.physicalEntranceNotFound": "§c未找到地牢入口房間座標。 請返回綠色的入口房間。", + "skyblocker.dungeons.secrets.physicalEntranceNotFound": "§c未找到地下城入口房間座標。 請返回綠色的入口房間。", "skyblocker.tips.protectItem": "使用 /skyblocker protectItem 防止意外掉落物品。", "skyblocker.partyFinder.error.message": "出現錯誤,將自動轉回原版的隊伍查找器", "skyblocker.fairySouls.markAllMissing": "§r將目前島嶼上的全部仙女之魂標記為已忽略", @@ -462,13 +457,13 @@ "skyblocker.config.uiAndVisuals.searchOverlay.keepPreviousSearches.@Tooltip": "開啟介面時保留已有的搜尋記錄。", "skyblocker.config.uiAndVisuals.searchOverlay.historyLength": "保存的搜尋歷史長度", "skyblocker.config.dungeons.secretWaypoints.enablePearlWaypoints.@Tooltip": "透過這些路徑點,你可以透過同時使用末影珍珠和AOTV實現穿牆。", - "skyblocker.config.dungeons.doorHighlight.doorHighlightType.secretWaypointsNote": "\n\n\n注意:若使此功能生效,必須啟用地牢秘密路徑點。", + "skyblocker.config.dungeons.doorHighlight.doorHighlightType.secretWaypointsNote": "\n\n\n注意:若使此功能生效,必須啟用地下城秘密路徑點。", "skyblocker.config.dungeons.dungeonScore.scoreScaling": "分數縮放", "skyblocker.config.dungeons.dungeonChestProfit.enableProfitCalculator": "啟用利潤計算器", "skyblocker.config.dungeons.croesusHelper": "Croesus助手", "skyblocker.config.dungeons.puzzle.creeperSolver": "苦力怕光束謎題助手", "skyblocker.config.dungeons.puzzle.solveTrivia": "常識問答謎題助手", - "skyblocker.partyFinder.partyCard.minDungeonLevel": "最低地牢等級要求:%d", + "skyblocker.partyFinder.partyCard.minDungeonLevel": "最低地下城等級要求:%d", "skyblocker.partyFinder.deList": "點擊取消列出", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground": "啟用HUD背景", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground.@Tooltip": "為非TAB的HUD啟用背景。", @@ -581,5 +576,145 @@ "skyblocker.bars.config.showValue": "顯示價值", "skyblocker.bars.config.icon": "圖標", "skyblocker.config.uiAndVisuals.bars.openScreen": "狀態欄設定介面", - "skyblocker.bars.config.explanationTitle": "這是什麽?" + "skyblocker.bars.config.explanationTitle": "這是什麽?", + "skyblocker.config.dungeons.puzzle": "謎題助手", + "skyblocker.config.farming": "農業", + "skyblocker.config.helpers": "助手", + "skyblocker.config.mining.crystalHollows": "水晶洞窟", + "skyblocker.config.mining.dwarvenMines": "矮人礦井", + "skyblocker.config.otherLocations.end.protectorLocationEnable": "啟用保護者位置", + "skyblocker.config.otherLocations.end.zealotKillsEnabled": "顯示Zealot擊殺訊息", + "skyblocker.config.otherLocations.rift.mcGrubberStacks.@Tooltip": "用於計算Motes銷售價格。", + "skyblocker.config.uiAndVisuals.waypoints.waypointType": "路徑點類型", + "skyblocker.config.dungeons.map": "地圖", + "skyblocker.config.general.itemTooltip.enableExoticTooltip": "啟用異域風情提示", + "skyblocker.config.otherLocations.rift.mirrorverseWaypoints": "啟用Mirrorverse路徑點", + "skyblocker.config.uiAndVisuals.chestValue.incompleteColor": "不完整的顔色", + "skyblocker.config.uiAndVisuals.chestValue.incompleteColor.@Tooltip": "當價格數據不完整時顯示的顏色。", + "skyblocker.config.uiAndVisuals.waypoints.waypointType.@Tooltip": "路徑點:高亮顯示並使用信標光束。\n\n邊框路徑點:顯示路徑點和邊框。\n\n高亮:只顯示高亮。\n\n邊框+高亮:顯示高亮與邊框。\n\n邊框:只顯示邊框。", + "skyblocker.config.crimsonIsle.kuudra.arrowPoisonThreshold.@Tooltip": "當背包中箭毒的數量低於設定的值時收到警告。\n\n16是最小值。\n如果需要平滑的DPS階段,應設定為32。", + "skyblocker.config.slayer.vampireSlayer.enableHealingMelonIndicator": "啟用治療西瓜指示器", + "skyblocker.config.slayer.vampireSlayer.holyIceIndicatorTickDelay": "神聖冰塊指示器延遲(以遊戲刻為單位)", + "skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency": "神聖冰塊指示器更新頻率(以遊戲刻為單位)", + "skyblocker.config.uiAndVisuals": "UI&視覺效果", + "skyblocker.config.uiAndVisuals.dontStripSkinAlphaValues": "修正透明外觀像素", + "skyblocker.config.general.itemTooltip.enableExoticTooltip.@Tooltip": "當護甲是異域風情時,在物品名稱下方顯示異域風情的類型。", + "skyblocker.shortcuts.commandSuggestionTooltip": "由於Minecraft的限制,指令建議只有在加入新世界後才會起作用。", + "skyblocker.config.slayer.vampireSlayer.enableHolyIceIndicator": "啟用神聖冰塊指示器", + "skyblocker.config.dungeons.hideSoulweaverSkulls": "隱藏Soulweaver的頭顱", + "skyblocker.config.dungeons.hideSoulweaverSkulls.@Tooltip": "隱藏使用Soulweaver Gloves時產生的頭顱。", + "skyblocker.config.chat.filter": "過濾器", + "skyblocker.config.mining.commissionWaypoints.showEmissary.@Tooltip": "當矮人礦井中的任務完成時,顯示使者的位置(任務更新後生效)。", + "skyblocker.config.mining.commissionWaypoints": "任務路徑點", + "skyblocker.config.mining.commissionWaypoints.mode": "啟用任務路徑點", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[0]": "Off:不顯示任務路徑點。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[1]": "\nDwarven:僅在矮人礦井顯示路徑點。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[2]": "\nGlacite:僅在極寒隧道中顯示路徑點。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[3]": "\nBoth:顯示矮人礦井和極寒隧道的路徑點。", + "skyblocker.config.mining.commissionWaypoints.mode.@Tooltip[4]": "\n(任務更新後生效)", + "skyblocker.config.mining.commissionWaypoints.useColor": "路徑點著色", + "skyblocker.config.mining.commissionWaypoints.useColor.@Tooltip": "為路徑點文字著色以匹配其所在的方塊。", + "skyblocker.config.mining.commissionWaypoints.textScale": "文本縮放", + "skyblocker.config.mining.commissionWaypoints.textScale.@Tooltip": "任務欄的縮放尺寸。", + "skyblocker.config.mining.commissionWaypoints.showBaseCamp": "顯示營地路徑點", + "skyblocker.config.mining.commissionWaypoints.showBaseCamp.@Tooltip": "在極寒隧道中顯示營地的路徑點(任務更新後生效)。", + "skyblocker.config.mining.commissionWaypoints.showEmissary": "顯示使者", + "skyblocker.config.dungeons.terminals.blockIncorrectClicks": "阻止錯誤的點擊", + "skyblocker.config.uiAndVisuals.dontStripSkinAlphaValues.@Tooltip": "啟用後,在Skyblock中,外觀紋理中像素的Alpha值不再被剝離。\n\n這導致使用玩家頭的物品上的「填充」像素現在完全透明,儘管這在奇怪的情況下可能會產生一些副作用。", + "skyblocker.config.uiAndVisuals.inputCalculator.enabled": "啟用告示牌計算器", + "skyblocker.config.uiAndVisuals.inputCalculator.enabled.@Tooltip": "使你可以在輸入諸如拍賣價格之類的值時進行計算。\n 單位:\n S = 64\n E = 160\n K = 1,000\n M = 1,000,000\n B = 1,000,000,000\n\npurse/P = 目前錢包中的硬幣數", + "skyblocker.config.uiAndVisuals.inputCalculator": "輸入計算器", + "skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals": "僅當「=」時顯示。", + "skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals.@Tooltip": "僅當訊息以「=」開頭時才顯示計算器。", + "skyblocker.config.uiAndVisuals.inputCalculator.invalidEquation": "無效方程", + "skyblocker.config.uiAndVisuals.compactDamage": "簡潔的傷害數值", + "skyblocker.config.uiAndVisuals.compactDamage.enabled": "已啟用", + "skyblocker.config.uiAndVisuals.compactDamage.precision": "精確度", + "skyblocker.config.uiAndVisuals.compactDamage.precision.@Tooltip": "小數點後的位數。", + "skyblocker.config.uiAndVisuals.compactDamage.normalDamageColor": "常規傷害顏色", + "skyblocker.config.uiAndVisuals.compactDamage.critDamageGradientStart": "暴擊傷害漸層顏色的起始色", + "skyblocker.config.uiAndVisuals.compactDamage.critDamageGradientEnd": "暴擊傷害漸層顏色的結束色", + "skyblocker.config.uiAndVisuals.waypoints.waypointType.generalNote": "\n\n\n此選項並不適用於所有路徑點。 某些路徑點(例如地下城秘密路徑點)有自己的路徑點類型選項。", + "skyblocker.config.foraging": "覓食", + "skyblocker.config.foraging.hunting": "狩獵", + "skyblocker.config.helpers.chocolateFactory.enableChocolateFactoryHelper": "啟用巧克力工廠助手", + "skyblocker.config.helpers.chocolateFactory.enableChocolateFactoryHelper.@Tooltip": "啟用時,高亮顯示最合適的升級。\n\n最合適的升級將以綠色顯示,但如果你沒有足夠的巧克力,則將以黃色顯示,並將你能購買的最佳升級標記為綠色。", + "skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages": "發送彩蛋已找到的訊息", + "skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages.@Tooltip": "當在目前島嶼中發現彩蛋時在聊天欄中發送提示訊息。", + "skyblocker.config.helpers.chocolateFactory.waypointType.@Tooltip": "路徑點:高亮顯示以及信標光束。\n\n邊框+路徑點:顯示路徑點和邊框。\n\n高亮:只顯示高亮。\n\n邊框+高亮:顯示高亮與邊框。\n\n邊框:只顯示邊框。", + "skyblocker.config.helpers.chocolateFactory.enableEggFinder": "啟用尋蛋助手", + "skyblocker.config.helpers.chocolateFactory.enableEggFinder.@Tooltip": "高亮顯示Hoppity的尋蛋活動期間的彩蛋。", + "skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder": "啟用時間塔提醒", + "skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder.@Tooltip": "當時間塔停止運作時,在聊天欄發送提示訊息。", + "skyblocker.config.helpers.chocolateFactory": "巧克力工廠", + "skyblocker.config.helpers.chocolateFactory.waypointType": "彩蛋路徑點類型", + "skyblocker.config.mining.glacite": "極寒隧道", + "skyblocker.config.mining.glacite.coldOverlay": "寒冷效果動畫修改", + "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "顯示極寒隧道中的霜凍效果,隨著溫度的降低,霜凍效果會變得更強。", + "skyblocker.config.misc": "雜項", + "skyblocker.config.general.searchOverlay.starsTooltip": "地下城物品的星星數量", + "skyblocker.config.general.searchOverlay.maxPet": "最高寵物等級", + "skyblocker.config.general.searchOverlay.maxPet.@Tooltip": "只顯示最高等級的寵物", + "skyblocker.config.eventNotifications": "活動通知", + "skyblocker.config.eventNotifications.notificationSound": "通知聲音", + "skyblocker.config.eventNotifications.@Tooltip[0]": "設定在活動開始前多久你將收到通知!例如,如果在清單中設定“5m”和“30s”,你將分別在活動開始前5分鐘和30秒前收到通知。", + "skyblocker.config.eventNotifications.@Tooltip[1]": "順序並不重要。如果想停用通知,清空清單即可。", + "skyblocker.config.eventNotifications.@Tooltip[2]": "該列表將修改「%s」活動", + "skyblocker.dungeons.secrets.notMatched": "§當前房間不匹配! (你在地下城房間中嗎?)", + "skyblocker.config.crimsonIsle": "緋紅島", + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.pling": "音符盒-電鋼琴", + "skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency": "Effigy路徑點更新頻率(以遊戲刻為單位)", + "skyblocker.config.slayer.vampireSlayer.enableSteakStakeIndicator": "啟用Steak Stake指示器", + "skyblocker.config.slayer.vampireSlayer.healingMelonHealthThreshold": "療傷西瓜指示器閾值(單位:心)", + "skyblocker.config.quickNav.button.clickEvent": "點擊事件", + "skyblocker.config.slayer.vampireSlayer.steakStakeUpdateFrequency": "Steak Stake指示器更新頻率(以遊戲刻為單位)", + "skyblocker.dungeons.secrets.noTarget": "§c沒有發現目標方塊! (目標方塊是否在範圍內?)", + "skyblocker.events.startsNow": "%s開始了!", + "skyblocker.events.startsSoon": "%s即將開始!", + "skyblocker.events.tab.endsIn": "%s後結束", + "skyblocker.events.tab.clickToWarp": "點擊傳送!", + "skyblocker.events.tab.startsIn": "%s後開始", + "skyblocker.customAnimatedDyes.neverHad": "該物品還沒有自訂動態染色,為什麼不設定一個呢? :)", + "skyblocker.config.quickNav.button.item.components.@Tooltip": "用方括號括起來的項目組件字串,其格式與/give指令相同。\n\n例如:[minecraft:enchantment_glint_override=true]", + "skyblocker.config.quickNav.button.item.components": "物品元件", + "skyblocker.customAnimatedDyes.noItemUuid": "§c你必須手持一個有UUID的物品才能設定自訂動態染色。", + "skyblocker.config.quickNav.button.render": "啟用", + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.anvil": "鐵砧", + "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.arrowHit": "箭:擊中玩家", + "skyblocker.customAnimatedDyes.removed": "移除了該物品的自訂動態染色。", + "skyblocker.customAnimatedDyes.added": "成功為你目前所持物品設定了自訂動態染色!", + "skyblocker.config.slayer.vampireSlayer.enableEffigyWaypoints": "啟用Effigy路徑點", + "skyblocker.config.slayer.vampireSlayer.maniaUpdateFrequency": "Mania指示器更新頻率(以遊戲刻為單位)", + "skyblocker.config.general.itemTooltip.dungeonQuality.@Tooltip": "顯示地下城怪物掉落物的品質和等級。\n\n\n提示:\nT1-3來自F1-F3\nT4-7來自F4-F7或M1-M4\nT8-10來自M5-M7", + "skyblocker.config.general.itemInfoDisplay.slotText": "物品欄文本", + "skyblocker.config.general.itemInfoDisplay.slotText.@Tooltip": "顯示附魔書等級、Minion等級、寵物等級、藥水等級、史前蛋的行走步數、牧場主靴子速度上限和技能等級等資訊", + "skyblocker.events.tab.noMore": "今年沒有更多活動了!", + "skyblocker.waypoints.config": "路徑點設置", + "skyblocker.waypoints.newCategory": "新路徑點分類", + "skyblocker.waypoints.new": "新路徑點", + "skyblocker.waypoints.share": "分享", + "skyblocker.waypoints.shareWaypoints": "分享路徑點", + "skyblocker.waypoints.importWaypointsSkytils.tooltip": "從剪貼簿匯入路徑點(Skytils格式)", + "skyblocker.waypoints.importWaypointsSkytils": "匯入路徑點(Skytils)", + "skyblocker.waypoints.importWaypointsSnoopy": "匯入路徑點(Snoopy)", + "skyblocker.waypoints.importWaypointsSnoopy.tooltip": "從剪貼簿匯入路徑點(Snoopy格式)(即將到來)", + "skyblocker.waypoints.exportWaypointsSkytils": "匯出路徑點(Skytils)", + "skyblocker.waypoints.exportWaypointsSkytils.tooltip": "將路徑點匯出至剪貼簿(Skytils格式)", + "skyblocker.waypoints.importSuccess": "路徑點已匯入", + "skyblocker.waypoints.importSuccessText": "成功從%d個分類匯入%d個路徑點。", + "skyblocker.waypoints.importError": "匯入路徑點失敗", + "skyblocker.waypoints.importErrorText": "匯入路徑點失敗。詳細資訊請參閱日誌。", + "skyblocker.waypoints.exportSuccess": "路徑點已匯出", + "skyblocker.waypoints.exportSuccessText": "成功從%d個分類匯出%d個路徑點。", + "skyblocker.waypoints.exportError": "匯出路徑點失敗", + "skyblocker.waypoints.exportErrorText": "匯出路徑點失敗。詳細資訊請參閱日誌。", + "skyblocker.waypoints.deleteQuestion": "你確定要刪除該路徑點嗎?", + "skyblocker.waypoints.deleteWarning": "路徑點「%s」將永遠消失! (真的很久!)", + "skyblocker.customAnimatedDyes.unableToSetDye": "§c無法設定自訂動態染料 :( (請檢查自己是否在Skyblock中,以及是否持有物品)", + "skyblocker.config.slayer.vampireSlayer.enableManiaIndicator": "啟用Mania Block指示器", + "skyblocker.tips.customAnimatedDyes": "你可以使用 /skyblocker custom animatedDye 將自訂動態染料應用到你的皮革護甲上!", + "skyblocker.tips.customDungeonSecretWaypoints": "你可以使用 /skyblocker dungeons secrets addWaypoint 將自訂秘密路徑點新增到任何地下城房間。", + "skyblocker.waypoints.ordered.export.success": "成功將路徑點複製到剪貼簿!", + "skyblocker.shortcuts.commandArg.tooltip": "替換具有多個單字/參數的指令的一個或多個單字/參數,而不是匹配整個指令。", + "skyblocker.waypoints.ordered.export.fail": "§c路徑點匯出失敗,請檢查latest.log以取得更多資訊。" } diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/base_plate.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/base_plate.png Binary files differnew file mode 100644 index 00000000..0edf5705 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/base_plate.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/blaze.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/blaze.png Binary files differnew file mode 100644 index 00000000..ed168ddf --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/blaze.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon.png Binary files differnew file mode 100644 index 00000000..d29969be --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_highlighted.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_highlighted.png Binary files differnew file mode 100644 index 00000000..11340a55 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_highlighted.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled.png Binary files differnew file mode 100644 index 00000000..109cd859 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled_highlighted.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled_highlighted.png Binary files differnew file mode 100644 index 00000000..581cdefe --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled_highlighted.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_body.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_body.png Binary files differnew file mode 100644 index 00000000..379557e4 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_body.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_header.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_header.png Binary files differnew file mode 100644 index 00000000..384dc0e3 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_header.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/enderman.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/enderman.png Binary files differnew file mode 100644 index 00000000..84650c7f --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/enderman.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/icon_data_widget.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/icon_data_widget.png Binary files differnew file mode 100644 index 00000000..c28aeed4 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/icon_data_widget.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/run_icon.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/run_icon.png Binary files differnew file mode 100644 index 00000000..d83fad70 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/run_icon.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/spider.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/spider.png Binary files differnew file mode 100644 index 00000000..a5daa3d6 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/spider.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/vampire.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/vampire.png Binary files differnew file mode 100644 index 00000000..efbe985d --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/vampire.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/wolf.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/wolf.png Binary files differnew file mode 100644 index 00000000..f7bec9a4 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/wolf.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/zombie.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/zombie.png Binary files differnew file mode 100644 index 00000000..37ea069f --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/zombie.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/notification.png b/src/main/resources/assets/skyblocker/textures/gui/sprites/notification.png Binary files differnew file mode 100644 index 00000000..8f272cd7 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/sprites/notification.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/notification.png.mcmeta b/src/main/resources/assets/skyblocker/textures/gui/sprites/notification.png.mcmeta new file mode 100644 index 00000000..c514d54c --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/sprites/notification.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 160, + "height": 32, + "border": 4 + } + } +}
\ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 645eda34..e46085de 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -5,7 +5,7 @@ "name": "Skyblocker", "description": "Hypixel Skyblock Mod", "authors": ["LifeIsAParadox/wohlhabend", "kevinthegreat1", "AzureAaron"], - "contributors": ["xMrVizzy", "d3dx9", "ExternalTime", "Zailer43", "TacoMonkey", "KonaeAkira", "Fix3dll", "null2264", "HyperSoop", "edgarogh", "TheColdPot", "Julienraptor01", "ADON15c", "catandA", "msg-programs", "lantice3720", "Futuremappermydud", "koloiyolo", "viciscat", "Grayray75", "alexiayaa", "KhafraDev", "btwonion", "Kaluub", "Emirlol", "LegendaryLilac", "olim88", "Ghost-3", "esteban4567890", "Fluboxer", "VeritasDL", "TheDearbear"], + "contributors": ["xMrVizzy", "d3dx9", "ExternalTime", "Zailer43", "TacoMonkey", "KonaeAkira", "Fix3dll", "null2264", "HyperSoop", "edgarogh", "TheColdPot", "Julienraptor01", "ADON15c", "catandA", "msg-programs", "lantice3720", "Futuremappermydud", "koloiyolo", "viciscat", "Grayray75", "alexiayaa", "KhafraDev", "btwonion", "Kaluub", "Emirlol", "LegendaryLilac", "olim88", "Ghost-3", "esteban4567890", "Fluboxer", "VeritasDL", "TheDearbear", "BigloBot", "f3shqt", "UpFault"], "contact": { "homepage": "https://hysky.de", "sources": "https://github.com/SkyblockerMod/Skyblocker", @@ -33,14 +33,14 @@ ], "accessWidener": "skyblocker.accesswidener", "depends": { - "fabricloader": ">=0.15.10", - "fabric-api": ">=0.97.8+1.20.6", - "yet_another_config_lib_v3": ">=3.4.1+1.20.5", - "minecraft": "~1.20.5", + "fabricloader": ">=0.15.11", + "fabric-api": ">=0.100.1+1.21", + "yet_another_config_lib_v3": ">=3.5.0+1.21", + "minecraft": "~1.21", "java": ">=21" }, "conflicts": { - "immediatelyfast": "<=1.2.12+1.20.5" + "immediatelyfast": "<=1.2.18+1.21" }, "breaks": { "forcecloseworldloadingscreen": "<=2.2.0" @@ -52,6 +52,11 @@ "modmenu.modrinth": "https://modrinth.com/mod/skyblocker-liap", "text.skyblocker.translate": "https://translate.hysky.de" } + }, + "loom:injected_interfaces": { + "net/minecraft/class_1799": [ + "de/hysky/skyblocker/injected/SkyblockerStack" + ] } } } diff --git a/src/main/resources/skyblocker.accesswidener b/src/main/resources/skyblocker.accesswidener index 2464d6d5..9fcdfa45 100644 --- a/src/main/resources/skyblocker.accesswidener +++ b/src/main/resources/skyblocker.accesswidener @@ -1 +1,20 @@ -accessWidener v2 named
\ No newline at end of file +accessWidener v2 named + +accessible class net/minecraft/client/render/RenderLayer$MultiPhase +accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters +accessible class net/minecraft/client/render/RenderPhase$Transparency +accessible class net/minecraft/client/render/RenderPhase$ShaderProgram +accessible class net/minecraft/client/render/RenderPhase$Texture +accessible class net/minecraft/client/render/RenderPhase$TextureBase +accessible class net/minecraft/client/render/RenderPhase$Texturing +accessible class net/minecraft/client/render/RenderPhase$Lightmap +accessible class net/minecraft/client/render/RenderPhase$Overlay +accessible class net/minecraft/client/render/RenderPhase$Cull +accessible class net/minecraft/client/render/RenderPhase$DepthTest +accessible class net/minecraft/client/render/RenderPhase$WriteMaskState +accessible class net/minecraft/client/render/RenderPhase$Layering +accessible class net/minecraft/client/render/RenderPhase$Target +accessible class net/minecraft/client/render/RenderPhase$LineWidth +accessible class net/minecraft/client/render/RenderPhase$ColorLogic +accessible class net/minecraft/client/render/RenderPhase$OffsetTexturing +accessible class net/minecraft/client/render/RenderPhase$Textures diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index 828cc206..27755550 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -39,11 +39,13 @@ "YggdrasilMinecraftSessionServiceMixin", "YggdrasilServicesKeyInfoMixin", "accessors.BeaconBlockEntityRendererInvoker", + "accessors.CheckboxWidgetAccessor", "accessors.DrawContextInvoker", "accessors.EndermanEntityAccessor", "accessors.FrustumInvoker", "accessors.HandledScreenAccessor", "accessors.MessageHandlerAccessor", + "accessors.MinecraftClientAccessor", "accessors.PlayerListHudAccessor", "accessors.RecipeBookWidgetAccessor", "accessors.ScreenAccessor", |