diff options
Diffstat (limited to 'src/main/java/me/xmrvizzy')
54 files changed, 1326 insertions, 291 deletions
diff --git a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java index 5c7f2a99..b28ad3d4 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java +++ b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java @@ -4,10 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.skyblock.*; -import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonBlaze; -import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonMap; -import me.xmrvizzy.skyblocker.skyblock.dungeon.LividColor; -import me.xmrvizzy.skyblocker.skyblock.dungeon.TicTacToe; +import me.xmrvizzy.skyblocker.skyblock.dungeon.*; import me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonSecrets; import me.xmrvizzy.skyblocker.skyblock.dwarven.DwarvenHud; import me.xmrvizzy.skyblocker.skyblock.item.*; @@ -15,6 +12,8 @@ import me.xmrvizzy.skyblocker.skyblock.itemlist.ItemRegistry; import me.xmrvizzy.skyblocker.skyblock.quicknav.QuickNav; import me.xmrvizzy.skyblocker.skyblock.rift.TheRift; import me.xmrvizzy.skyblocker.skyblock.shortcut.Shortcuts; +import me.xmrvizzy.skyblocker.skyblock.special.SpecialEffects; +import me.xmrvizzy.skyblocker.skyblock.spidersden.Relics; import me.xmrvizzy.skyblocker.skyblock.tabhud.TabHud; import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster; import me.xmrvizzy.skyblocker.skyblock.tabhud.util.PlayerListMgr; @@ -40,14 +39,11 @@ import java.nio.file.Path; * this class. */ public class SkyblockerMod implements ClientModInitializer { + public static final String VERSION = FabricLoader.getInstance().getModContainer("skyblocker").get().getMetadata().getVersion().getFriendlyString(); public static final String NAMESPACE = "skyblocker"; public static final Path CONFIG_DIR = FabricLoader.getInstance().getConfigDir().resolve(NAMESPACE); public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static SkyblockerMod INSTANCE; - - @SuppressWarnings("deprecation") - public final Scheduler scheduler = new Scheduler(); - public final MessageScheduler messageScheduler = new MessageScheduler(); public final ContainerSolverManager containerSolverManager = new ContainerSolverManager(); public final StatusBarTracker statusBarTracker = new StatusBarTracker(); @@ -79,6 +75,7 @@ public class SkyblockerMod implements ClientModInitializer { ItemRegistry.init(); NEURepo.init(); FairySouls.init(); + Relics.init(); BackpackPreview.init(); QuickNav.init(); DwarvenHud.init(); @@ -91,6 +88,7 @@ public class SkyblockerMod implements ClientModInitializer { DungeonMap.init(); DungeonSecrets.init(); DungeonBlaze.init(); + DungeonChestProfit.init(); TheRift.init(); TitleContainer.init(); ScreenMaster.init(); @@ -101,15 +99,16 @@ public class SkyblockerMod implements ClientModInitializer { CustomArmorTrims.init(); TicTacToe.init(); QuiverWarning.init(); + SpecialEffects.init(); containerSolverManager.init(); statusBarTracker.init(); - scheduler.scheduleCyclic(Utils::update, 20); - scheduler.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 100); - scheduler.scheduleCyclic(TicTacToe::tick, 4); - scheduler.scheduleCyclic(LividColor::update, 10); - scheduler.scheduleCyclic(BackpackPreview::tick, 50); - scheduler.scheduleCyclic(DwarvenHud::update, 40); - scheduler.scheduleCyclic(PlayerListMgr::updateList, 20); + Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20); + Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 100); + Scheduler.INSTANCE.scheduleCyclic(TicTacToe::tick, 4); + Scheduler.INSTANCE.scheduleCyclic(LividColor::update, 10); + Scheduler.INSTANCE.scheduleCyclic(BackpackPreview::tick, 50); + Scheduler.INSTANCE.scheduleCyclic(DwarvenHud::update, 40); + Scheduler.INSTANCE.scheduleCyclic(PlayerListMgr::updateList, 20); } /** @@ -119,7 +118,7 @@ public class SkyblockerMod implements ClientModInitializer { * @param client the Minecraft client. */ public void tick(MinecraftClient client) { - scheduler.tick(); - messageScheduler.tick(); + Scheduler.INSTANCE.tick(); + MessageScheduler.INSTANCE.tick(); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/compatibility/MixinPlugin.java b/src/main/java/me/xmrvizzy/skyblocker/compatibility/MixinPlugin.java index c6fc1924..8b96499d 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/compatibility/MixinPlugin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/compatibility/MixinPlugin.java @@ -26,7 +26,7 @@ public class MixinPlugin implements IMixinConfigPlugin { public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { //OptiFabric Compatibility if (mixinClassName.endsWith("WorldRendererMixin") && OPTIFABRIC_LOADED) return false; - + return true; } @@ -41,7 +41,7 @@ public class MixinPlugin implements IMixinConfigPlugin { } @Override - public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { //Do nothing } diff --git a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java index 127bc601..5848ed15 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java @@ -20,7 +20,9 @@ import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.resource.language.I18n; import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import org.eclipse.jgit.util.StringUtils; import java.util.ArrayList; import java.util.List; @@ -67,11 +69,19 @@ public class SkyblockerConfig implements ConfigData { @ConfigEntry.Category("button3") @ConfigEntry.Gui.CollapsibleObject() - public QuickNavItem button3 = new QuickNavItem(true, new ItemData("bone"), "\\(\\d+/\\d+\\) Pets", "/pets"); + public QuickNavItem button3 = new QuickNavItem(true, new ItemData("bone"), "Pets(:? \\(\\d+\\/\\d+\\))?", "/pets"); + /* REGEX Explanation + * "Pets" : simple match on letters + * "(?: \\(\\d+\\/\\d+\\))?" : optional match on the non-capturing group for the page in the format " ($number/$number)" + */ @ConfigEntry.Category("button4") @ConfigEntry.Gui.CollapsibleObject() - public QuickNavItem button4 = new QuickNavItem(true, new ItemData("leather_chestplate", 1, "tag:{display:{color:8991416}}"), "Wardrobe \\([12]/2\\)", "/wardrobe"); + public QuickNavItem button4 = new QuickNavItem(true, new ItemData("leather_chestplate", 1, "tag:{display:{color:8991416}}"), "Wardrobe \\([12]\\/2\\)", "/wardrobe"); + /* REGEX Explanation + * "Wardrobe" : simple match on letters + * " \\([12]\\/2\\)" : match on the page either " (1/2)" or " (2/2)" + */ @ConfigEntry.Category("button5") @ConfigEntry.Gui.CollapsibleObject() @@ -79,7 +89,12 @@ public class SkyblockerConfig implements ConfigData { @ConfigEntry.Category("button6") @ConfigEntry.Gui.CollapsibleObject() - public QuickNavItem button6 = new QuickNavItem(true, new ItemData("ender_chest"), "(?:Rift )?Storage(?: \\(1/2\\))?", "/storage"); + public QuickNavItem button6 = new QuickNavItem(true, new ItemData("ender_chest"), "(?:Rift )?Storage(?: \\([12]\\/2\\))?", "/storage"); + /* REGEX Explanation + * "(?:Rift )?" : optional match on the non-capturing group "Rift " + * "Storage" : simple match on letters + * "(?: \\([12]\\/2\\))?" : optional match on the non-capturing group " (1/2)" or " (2/2)" + */ @ConfigEntry.Category("button7") @ConfigEntry.Gui.CollapsibleObject() @@ -145,7 +160,9 @@ public class SkyblockerConfig implements ConfigData { public static class General { public boolean acceptReparty = true; public boolean backpackPreviewWithoutShift = false; + public boolean compactorDeletorPreview = true; public boolean hideEmptyTooltips = true; + public boolean hideStatusEffectOverlay = false; @ConfigEntry.Category("tabHud") @ConfigEntry.Gui.CollapsibleObject() @@ -185,11 +202,15 @@ public class SkyblockerConfig implements ConfigData { @ConfigEntry.Category("itemTooltip") @ConfigEntry.Gui.CollapsibleObject() public ItemTooltip itemTooltip = new ItemTooltip(); - + @ConfigEntry.Category("itemInfoDisplay") @ConfigEntry.Gui.CollapsibleObject public ItemInfoDisplay itemInfoDisplay = new ItemInfoDisplay(); + @ConfigEntry.Category("specialEffects") + @ConfigEntry.Gui.CollapsibleObject + public SpecialEffects specialEffects = new SpecialEffects(); + @ConfigEntry.Category("hitbox") @ConfigEntry.Gui.CollapsibleObject() public Hitbox hitbox = new Hitbox(); @@ -259,7 +280,6 @@ public class SkyblockerConfig implements ConfigData { public BarPosition defenceBarPosition = BarPosition.LAYER1; @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public BarPosition experienceBarPosition = BarPosition.LAYER1; - } public enum BarPosition { @@ -295,6 +315,9 @@ public class SkyblockerConfig implements ConfigData { public static class FairySouls { public boolean enableFairySoulsHelper = false; + public boolean highlightFoundSouls = true; + @ConfigEntry.Gui.Tooltip() + public boolean highlightOnlyNearbySouls = false; } public static class Shortcuts { @@ -401,12 +424,17 @@ public class SkyblockerConfig implements ConfigData { public boolean enableBazaarPrice = true; public boolean enableMuseumDate = true; } - + public static class ItemInfoDisplay { - @ConfigEntry.Gui.Tooltip + @ConfigEntry.Gui.Tooltip public boolean attributeShardInfo = true; } + public static class SpecialEffects { + @ConfigEntry.Gui.Tooltip + public boolean rareDungeonDropEffects = true; + } + public static class Locations { @ConfigEntry.Category("barn") @ConfigEntry.Gui.CollapsibleObject() @@ -423,11 +451,17 @@ public class SkyblockerConfig implements ConfigData { @ConfigEntry.Category("rift") @ConfigEntry.Gui.CollapsibleObject() public Rift rift = new Rift(); + + @ConfigEntry.Category("spidersden") + @ConfigEntry.Gui.CollapsibleObject() + public SpidersDen spidersDen = new SpidersDen(); } public static class Dungeons { @ConfigEntry.Gui.CollapsibleObject public SecretWaypoints secretWaypoints = new SecretWaypoints(); + @ConfigEntry.Gui.CollapsibleObject + public DungeonChestProfit dungeonChestProfit = new DungeonChestProfit(); @ConfigEntry.Gui.Tooltip() public boolean croesusHelper = true; public boolean enableMap = true; @@ -466,6 +500,61 @@ public class SkyblockerConfig implements ConfigData { public boolean enableDefaultWaypoints = true; } + public static class DungeonChestProfit { + @ConfigEntry.Gui.Tooltip + public boolean enableProfitCalculator = true; + @ConfigEntry.Gui.Tooltip + public boolean includeKismet = false; + @ConfigEntry.Gui.Tooltip + public boolean includeEssence = true; + @ConfigEntry.Gui.Tooltip + public int neutralThreshold = 1000; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.DROPDOWN) + public FormattingOption neutralColor = FormattingOption.DARK_GRAY; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.DROPDOWN) + public FormattingOption profitColor = FormattingOption.DARK_GREEN; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.DROPDOWN) + public FormattingOption lossColor = FormattingOption.RED; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.DROPDOWN) + @ConfigEntry.Gui.Tooltip + public FormattingOption incompleteColor = FormattingOption.BLUE; + } + + public enum FormattingOption { + BLACK(Formatting.BLACK), + DARK_BLUE(Formatting.DARK_BLUE), + DARK_GREEN(Formatting.DARK_GREEN), + DARK_AQUA(Formatting.DARK_AQUA), + DARK_RED(Formatting.DARK_RED), + DARK_PURPLE(Formatting.DARK_PURPLE), + GOLD(Formatting.GOLD), + GRAY(Formatting.GRAY), + DARK_GRAY(Formatting.DARK_GRAY), + BLUE(Formatting.BLUE), + GREEN(Formatting.GREEN), + AQUA(Formatting.AQUA), + RED(Formatting.RED), + LIGHT_PURPLE(Formatting.LIGHT_PURPLE), + YELLOW(Formatting.YELLOW), + WHITE(Formatting.WHITE), + OBFUSCATED(Formatting.OBFUSCATED), + BOLD(Formatting.BOLD), + STRIKETHROUGH(Formatting.STRIKETHROUGH), + UNDERLINE(Formatting.UNDERLINE), + ITALIC(Formatting.ITALIC), + RESET(Formatting.RESET); + public final Formatting formatting; + + FormattingOption(Formatting formatting) { + this.formatting = formatting; + } + + @Override + public String toString() { + return StringUtils.capitalize(formatting.getName().replaceAll("_", " ")); + } + } + public static class LividColor { @ConfigEntry.Gui.Tooltip() public boolean enableLividColor = true; @@ -524,6 +613,17 @@ public class SkyblockerConfig implements ConfigData { public int mcGrubberStacks = 0; } + public static class SpidersDen { + @ConfigEntry.Category("relics") + @ConfigEntry.Gui.CollapsibleObject() + public Relics relics = new Relics(); + } + + public static class Relics { + public boolean enableRelicsHelper = false; + public boolean highlightFoundRelics = true; + } + public static class Slayer { @ConfigEntry.Category("vampire") @ConfigEntry.Gui.CollapsibleObject() diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/AbstractInventoryScreenMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/AbstractInventoryScreenMixin.java new file mode 100644 index 00000000..db517411 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/AbstractInventoryScreenMixin.java @@ -0,0 +1,19 @@ +package me.xmrvizzy.skyblocker.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Utils; +import net.minecraft.client.gui.screen.ingame.AbstractInventoryScreen; + +@Mixin(AbstractInventoryScreen.class) +public class AbstractInventoryScreenMixin { + + @Inject(method = "drawStatusEffects", at = @At("HEAD"), cancellable = true) + private void skyblocker$dontDrawStatusEffects(CallbackInfo ci) { + if (Utils.isOnSkyblock() && SkyblockerConfig.get().general.hideStatusEffectOverlay) ci.cancel(); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/BatEntityMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/BatEntityMixin.java index 86c4e672..3eb13073 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/BatEntityMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/BatEntityMixin.java @@ -6,7 +6,6 @@ import net.minecraft.entity.mob.AmbientEntity; import net.minecraft.entity.passive.BatEntity; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(BatEntity.class) public abstract class BatEntityMixin extends AmbientEntity { diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java index 0a1084cf..cfe979d0 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java @@ -36,7 +36,7 @@ public abstract class DrawContextMixin { @Shadow public abstract void fill(RenderLayer layer, int x1, int x2, int y1, int y2, int color); - + @Shadow public abstract int drawText(TextRenderer textRenderer, @Nullable String text, int x, int y, int color, boolean shadow); @@ -96,35 +96,35 @@ public abstract class DrawContextMixin { matrices.pop(); RenderSystem.enableDepthTest(); } - + @Inject(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At("HEAD")) private void skyblocker$renderAttributeShardDisplay(@Arg TextRenderer textRenderer, @Arg ItemStack stack, @Arg(ordinal = 0) int x, @Arg(ordinal = 1) int y, @Local(argsOnly = true) LocalRef<String> countOverride) { if (!SkyblockerConfig.get().general.itemInfoDisplay.attributeShardInfo) return; - + NbtCompound nbt = stack.getNbt(); - + if (Utils.isOnSkyblock() && nbt != null && nbt.contains("ExtraAttributes")) { NbtCompound extraAttributes = nbt.getCompound("ExtraAttributes"); - + if (extraAttributes.getString("id").equals("ATTRIBUTE_SHARD")) { NbtCompound attributesTag = extraAttributes.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(); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java index af6f6aa7..33532788 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java @@ -2,12 +2,14 @@ package me.xmrvizzy.skyblocker.mixin; import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; -import me.xmrvizzy.skyblocker.skyblock.BackpackPreview; import me.xmrvizzy.skyblocker.skyblock.experiment.ChronomatronSolver; import me.xmrvizzy.skyblocker.skyblock.experiment.ExperimentSolver; import me.xmrvizzy.skyblocker.skyblock.experiment.SuperpairsSolver; import me.xmrvizzy.skyblocker.skyblock.experiment.UltrasequencerSolver; +import me.xmrvizzy.skyblocker.skyblock.item.BackpackPreview; +import me.xmrvizzy.skyblocker.skyblock.item.CompactorDeletorPreview; import me.xmrvizzy.skyblocker.skyblock.item.WikiLookup; +import me.xmrvizzy.skyblocker.skyblock.itemlist.ItemRegistry; import me.xmrvizzy.skyblocker.utils.Utils; import me.xmrvizzy.skyblocker.utils.render.gui.ContainerSolver; import net.minecraft.client.gui.DrawContext; @@ -31,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.Map; +import java.util.regex.Matcher; @Mixin(HandledScreen.class) public abstract class HandledScreenMixin extends Screen { @@ -49,18 +52,31 @@ public abstract class HandledScreenMixin extends Screen { } } - @Inject(at = @At("HEAD"), method = "drawMouseoverTooltip", cancellable = true) + @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) { + if (!Utils.isOnSkyblock()) return; + // Hide Empty Tooltips - if (Utils.isOnSkyblock() && SkyblockerConfig.get().general.hideEmptyTooltips && this.focusedSlot != null && focusedSlot.getStack().getName().getString().equals(" ")) { + if (SkyblockerConfig.get().general.hideEmptyTooltips && focusedSlot.getStack().getName().getString().equals(" ")) { ci.cancel(); } // Backpack Preview boolean shiftDown = SkyblockerConfig.get().general.backpackPreviewWithoutShift ^ Screen.hasShiftDown(); - if (this.client != null && this.client.player != null && this.focusedSlot != null && shiftDown && this.getTitle().getString().equals("Storage") && this.focusedSlot.inventory != this.client.player.getInventory() && BackpackPreview.renderPreview(context, this.focusedSlot.getIndex(), x, y)) { + if (shiftDown && getTitle().getString().equals("Storage") && focusedSlot.inventory != client.player.getInventory() && BackpackPreview.renderPreview(context, focusedSlot.getIndex(), x, y)) { ci.cancel(); } + + // Compactor Preview + if (SkyblockerConfig.get().general.compactorDeletorPreview) { + ItemStack stack = focusedSlot.getStack(); + Matcher matcher = CompactorDeletorPreview.NAME.matcher(ItemRegistry.getInternalName(stack)); + if (matcher.matches() && CompactorDeletorPreview.drawPreview(context, stack, matcher.group("type"), matcher.group("size"), x, y)) { + ci.cancel(); + } + } } @Redirect(method = "drawMouseoverTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/screen/slot/Slot;getStack()Lnet/minecraft/item/ItemStack;", ordinal = 0)) @@ -73,6 +89,7 @@ public abstract class HandledScreenMixin extends Screen { return skyblocker$experimentSolvers$getStack(slot, stack); } + @Unique private ItemStack skyblocker$experimentSolvers$getStack(Slot slot, ItemStack stack) { ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver(); diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/InGameHudMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/InGameHudMixin.java index bc3df266..752b102a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/InGameHudMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/InGameHudMixin.java @@ -61,4 +61,9 @@ public abstract class InGameHudMixin { if (Utils.isOnSkyblock() && SkyblockerConfig.get().general.bars.enableBars && !Utils.isInTheRift()) ci.cancel(); } + + @Inject(method = "renderStatusEffectOverlay", at = @At("HEAD"), cancellable = true) + private void skyblocker$dontRenderStatusEffects(CallbackInfo ci) { + if (Utils.isOnSkyblock() && SkyblockerConfig.get().general.hideStatusEffectOverlay) ci.cancel(); + } }
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/SocialInteractionsPlayerListWidgetMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/SocialInteractionsPlayerListWidgetMixin.java new file mode 100644 index 00000000..854c4e17 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/SocialInteractionsPlayerListWidgetMixin.java @@ -0,0 +1,24 @@ +package me.xmrvizzy.skyblocker.mixin; + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + +import me.xmrvizzy.skyblocker.utils.Utils; +import net.minecraft.client.gui.screen.multiplayer.SocialInteractionsPlayerListEntry; +import net.minecraft.client.gui.screen.multiplayer.SocialInteractionsPlayerListWidget; + +@Mixin(SocialInteractionsPlayerListWidget.class) +public class SocialInteractionsPlayerListWidgetMixin { + + @WrapOperation(method = "setPlayers", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", remap = false)) + private Object skyblocker$hideInvalidPlayers(Map<Object, Object> map, Object uuid, Object entry, Operation<Object> operation) { + if (Utils.isOnSkyblock() && !((SocialInteractionsPlayerListEntry) entry).getName().matches("[A-Za-z0-9_]+")) return null; + + return operation.call(map, uuid, entry); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java index 3b796c38..53dd57a7 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java @@ -16,16 +16,16 @@ import net.minecraft.entity.Entity; @Mixin(WorldRenderer.class) public class WorldRendererMixin { - + @ModifyExpressionValue(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;hasOutline(Lnet/minecraft/entity/Entity;)Z")) private boolean skyblocker$shouldStarredMobGlow(boolean original, @Local Entity entity, @Share("isGlowingStarredMob") LocalBooleanRef isGlowingStarredMob) { boolean isAStarredMobThatShouldGlow = SkyblockerConfig.get().locations.dungeons.starredMobGlow && StarredMobGlow.shouldMobGlow(entity); - + isGlowingStarredMob.set(isAStarredMobThatShouldGlow); - + return original || isAStarredMobThatShouldGlow; } - + @ModifyVariable(method = "render", at = @At("STORE"), ordinal = 0) private int skyblocker$modifyGlowColor(int color, @Local Entity entity, @Share("isGlowingStarredMob") LocalBooleanRef isGlowingStarredMob) { return isGlowingStarredMob.get() ? StarredMobGlow.getGlowColor(entity) : color; diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/YggdrasilServicesKeyInfoMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/YggdrasilServicesKeyInfoMixin.java new file mode 100644 index 00000000..d9668100 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/YggdrasilServicesKeyInfoMixin.java @@ -0,0 +1,54 @@ +package me.xmrvizzy.skyblocker.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.authlib.yggdrasil.YggdrasilServicesKeyInfo; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import me.xmrvizzy.skyblocker.utils.Utils; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.Base64; +import java.util.Map; + +@Mixin(value = YggdrasilServicesKeyInfo.class, remap = false) +public class YggdrasilServicesKeyInfoMixin { + @Shadow + @Final + private static Logger LOGGER; + @Unique + private static final Map<String, String> REPLACEMENT_MAP = Map.of(); + @Unique + private static final IntList ERRONEUS_SIGNATURE_HASHES = new IntArrayList(); + + @WrapOperation(method = "validateProperty", at = @At(value = "INVOKE", target = "Ljava/util/Base64$Decoder;decode(Ljava/lang/String;)[B", remap = false), remap = false) + private byte[] skyblocker$replaceKnownWrongBase64(Base64.Decoder decoder, String signature, Operation<byte[]> decode) { + try { + return decode.call(decoder, signature); + } catch (IllegalArgumentException e) { + try { + return decode.call(decoder, signature.replaceAll("[^A-Za-z0-9+/=]", "")); + } catch (IllegalArgumentException e2) { + if (Utils.isOnSkyblock()) { + if (REPLACEMENT_MAP.containsKey(signature)) { + return decode.call(decoder, REPLACEMENT_MAP.get(signature)); + } + int signatureHashCode = signature.hashCode(); + if (!ERRONEUS_SIGNATURE_HASHES.contains(signatureHashCode)) { + ERRONEUS_SIGNATURE_HASHES.add(signatureHashCode); + LOGGER.warn("[Skyblocker Base64 Fixer] Failed to decode base64 string No.{}: {}", ERRONEUS_SIGNATURE_HASHES.size() - 1, signature); + } else { + LOGGER.warn("[Skyblocker Base64 Fixer] Failed to decode the base64 string No.{} again", ERRONEUS_SIGNATURE_HASHES.indexOf(signatureHashCode)); + } + } + } + throw e; + } + } +}
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/DrawContextInvoker.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/DrawContextInvoker.java new file mode 100644 index 00000000..f1e5b684 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/DrawContextInvoker.java @@ -0,0 +1,17 @@ +package me.xmrvizzy.skyblocker.mixin.accessor; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.tooltip.TooltipComponent; +import net.minecraft.client.gui.tooltip.TooltipPositioner; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; + +@Mixin(DrawContext.class) +public interface DrawContextInvoker { + + @Invoker + void invokeDrawTooltip(TextRenderer textRenderer, List<TooltipComponent> components, int x, int y, TooltipPositioner positioner); +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java new file mode 100644 index 00000000..6a671601 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java @@ -0,0 +1,14 @@ +package me.xmrvizzy.skyblocker.mixin.accessor; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Screen.class) +public interface ScreenAccessor { + @Accessor + @Mutable + void setTitle(Text title); +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/FairySouls.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/FairySouls.java index ab6fa767..fcd6be7a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/FairySouls.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/FairySouls.java @@ -5,17 +5,21 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.mojang.brigadier.CommandDispatcher; import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.NEURepo; +import me.xmrvizzy.skyblocker.utils.PosUtils; import me.xmrvizzy.skyblocker.utils.Utils; import me.xmrvizzy.skyblocker.utils.render.RenderHelper; 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.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.DyeColor; @@ -37,6 +41,7 @@ public class FairySouls { private static final Map<String, Set<BlockPos>> fairySouls = new HashMap<>(); private static final Map<String, Map<String, Set<BlockPos>>> foundFairies = new HashMap<>(); + @SuppressWarnings("UnusedReturnValue") public static CompletableFuture<Void> runAsyncAfterFairySoulsLoad(Runnable runnable) { if (fairySoulsLoaded == null) { LOGGER.error("Fairy Souls have not being initialized yet! Please ensure the Fairy Souls module is initialized before modules calling this method in SkyblockerMod#onInitializeClient. This error can be safely ignore in a test environment."); @@ -50,9 +55,16 @@ public class FairySouls { } public static void init() { + loadFairySouls(); + ClientLifecycleEvents.CLIENT_STOPPING.register(FairySouls::saveFoundFairySouls); + ClientCommandRegistrationCallback.EVENT.register(FairySouls::registerCommands); + WorldRenderEvents.AFTER_TRANSLUCENT.register(FairySouls::render); + ClientReceiveMessageEvents.GAME.register(FairySouls::onChatMessage); + } + + private static void loadFairySouls() { fairySoulsLoaded = NEURepo.runAsyncAfterLoad(() -> { - try { - BufferedReader reader = new BufferedReader(new FileReader(NEURepo.LOCAL_REPO_DIR.resolve("constants").resolve("fairy_souls.json").toFile())); + try (BufferedReader reader = new BufferedReader(new FileReader(NEURepo.LOCAL_REPO_DIR.resolve("constants").resolve("fairy_souls.json").toFile()))) { for (Map.Entry<String, JsonElement> fairySoulJson : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) { if (fairySoulJson.getKey().equals("//") || fairySoulJson.getKey().equals("Max Souls")) { if (fairySoulJson.getKey().equals("Max Souls")) { @@ -62,61 +74,44 @@ public class FairySouls { } ImmutableSet.Builder<BlockPos> fairySoulsForLocation = ImmutableSet.builder(); for (JsonElement fairySoul : fairySoulJson.getValue().getAsJsonArray().asList()) { - fairySoulsForLocation.add(parseBlockPos(fairySoul)); + fairySoulsForLocation.add(PosUtils.parsePosString(fairySoul.getAsString())); } fairySouls.put(fairySoulJson.getKey(), fairySoulsForLocation.build()); } - reader = new BufferedReader(new FileReader(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json").toFile())); + LOGGER.debug("[Skyblocker] Loaded fairy soul locations"); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to load fairy soul locations", e); + } + + try (BufferedReader reader = new BufferedReader(new FileReader(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json").toFile()))) { for (Map.Entry<String, JsonElement> foundFairiesForProfileJson : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) { Map<String, Set<BlockPos>> foundFairiesForProfile = new HashMap<>(); for (Map.Entry<String, JsonElement> foundFairiesForLocationJson : foundFairiesForProfileJson.getValue().getAsJsonObject().asMap().entrySet()) { Set<BlockPos> foundFairiesForLocation = new HashSet<>(); for (JsonElement foundFairy : foundFairiesForLocationJson.getValue().getAsJsonArray().asList()) { - foundFairiesForLocation.add(parseBlockPos(foundFairy)); + foundFairiesForLocation.add(PosUtils.parsePosString(foundFairy.getAsString())); } foundFairiesForProfile.put(foundFairiesForLocationJson.getKey(), foundFairiesForLocation); } foundFairies.put(foundFairiesForProfileJson.getKey(), foundFairiesForProfile); } - reader.close(); + LOGGER.debug("[Skyblocker] Loaded found fairy souls"); + } catch (FileNotFoundException ignored) { } catch (IOException e) { - LOGGER.error("Failed to load found fairy souls", e); - } catch (Exception e) { - LOGGER.error("Encountered unknown exception loading fairy souls", e); + LOGGER.error("[Skyblocker] Failed to load found fairy souls", e); } }); - ClientLifecycleEvents.CLIENT_STOPPING.register(FairySouls::saveFoundFairySouls); - WorldRenderEvents.AFTER_TRANSLUCENT.register(FairySouls::render); - ClientReceiveMessageEvents.GAME.register(FairySouls::onChatMessage); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE) - .then(literal("fairySouls") - .then(literal("markAllInCurrentIslandFound").executes(context -> { - FairySouls.markAllFairiesFound(); - context.getSource().sendFeedback(Text.translatable("skyblocker.fairySouls.markAllFound")); - return 1; - })) - .then(literal("markAllInCurrentIslandMissing").executes(context -> { - FairySouls.markAllFairiesNotFound(); - context.getSource().sendFeedback(Text.translatable("skyblocker.fairySouls.markAllMissing")); - return 1; - }))))); } - private static BlockPos parseBlockPos(JsonElement posJson) { - String[] posArray = posJson.getAsString().split(","); - return new BlockPos(Integer.parseInt(posArray[0]), Integer.parseInt(posArray[1]), Integer.parseInt(posArray[2])); - } - - public static void saveFoundFairySouls(MinecraftClient client) { - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json").toFile())); + private static void saveFoundFairySouls(MinecraftClient client) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json").toFile()))) { JsonObject foundFairiesJson = new JsonObject(); for (Map.Entry<String, Map<String, Set<BlockPos>>> foundFairiesForProfile : foundFairies.entrySet()) { JsonObject foundFairiesForProfileJson = new JsonObject(); for (Map.Entry<String, Set<BlockPos>> foundFairiesForLocation : foundFairiesForProfile.getValue().entrySet()) { JsonArray foundFairiesForLocationJson = new JsonArray(); for (BlockPos foundFairy : foundFairiesForLocation.getValue()) { - foundFairiesForLocationJson.add(foundFairy.getX() + "," + foundFairy.getY() + "," + foundFairy.getZ()); + foundFairiesForLocationJson.add(PosUtils.getPosString(foundFairy)); } foundFairiesForProfileJson.add(foundFairiesForLocation.getKey(), foundFairiesForLocationJson); } @@ -124,57 +119,84 @@ public class FairySouls { } SkyblockerMod.GSON.toJson(foundFairiesJson, writer); writer.close(); + LOGGER.info("[Skyblocker] Saved found fairy souls"); } catch (IOException e) { - LOGGER.error("Failed to write found fairy souls to file."); + LOGGER.error("[Skyblocker] Failed to write found fairy souls to file", e); } } - public static void render(WorldRenderContext context) { - if (SkyblockerConfig.get().general.fairySouls.enableFairySoulsHelper && fairySoulsLoaded.isDone() && fairySouls.containsKey(Utils.getLocationRaw())) { - for (BlockPos fairySoul : fairySouls.get(Utils.getLocationRaw())) { - float[] colorComponents = isFairySoulNotFound(fairySoul) ? DyeColor.GREEN.getColorComponents() : DyeColor.RED.getColorComponents(); - RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, fairySoul, colorComponents, 0.5F); - } - } + private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(literal(SkyblockerMod.NAMESPACE) + .then(literal("fairySouls") + .then(literal("markAllInCurrentIslandFound").executes(context -> { + FairySouls.markAllFairiesOnCurrentIslandFound(); + context.getSource().sendFeedback(Text.translatable("skyblocker.fairySouls.markAllFound")); + return 1; + })) + .then(literal("markAllInCurrentIslandMissing").executes(context -> { + FairySouls.markAllFairiesOnCurrentIslandMissing(); + context.getSource().sendFeedback(Text.translatable("skyblocker.fairySouls.markAllMissing")); + return 1; + })))); } - private static boolean isFairySoulNotFound(BlockPos fairySoul) { - Map<String, Set<BlockPos>> foundFairiesForProfile = foundFairies.get(Utils.getProfile()); - if (foundFairiesForProfile == null) { - return true; - } - Set<BlockPos> foundFairiesForProfileAndLocation = foundFairiesForProfile.get(Utils.getLocationRaw()); - if (foundFairiesForProfileAndLocation == null) { - return true; + private static void render(WorldRenderContext context) { + SkyblockerConfig.FairySouls fairySoulsConfig = SkyblockerConfig.get().general.fairySouls; + + if (fairySoulsConfig.enableFairySoulsHelper && fairySoulsLoaded.isDone() && fairySouls.containsKey(Utils.getLocationRaw())) { + for (BlockPos fairySoulPos : fairySouls.get(Utils.getLocationRaw())) { + boolean fairySoulNotFound = isFairySoulMissing(fairySoulPos); + if (!fairySoulsConfig.highlightFoundSouls && !fairySoulNotFound || fairySoulsConfig.highlightOnlyNearbySouls && fairySoulPos.getSquaredDistance(context.camera().getPos()) > 2500) { + continue; + } + float[] colorComponents = fairySoulNotFound ? DyeColor.GREEN.getColorComponents() : DyeColor.RED.getColorComponents(); + RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, fairySoulPos, colorComponents, 0.5F); + } } - return !foundFairiesForProfileAndLocation.contains(fairySoul); } - public static void onChatMessage(Text text, boolean overlay) { + private static void onChatMessage(Text text, boolean overlay) { String message = text.getString(); - if (message.equals("You have already found that Fairy Soul!") || message.equals("SOUL! You found a Fairy Soul!")) { + if (message.equals("You have already found that Fairy Soul!") || message.equals("§d§lSOUL! §fYou found a §dFairy Soul§f!")) { markClosestFairyFound(); } } private static void markClosestFairyFound() { + if (!fairySoulsLoaded.isDone()) return; PlayerEntity player = MinecraftClient.getInstance().player; if (player == null) { - LOGGER.warn("Failed to mark closest fairy soul as found because player is null."); + LOGGER.warn("[Skyblocker] Failed to mark closest fairy soul as found because player is null"); return; } - fairySouls.get(Utils.getLocationRaw()).stream().filter(FairySouls::isFairySoulNotFound).min(Comparator.comparingDouble(fairySoul -> fairySoul.getSquaredDistance(player.getPos()))).ifPresent(fairySoul -> { - initializeFoundFairiesForCurrentProfileAndLocation(); - foundFairies.get(Utils.getProfile()).get(Utils.getLocationRaw()).add(fairySoul); - }); + fairySouls.get(Utils.getLocationRaw()).stream() + .filter(FairySouls::isFairySoulMissing) + .min(Comparator.comparingDouble(fairySoulPos -> fairySoulPos.getSquaredDistance(player.getPos()))) + .filter(fairySoulPos -> fairySoulPos.getSquaredDistance(player.getPos()) <= 16) + .ifPresent(fairySoulPos -> { + initializeFoundFairiesForCurrentProfileAndLocation(); + foundFairies.get(Utils.getProfile()).get(Utils.getLocationRaw()).add(fairySoulPos); + }); + } + + private static boolean isFairySoulMissing(BlockPos fairySoulPos) { + Map<String, Set<BlockPos>> foundFairiesForProfile = foundFairies.get(Utils.getProfile()); + if (foundFairiesForProfile == null) { + return true; + } + Set<BlockPos> foundFairiesForProfileAndLocation = foundFairiesForProfile.get(Utils.getLocationRaw()); + if (foundFairiesForProfileAndLocation == null) { + return true; + } + return !foundFairiesForProfileAndLocation.contains(fairySoulPos); } - public static void markAllFairiesFound() { + public static void markAllFairiesOnCurrentIslandFound() { initializeFoundFairiesForCurrentProfileAndLocation(); foundFairies.get(Utils.getProfile()).get(Utils.getLocationRaw()).addAll(fairySouls.get(Utils.getLocationRaw())); } - public static void markAllFairiesNotFound() { + public static void markAllFairiesOnCurrentIslandMissing() { Map<String, Set<BlockPos>> foundFairiesForProfile = foundFairies.get(Utils.getProfile()); if (foundFairiesForProfile != null) { foundFairiesForProfile.remove(Utils.getLocationRaw()); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/QuiverWarning.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/QuiverWarning.java index cf793461..381bf94c 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/QuiverWarning.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/QuiverWarning.java @@ -1,8 +1,8 @@ package me.xmrvizzy.skyblocker.skyblock; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.hud.InGameHud; @@ -16,7 +16,7 @@ public class QuiverWarning { public static void init() { ClientReceiveMessageEvents.ALLOW_GAME.register(QuiverWarning::onChatMessage); - SkyblockerMod.getInstance().scheduler.scheduleCyclic(QuiverWarning::update, 10); + Scheduler.INSTANCE.scheduleCyclic(QuiverWarning::update, 10); } public static boolean onChatMessage(Text text, boolean overlay) { diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java index 6e354ddc..8d676d0b 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java @@ -1,10 +1,10 @@ package me.xmrvizzy.skyblocker.skyblock.dungeon; import it.unimi.dsi.fastutil.objects.ObjectIntPair; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; import me.xmrvizzy.skyblocker.utils.render.RenderHelper; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.client.MinecraftClient; @@ -35,7 +35,7 @@ public class DungeonBlaze { private static ArmorStandEntity nextLowestBlaze = null; public static void init() { - SkyblockerMod.getInstance().scheduler.scheduleCyclic(DungeonBlaze::update, 4); + Scheduler.INSTANCE.scheduleCyclic(DungeonBlaze::update, 4); WorldRenderEvents.BEFORE_DEBUG_RENDER.register(DungeonBlaze::blazeRenderer); } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java new file mode 100644 index 00000000..b223e1a2 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java @@ -0,0 +1,168 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.ints.IntBooleanPair; +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.mixin.accessor.ScreenAccessor; +import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip; +import me.xmrvizzy.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DungeonChestProfit { + private static final Logger LOGGER = LoggerFactory.getLogger(DungeonChestProfit.class); + private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?<type>[A-Za-z]+) Essence x(?<amount>[0-9]+)"); + private static final DecimalFormat FORMATTER = new DecimalFormat("#,###"); + + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> ScreenEvents.afterTick(screen).register(screen1 -> { + if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen && genericContainerScreen.getScreenHandler().getType() == ScreenHandlerType.GENERIC_9X6) { + ((ScreenAccessor) screen).setTitle(getChestProfit(genericContainerScreen.getScreenHandler(), screen.getTitle(), client)); + } + })); + } + + public static Text getChestProfit(GenericContainerScreenHandler handler, Text title, MinecraftClient client) { + try { + if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator && isDungeonChest(title.getString())) { + int profit = 0; + boolean hasIncompleteData = false, usedKismet = false; + List<Slot> slots = handler.slots.subList(0, handler.getRows() * 9); + + //If the item stack for the "Open Reward Chest" button or the kismet button hasn't been sent to the client yet + if (slots.get(31).getStack().isEmpty() || slots.get(50).getStack().isEmpty()) return title; + + for (Slot slot : slots) { + ItemStack stack = slot.getStack(); + + if (!stack.isEmpty()) { + String name = stack.getName().getString(); + String id = PriceInfoTooltip.getInternalNameFromNBT(stack, false); + + //Regular item price + if (id != null) { + IntBooleanPair priceData = getItemPrice(id); + + if (!priceData.rightBoolean()) hasIncompleteData = true; + + //Add the item price to the profit + profit += priceData.leftInt(); + + continue; + } + + //Essence price + if (name.contains("Essence") && SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.includeEssence) { + Matcher matcher = ESSENCE_PATTERN.matcher(name); + + if (matcher.matches()) { + String type = matcher.group("type"); + int amount = Integer.parseInt(matcher.group("amount")); + + IntBooleanPair priceData = getItemPrice(("ESSENCE_" + type).toUpperCase()); + + if (!priceData.rightBoolean()) hasIncompleteData = true; + + //Add the price of the essence to the profit + profit += priceData.leftInt() * amount; + + continue; + } + } + + //Determine the cost of the chest + if (name.contains("Open Reward Chest")) { + String foundString = searchLoreFor(stack, client, "Coins"); + + //Incase we're searching the free chest + if (!StringUtils.isBlank(foundString)) { + profit -= Integer.parseInt(foundString.replaceAll("[^0-9]", "")); + } + + continue; + } + + //Determine if a kismet was used or not + if (name.contains("Reroll Chest")) { + usedKismet = !StringUtils.isBlank(searchLoreFor(stack, client, "You already rerolled a chest!")); + } + } + } + + if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.includeKismet && usedKismet) { + IntBooleanPair kismetPriceData = getItemPrice("KISMET_FEATHER"); + + if (!kismetPriceData.rightBoolean()) hasIncompleteData = true; + + profit -= kismetPriceData.leftInt(); + } + + return Text.literal(title.getString()).append(getProfitText(profit, hasIncompleteData)); + } + } catch (Exception e) { + LOGGER.error("[Skyblocker Profit Calculator] Failed to calculate dungeon chest profit! ", e); + } + + return title; + } + + /** + * @return An {@link IntBooleanPair} with the {@code left int} representing the item's price, and the {@code right boolean} indicating if the price + * was based on complete data. + */ + private static IntBooleanPair getItemPrice(String id) { + JsonObject bazaarPrices = PriceInfoTooltip.getBazaarPrices(); + JsonObject lbinPrices = PriceInfoTooltip.getLBINPrices(); + + if (bazaarPrices == null || lbinPrices == null) return IntBooleanPair.of(0, false); + + if (bazaarPrices.has(id)) { + JsonObject item = bazaarPrices.get(id).getAsJsonObject(); + boolean isPriceNull = item.get("sellPrice").isJsonNull(); + + return IntBooleanPair.of(isPriceNull ? 0 : (int) item.get("sellPrice").getAsDouble(), !isPriceNull); + } + + if (lbinPrices.has(id)) { + return IntBooleanPair.of((int) lbinPrices.get(id).getAsDouble(), true); + } + + return IntBooleanPair.of(0, 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) { + return stack.getTooltip(client.player, TooltipContext.BASIC).stream().map(Text::getString).filter(line -> line.contains(searchString)).findAny().orElse(null); + } + + private static boolean isDungeonChest(String name) { + return name.equals("Wood Chest") || name.equals("Gold Chest") || name.equals("Diamond Chest") || name.equals("Emerald Chest") || name.equals("Obsidian Chest") || name.equals("Bedrock Chest"); + } + + private static Text getProfitText(int profit, boolean hasIncompleteData) { + SkyblockerConfig.DungeonChestProfit config = SkyblockerConfig.get().locations.dungeons.dungeonChestProfit; + return getProfitText(profit, hasIncompleteData, config.neutralThreshold, config.neutralColor.formatting, config.profitColor.formatting, config.lossColor.formatting, config.incompleteColor.formatting); + } + + static Text getProfitText(int profit, boolean hasIncompleteData, int neutralThreshold, Formatting neutralColor, Formatting profitColor, Formatting lossColor, Formatting incompleteColor) { + return Text.literal((profit > 0 ? " +" : " ") + FORMATTER.format(profit)).formatted(hasIncompleteData ? incompleteColor : (Math.abs(profit) < neutralThreshold) ? neutralColor : (profit > 0) ? profitColor : lossColor); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java index 4701c485..e5d8a720 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java @@ -1,8 +1,8 @@ package me.xmrvizzy.skyblocker.skyblock.dungeon; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.scheduler.MessageScheduler; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.util.math.BlockPos; @@ -23,13 +23,13 @@ public class LividColor { if (tenTicks != 0) { if (SkyblockerConfig.get().locations.dungeons.lividColor.enableLividColor && Utils.isInDungeons() && client.world != null) { if (tenTicks == 1) { - SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown(SkyblockerConfig.get().locations.dungeons.lividColor.lividColorText.replace("[color]", "red")); + MessageScheduler.INSTANCE.sendMessageAfterCooldown(SkyblockerConfig.get().locations.dungeons.lividColor.lividColorText.replace("[color]", "red")); tenTicks = 0; return; } String key = client.world.getBlockState(new BlockPos(5, 110, 42)).getBlock().getTranslationKey(); if (key.startsWith("block.minecraft.") && key.endsWith("wool") && !key.endsWith("red_wool")) { - SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown(SkyblockerConfig.get().locations.dungeons.lividColor.lividColorText.replace("[color]", key.substring(16, key.length() - 5))); + MessageScheduler.INSTANCE.sendMessageAfterCooldown(SkyblockerConfig.get().locations.dungeons.lividColor.lividColorText.replace("[color]", key.substring(16, key.length() - 5))); tenTicks = 0; return; } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java index e1194632..8d8a840a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java @@ -1,10 +1,11 @@ package me.xmrvizzy.skyblocker.skyblock.dungeon; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; import me.xmrvizzy.skyblocker.utils.chat.ChatFilterResult; import me.xmrvizzy.skyblocker.utils.chat.ChatPatternListener; +import me.xmrvizzy.skyblocker.utils.scheduler.MessageScheduler; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; @@ -35,7 +36,7 @@ public class Reparty extends ChatPatternListener { ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> { if (!Utils.isOnSkyblock() || this.repartying || client.player == null) return 0; this.repartying = true; - SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown("/p list"); + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/p list"); return 0; }))); } @@ -57,7 +58,7 @@ public class Reparty extends ChatPatternListener { } } else if (matcher.group("disband") != null && !matcher.group("disband").equals(client.getSession().getUsername())) { partyLeader = matcher.group("disband"); - SkyblockerMod.getInstance().scheduler.schedule(() -> partyLeader = null, 61); + Scheduler.INSTANCE.schedule(() -> partyLeader = null, 61); return false; } else if (matcher.group("invite") != null && matcher.group("invite").equals(partyLeader)) { String command = "/party accept " + partyLeader; @@ -84,10 +85,10 @@ public class Reparty extends ChatPatternListener { String command = "/p invite " + this.players[i]; sendCommand(command, i + 2); } - SkyblockerMod.getInstance().scheduler.schedule(() -> this.repartying = false, this.players.length + 2); + Scheduler.INSTANCE.schedule(() -> this.repartying = false, this.players.length + 2); } private void sendCommand(String command, int delay) { - SkyblockerMod.getInstance().messageScheduler.queueMessage(command, delay * BASE_DELAY); + MessageScheduler.INSTANCE.queueMessage(command, delay * BASE_DELAY); } }
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java index a7420bf5..ac389d34 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java @@ -13,6 +13,7 @@ import it.unimi.dsi.fastutil.objects.ObjectIntPair; import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; @@ -140,7 +141,7 @@ public class DungeonSecrets { LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e); return null; }); - SkyblockerMod.getInstance().scheduler.scheduleCyclic(DungeonSecrets::update, 10); + Scheduler.INSTANCE.scheduleCyclic(DungeonSecrets::update, 10); WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonSecrets::render); ClientReceiveMessageEvents.GAME.register(DungeonSecrets::onChatMessage); ClientReceiveMessageEvents.GAME_CANCELED.register(DungeonSecrets::onChatMessage); @@ -178,8 +179,8 @@ public class DungeonSecrets { } 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"))) { - SkyblockerMod.GSON.fromJson(roomsReader, JsonObject.class).asMap().forEach((room, jsonElement) -> roomsJson.put(room.toLowerCase(), jsonElement)); - SkyblockerMod.GSON.fromJson(waypointsReader, JsonObject.class).asMap().forEach((room, jsonElement) -> waypointsJson.put(room.toLowerCase(), jsonElement)); + loadJson(roomsReader, roomsJson); + loadJson(waypointsReader, waypointsJson); LOGGER.debug("[Skyblocker] Loaded dungeon secrets json"); } catch (Exception e) { LOGGER.error("[Skyblocker] Failed to load dungeon secrets json", e); @@ -200,6 +201,15 @@ public class DungeonSecrets { } } + /** + * Loads the json from the given {@link BufferedReader} into the given {@link Map}. + * @param reader the reader to read the json from + * @param map the map to load into + */ + private static void loadJson(BufferedReader reader, Map<String, JsonElement> map) { + SkyblockerMod.GSON.fromJson(reader, JsonObject.class).asMap().forEach((room, jsonElement) -> map.put(room.toLowerCase().replaceAll(" ", "-"), jsonElement)); + } + private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, Integer>> markSecretsCommand(boolean found) { return argument("secret", IntegerArgumentType.integer()).executes(context -> { int secretIndex = IntegerArgumentType.getInteger(context, "secret"); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java index 6825d779..ae5afaa3 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java @@ -8,7 +8,7 @@ import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.IntRBTreeSet; import it.unimi.dsi.fastutil.ints.IntSortedSet; import it.unimi.dsi.fastutil.ints.IntSortedSets; -import me.xmrvizzy.skyblocker.SkyblockerMod; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.util.TriState; import net.minecraft.block.BlockState; @@ -249,7 +249,7 @@ public class Room { // If no rooms match, reset the fields and scan again after 50 ticks. matched = TriState.FALSE; DungeonSecrets.LOGGER.warn("[Skyblocker] No dungeon room matches after checking {} block(s)", checkedBlocks.size()); - SkyblockerMod.getInstance().scheduler.schedule(() -> matched = TriState.DEFAULT, 50); + Scheduler.INSTANCE.schedule(() -> matched = TriState.DEFAULT, 50); reset(); return true; } else if (matchingRoomsSize == 1 && ++doubleCheckBlocks >= 10) { diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/AdFilter.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/AdFilter.java index c8335699..23caa180 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/AdFilter.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/AdFilter.java @@ -1,6 +1,7 @@ package me.xmrvizzy.skyblocker.skyblock.filters; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Constants; import me.xmrvizzy.skyblocker.utils.chat.ChatFilterResult; import me.xmrvizzy.skyblocker.utils.chat.ChatPatternListener; import net.minecraft.text.Text; @@ -13,14 +14,13 @@ public class AdFilter extends ChatPatternListener { Pattern.compile("^(?:i(?:m|'m| am)? |(?:is )?any(?: ?one|1) )?(?:buy|sell|lowball|trade?)(?:ing)?(?:\\W|$)", Pattern.CASE_INSENSITIVE), Pattern.compile("(.)\\1{7,}"), Pattern.compile("\\W(?:on|in|check|at) my (?:ah|bin)(?:\\W|$)", Pattern.CASE_INSENSITIVE), }; - private static final String EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u12DE"; public AdFilter() { // Groups: // 1. Player name // 2. Message // (?:§8\[[§feadbc0-9]+§8\] )?(?:[§76l]+[<INSERT EMBLEMS>] )?§[67abc](?:\[[§A-Za-z0-9+]+\] )?([A-Za-z0-9_]+)§[f7]: (.+) - super("(?:§8\\[[§feadbc0-9]+§8\\] )?(?:[§76l]+[" + EMBLEMS + "] )?§[67abc](?:\\[[§A-Za-z0-9+]+\\] )?([A-Za-z0-9_]+)§[f7]: (.+)"); + super("(?:§8\\[[§feadbc0-9]+§8\\] )?(?:[§76l]+[" + Constants.LEVEL_EMBLEMS + "] )?§[67abc](?:\\[[§A-Za-z0-9+]+\\] )?([A-Za-z0-9_]+)§[f7]: (.+)"); } @Override diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/ShowOffFilter.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/ShowOffFilter.java index 6692e612..85be9e2a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/ShowOffFilter.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/filters/ShowOffFilter.java @@ -1,14 +1,14 @@ package me.xmrvizzy.skyblocker.skyblock.filters; +import me.xmrvizzy.skyblocker.utils.Constants; import me.xmrvizzy.skyblocker.utils.chat.ChatFilterResult; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; public class ShowOffFilter extends SimpleChatFilter { - private static final String EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u12DE"; private static final String[] SHOW_TYPES = { "is holding", "is wearing", "is friends with a", "has" }; public ShowOffFilter() { - super("(?:§8\\[[§feadbc0-9]+§8\\] )?(?:[§76l]+[" + EMBLEMS + "] )?§[67abc](?:\\[[§A-Za-z0-9+]+\\] )?([A-Za-z0-9_]+)[§f7]+ (?:" + String.join("|", SHOW_TYPES) + ") §8\\[(.+)§8\\]"); + super("(?:§8\\[[§feadbc0-9]+§8\\] )?(?:[§76l]+[" + Constants.LEVEL_EMBLEMS + "] )?§[67abc](?:\\[[§A-Za-z0-9+]+\\] )?([A-Za-z0-9_]+)[§f7]+ (?:" + String.join("|", SHOW_TYPES) + ") §8\\[(.+)§8\\]"); } @Override diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/AttributeShards.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/AttributeShards.java index 0f106cdf..8f71e7b9 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/AttributeShards.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/AttributeShards.java @@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; public class AttributeShards { private static final Object2ObjectOpenHashMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>(); - + static { //Weapons ID_2_SHORT_NAME.put("arachno", "A"); @@ -18,11 +18,11 @@ public class AttributeShards { ID_2_SHORT_NAME.put("mana_steal", "MS"); ID_2_SHORT_NAME.put("midas_touch", "MT"); ID_2_SHORT_NAME.put("undead", "U"); - + //Swords & Bows ID_2_SHORT_NAME.put("warrior", "W"); ID_2_SHORT_NAME.put("deadeye", "DE"); - + //Armor or Equipment ID_2_SHORT_NAME.put("arachno_resistance", "AR"); ID_2_SHORT_NAME.put("blazing_resistance", "BR"); @@ -40,7 +40,7 @@ public class AttributeShards { ID_2_SHORT_NAME.put("speed", "S"); ID_2_SHORT_NAME.put("undead_resistance", "UR"); ID_2_SHORT_NAME.put("veteran", "V"); - + //Fishing Gear ID_2_SHORT_NAME.put("blazing_fortune", "BF"); ID_2_SHORT_NAME.put("fishing_experience", "FE"); @@ -50,9 +50,9 @@ 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 static String getShortName(String id) { return ID_2_SHORT_NAME.getOrDefault(id, ""); } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/BackpackPreview.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/BackpackPreview.java index d89a18e0..dc8f77cd 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/BackpackPreview.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/BackpackPreview.java @@ -1,4 +1,4 @@ -package me.xmrvizzy.skyblocker.skyblock; +package me.xmrvizzy.skyblocker.skyblock.item; import com.mojang.blaze3d.systems.RenderSystem; import me.xmrvizzy.skyblocker.SkyblockerMod; @@ -10,27 +10,25 @@ 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.PlayerListEntry; -import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.*; import net.minecraft.util.Identifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class BackpackPreview { + private static final Logger LOGGER = LoggerFactory.getLogger(BackpackPreview.class); private static final Identifier TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/inventory_background.png"); - private static final BackpackPreview instance = new BackpackPreview(); - private static final Pattern PROFILE_PATTERN = Pattern.compile("Profile: ([a-zA-Z]+)"); 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; @@ -56,8 +54,8 @@ public class BackpackPreview { saveStorage(); // update save dir based on uuid and sb profile String uuid = MinecraftClient.getInstance().getSession().getUuid().replaceAll("-", ""); - String profile = getSkyblockProfile(); - if (profile != null) { + String profile = Utils.getProfile(); + if (profile != null && !profile.isEmpty()) { save_dir = FabricLoader.getInstance().getConfigDir().resolve("skyblocker/backpack-preview/" + uuid + "/" + profile); save_dir.toFile().mkdirs(); if (loaded.equals(uuid + "/" + profile)) { @@ -87,7 +85,7 @@ public class BackpackPreview { NbtCompound root = NbtIo.read(file); storage[index] = new DummyInventory(root); } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("Failed to load backpack preview file: " + file.getName(), e); } } } @@ -119,7 +117,7 @@ public class BackpackPreview { NbtIo.write(root, save_dir.resolve(index + ".nbt").toFile()); dirty[index] = false; } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("Failed to save backpack preview file: " + index + ".nbt", e); } } } @@ -144,19 +142,20 @@ public class BackpackPreview { int rows = (storage[index].size() - 9) / 9; Screen screen = MinecraftClient.getInstance().currentScreen; + if (screen == null) return false; int x = mouseX + 184 >= screen.width ? mouseX - 188 : mouseX + 8; int y = Math.max(0, mouseY - 16); RenderSystem.disableDepthTest(); RenderSystem.setShaderTexture(0, TEXTURE); context.drawTexture(TEXTURE, x, y, 0, 0, 176, 7); - for (int i = 0; i < rows; ++i) + for (int i = 0; i < rows; ++i) { context.drawTexture(TEXTURE, x, y + i * 18 + 7, 0, 7, 176, 18); + } context.drawTexture(TEXTURE, x, y + rows * 18 + 7, 0, 25, 176, 7); RenderSystem.enableDepthTest(); MatrixStack matrices = context.getMatrices(); - ItemRenderer itemRenderer = MinecraftClient.getInstance().getItemRenderer(); TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; for (int i = 9; i < storage[index].size(); ++i) { int itemX = x + (i - 9) % 9 * 18 + 8; @@ -178,19 +177,6 @@ public class BackpackPreview { if (backpack.find()) return Integer.parseInt(backpack.group(1)) + 8; return -1; } - - private static String getSkyblockProfile() { - Collection<PlayerListEntry> list = MinecraftClient.getInstance().getNetworkHandler().getPlayerList(); - for (PlayerListEntry entry : list) { - if (entry.getDisplayName() != null) { - Matcher matcher = PROFILE_PATTERN.matcher(entry.getDisplayName().getString()); - if (matcher.find()) { - return matcher.group(1); - } - } - } - return null; - } } class DummyInventory implements Inventory { diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CompactorDeletorPreview.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CompactorDeletorPreview.java new file mode 100644 index 00000000..7b93fe1e --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CompactorDeletorPreview.java @@ -0,0 +1,92 @@ +package me.xmrvizzy.skyblocker.skyblock.item; + +import it.unimi.dsi.fastutil.ints.IntIntPair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import me.xmrvizzy.skyblocker.mixin.accessor.DrawContextInvoker; +import me.xmrvizzy.skyblocker.skyblock.itemlist.ItemRegistry; +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; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class CompactorDeletorPreview { + /** + * The width and height in slots of the compactor/deletor + */ + private static final Map<String, IntIntPair> DIMENSIONS = Map.of( + "4000", IntIntPair.of(1, 1), + "5000", IntIntPair.of(1, 3), + "6000", IntIntPair.of(1, 7), + "7000", IntIntPair.of(2, 6) + ); + private static final IntIntPair DEFAULT_DIMENSION = IntIntPair.of(1, 6); + 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); + int targetIndex = getTargetIndex(tooltips); + if (targetIndex == -1) return false; + + // Get items in compactor or deletor + NbtCompound nbt = stack.getNbt(); + if (nbt == null || !nbt.contains("ExtraAttributes", 10)) { + return false; + } + NbtCompound extraAttributes = nbt.getCompound("ExtraAttributes"); + // Get the slots and their items from the nbt, which is in the format personal_compact_<slot_number> or personal_deletor_<slot_number> + List<IntObjectPair<ItemStack>> slots = extraAttributes.getKeys().stream().filter(slot -> slot.contains(type.toLowerCase().substring(0, 7))).map(slot -> IntObjectPair.of(Integer.parseInt(slot.substring(17)), ItemRegistry.getItemStack(extraAttributes.getString(slot)))).toList(); + + List<TooltipComponent> components = tooltips.stream().map(Text::asOrderedText).map(TooltipComponent::of).collect(Collectors.toList()); + IntIntPair dimensions = DIMENSIONS.getOrDefault(size, DEFAULT_DIMENSION); + + // If there are no items in compactor or deletor + if (slots.isEmpty()) { + int slotsCount = dimensions.leftInt() * dimensions.rightInt(); + components.add(targetIndex, TooltipComponent.of(Text.literal(slotsCount + (slotsCount == 1 ? " slot" : " slots")).formatted(Formatting.GRAY).asOrderedText())); + + ((DrawContextInvoker) context).invokeDrawTooltip(client.textRenderer, components, x, y, HoveredTooltipPositioner.INSTANCE); + return true; + } + + // Add the preview tooltip component + components.add(targetIndex, new CompactorPreviewTooltipComponent(slots, dimensions)); + + // Render accompanying text + components.add(targetIndex, TooltipComponent.of(Text.literal("Contents:").asOrderedText())); + if (extraAttributes.contains("PERSONAL_DELETOR_ACTIVE")) { + components.add(targetIndex, TooltipComponent.of(Text.literal("Active: ") + .append(extraAttributes.getBoolean("PERSONAL_DELETOR_ACTIVE") ? Text.literal("YES").formatted(Formatting.BOLD).formatted(Formatting.GREEN) : Text.literal("NO").formatted(Formatting.BOLD).formatted(Formatting.RED)).asOrderedText())); + } + ((DrawContextInvoker) context).invokeDrawTooltip(client.textRenderer, components, x, y, HoveredTooltipPositioner.INSTANCE); + return true; + } + + /** + * Finds the target index to insert the preview component, which is the second empty line + */ + private static int getTargetIndex(List<Text> tooltips) { + int targetIndex = -1; + int lineCount = 0; + for (int i = 0; i < tooltips.size(); i++) { + if (tooltips.get(i).getString().isEmpty()) { + lineCount += 1; + } + if (lineCount == 2) { + targetIndex = i; + break; + } + } + return targetIndex; + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java new file mode 100644 index 00000000..45e3c635 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java @@ -0,0 +1,54 @@ +package me.xmrvizzy.skyblocker.skyblock.item; + +import it.unimi.dsi.fastutil.ints.IntIntPair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import me.xmrvizzy.skyblocker.SkyblockerMod; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.tooltip.TooltipComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +public class CompactorPreviewTooltipComponent implements TooltipComponent { + private static final Identifier INVENTORY_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/inventory_background.png"); + private final Iterable<IntObjectPair<ItemStack>> items; + private final IntIntPair dimensions; + + public CompactorPreviewTooltipComponent(Iterable<IntObjectPair<ItemStack>> items, IntIntPair dimensions) { + this.items = items; + this.dimensions = dimensions; + } + + @Override + public int getHeight() { + return dimensions.leftInt() * 18 + 14; + } + + @Override + public int getWidth(TextRenderer textRenderer) { + return dimensions.rightInt() * 18 + 14; + } + + @Override + public void drawItems(TextRenderer textRenderer, int x, int y, DrawContext context) { + context.drawTexture(INVENTORY_TEXTURE, x, y, 0, 0, 7 + dimensions.rightInt() * 18, 7); + context.drawTexture(INVENTORY_TEXTURE, x + 7 + dimensions.rightInt() * 18, y, 169, 0, 7, 7); + + for (int i = 0; i < dimensions.leftInt(); i++) { + context.drawTexture(INVENTORY_TEXTURE, x, y + 7 + i * 18, 0, 7, 7, 18); + for (int j = 0; j < dimensions.rightInt(); j++) { + context.drawTexture(INVENTORY_TEXTURE, x + 7 + j * 18, y + 7 + i * 18, 7, 7, 18, 18); + } + context.drawTexture(INVENTORY_TEXTURE, x + 7 + dimensions.rightInt() * 18, y + 7 + i * 18, 169, 7, 7, 18); + } + context.drawTexture(INVENTORY_TEXTURE, x, y + 7 + dimensions.leftInt() * 18, 0, 25, 7 + dimensions.rightInt() * 18, 7); + context.drawTexture(INVENTORY_TEXTURE, x + 7 + dimensions.rightInt() * 18, y + 7 + dimensions.leftInt() * 18, 169, 25, 7, 7); + + for (IntObjectPair<ItemStack> entry : items) { + int itemX = x + entry.leftInt() % dimensions.rightInt() * 18 + 8; + int itemY = y + entry.leftInt() / dimensions.rightInt() * 18 + 8; + context.drawItem(entry.right(), itemX, itemY); + context.drawItemInSlot(textRenderer, entry.right(), itemX, itemY); + } + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomItemNames.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomItemNames.java index c744144a..8c160456 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomItemNames.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomItemNames.java @@ -53,11 +53,11 @@ public class CustomItemNames { } } else { //If the text is provided then set the item's custom name to it - + //Set italic to false if it hasn't been changed (or was already false) Style currentStyle = text.getStyle(); ((MutableText) text).setStyle(currentStyle.withItalic((currentStyle.isItalic() ? true : false))); - + customItemNames.put(itemUuid, text); SkyblockerConfig.save(); source.sendFeedback(Text.translatable("skyblocker.customItemNames.added")); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java index 84325042..d20aa0e3 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java @@ -2,9 +2,10 @@ package me.xmrvizzy.skyblocker.skyblock.item; import com.google.gson.Gson; import com.google.gson.JsonObject; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Http; import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.minecraft.client.MinecraftClient; import net.minecraft.client.item.TooltipContext; import net.minecraft.item.ItemStack; @@ -15,10 +16,7 @@ import net.minecraft.util.Formatting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; +import java.net.http.HttpHeaders; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -29,11 +27,9 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.zip.GZIPInputStream; public class PriceInfoTooltip { private static final Logger LOGGER = LoggerFactory.getLogger(PriceInfoTooltip.class.getName()); - private static final SkyblockerMod skyblocker = SkyblockerMod.getInstance(); private static final MinecraftClient client = MinecraftClient.getInstance(); private static JsonObject npcPricesJson; private static JsonObject bazaarPricesJson; @@ -45,14 +41,23 @@ public class PriceInfoTooltip { private static boolean nullMsgSend = false; private final static Gson gson = new Gson(); private static final Map<String, String> apiAddresses; + private static long npcHash = 0; + private static long museumHash = 0; + private static long motesHash = 0; public static void onInjectTooltip(ItemStack stack, TooltipContext context, List<Text> lines) { if (!Utils.isOnSkyblock() || client.player == null) return; 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; + } + int count = stack.getCount(); boolean bazaarOpened = lines.stream().anyMatch(each -> each.getString().contains("Buy price:") || each.getString().contains("Sell price:")); @@ -120,12 +125,12 @@ public class PriceInfoTooltip { */ switch (internalID) { case "PET" -> { - name = name.replaceAll("LVL_\\d*_", ""); - String[] parts = name.split("_"); + neuName = neuName.replaceAll("LVL_\\d*_", ""); + String[] parts = neuName.split("_"); String type = parts[0]; - name = name.replaceAll(type + "_", ""); - name = name + "-" + type; - name = name.replace("UNCOMMON", "1") + neuName = neuName.replaceAll(type + "_", ""); + neuName = neuName + "-" + type; + neuName = neuName.replace("UNCOMMON", "1") .replace("COMMON", "0") .replace("RARE", "2") .replace("EPIC", "3") @@ -133,14 +138,14 @@ public class PriceInfoTooltip { .replace("MYTHIC", "5") .replace("-", ";"); } - case "RUNE" -> name = name.replaceAll("_(?!.*_)", ";"); - case "POTION" -> name = ""; + case "RUNE" -> neuName = neuName.replaceAll("_(?!.*_)", ";"); + case "POTION" -> neuName = ""; case "ATTRIBUTE_SHARD" -> - name = internalID + "+" + name.replace("SHARD-", "").replaceAll("_(?!.*_)", ";"); - default -> name = name.replace(":", "-"); + neuName = internalID + "+" + neuName.replace("SHARD-", "").replaceAll("_(?!.*_)", ";"); + default -> neuName = neuName.replace(":", "-"); } - if (!name.isEmpty() && lbinExist) { + if (!neuName.isEmpty() && lbinExist) { SkyblockerConfig.Average type = SkyblockerConfig.get().general.itemTooltip.avg; // "No data" line because of API not keeping old data, it causes NullPointerException @@ -148,9 +153,9 @@ public class PriceInfoTooltip { lines.add( Text.literal(String.format("%-19s", "1 Day Avg. Price:")) .formatted(Formatting.GOLD) - .append(oneDayAvgPricesJson.get(name) == null + .append(oneDayAvgPricesJson.get(neuName) == null ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(oneDayAvgPricesJson.get(name).getAsDouble(), count) + : getCoinsMessage(oneDayAvgPricesJson.get(neuName).getAsDouble(), count) ) ); } @@ -158,9 +163,9 @@ public class PriceInfoTooltip { lines.add( Text.literal(String.format("%-19s", "3 Day Avg. Price:")) .formatted(Formatting.GOLD) - .append(threeDayAvgPricesJson.get(name) == null + .append(threeDayAvgPricesJson.get(neuName) == null ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(threeDayAvgPricesJson.get(name).getAsDouble(), count) + : getCoinsMessage(threeDayAvgPricesJson.get(neuName).getAsDouble(), count) ) ); } @@ -258,6 +263,10 @@ public class PriceInfoTooltip { } // Transformation to API format. + if (ea.contains("is_shiny")){ + return "ISSHINY_" + internalName; + } + switch (internalName) { case "ENCHANTED_BOOK" -> { if (ea.contains("enchantments")) { @@ -320,7 +329,6 @@ public class PriceInfoTooltip { return message; } - private static Text getMotesMessage(int price, int count) { float motesMultiplier = SkyblockerConfig.get().locations.rift.mcGrubberStacks * 0.05f + 1; @@ -346,7 +354,7 @@ public class PriceInfoTooltip { public static int minute = -1; public static void init() { - skyblocker.scheduler.scheduleCyclic(() -> { + Scheduler.INSTANCE.scheduleCyclic(() -> { if (!Utils.isOnSkyblock() && 0 < minute++) { nullMsgSend = false; return; @@ -367,10 +375,10 @@ public class PriceInfoTooltip { futureList.add(CompletableFuture.runAsync(() -> threeDayAvgPricesJson = downloadPrices("3 day avg"))); } } - if (SkyblockerConfig.get().general.itemTooltip.enableLowestBIN) + if (SkyblockerConfig.get().general.itemTooltip.enableLowestBIN || SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator) futureList.add(CompletableFuture.runAsync(() -> lowestPricesJson = downloadPrices("lowest bins"))); - if (SkyblockerConfig.get().general.itemTooltip.enableBazaarPrice) + if (SkyblockerConfig.get().general.itemTooltip.enableBazaarPrice || SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator) futureList.add(CompletableFuture.runAsync(() -> bazaarPricesJson = downloadPrices("bazaar"))); if (SkyblockerConfig.get().general.itemTooltip.enableNPCPrice && npcPricesJson == null) @@ -391,24 +399,43 @@ public class PriceInfoTooltip { private static JsonObject downloadPrices(String type) { try { String url = apiAddresses.get(type); - URL apiAddress = new URL(url); - InputStream src = apiAddress.openStream(); - InputStreamReader reader = new InputStreamReader(url.contains(".gz") ? new GZIPInputStream(src) : src); - return new Gson().fromJson(reader, JsonObject.class); - } catch (IOException e) { + + if (type.equals("npc") || type.equals("museum") || type.equals("motes")) { + HttpHeaders headers = Http.sendHeadRequest(url); + long combinedHash = Http.getEtag(headers).hashCode() + Http.getLastModified(headers).hashCode(); + + switch (type) { + case "npc": if (npcHash == combinedHash) return npcPricesJson; else npcHash = combinedHash; + case "museum": if (museumHash == combinedHash) return isMuseumJson; else museumHash = combinedHash; + case "motes": if (motesHash == combinedHash) return motesPricesJson; else motesHash = combinedHash; + } + } + + String apiResponse = Http.sendGetRequest(url); + + return new Gson().fromJson(apiResponse, JsonObject.class); + } catch (Exception e) { LOGGER.warn("[Skyblocker] Failed to download " + type + " prices!", e); return null; } } + + public static JsonObject getBazaarPrices() { + return bazaarPricesJson; + } + + public static JsonObject getLBINPrices() { + return lowestPricesJson; + } static { apiAddresses = new HashMap<>(); - apiAddresses.put("1 day avg", "https://moulberry.codes/auction_averages_lbin/1day.json.gz"); - apiAddresses.put("3 day avg", "https://moulberry.codes/auction_averages_lbin/3day.json.gz"); + apiAddresses.put("1 day avg", "https://moulberry.codes/auction_averages_lbin/1day.json"); + apiAddresses.put("3 day avg", "https://moulberry.codes/auction_averages_lbin/3day.json"); apiAddresses.put("bazaar", "https://hysky.de/api/bazaar"); apiAddresses.put("lowest bins", "https://hysky.de/api/auctions/lowestbins"); apiAddresses.put("npc", "https://hysky.de/api/npcprice"); apiAddresses.put("museum", "https://hysky.de/api/museum"); apiAddresses.put("motes", "https://hysky.de/api/motesprice"); } -} +}
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemListWidget.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemListWidget.java index 04dc6820..4fc24e6c 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemListWidget.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemListWidget.java @@ -6,8 +6,6 @@ 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.Drawable; -import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.util.math.MatrixStack; @@ -16,7 +14,7 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; @Environment(value = EnvType.CLIENT) -public class ItemListWidget extends RecipeBookWidget implements Drawable, Selectable { +public class ItemListWidget extends RecipeBookWidget { private int parentWidth; private int parentHeight; private int leftOffset; diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemRegistry.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemRegistry.java index c0685ec7..5eb9e488 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemRegistry.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemRegistry.java @@ -129,5 +129,9 @@ public class ItemRegistry { if (itemStack.getNbt() == null) return ""; return itemStack.getNbt().getCompound("ExtraAttributes").getString("id"); } + + public static ItemStack getItemStack(String internalName) { + return itemsMap.get(internalName); + } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemStackBuilder.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemStackBuilder.java index e74c145d..8028099a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemStackBuilder.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ItemStackBuilder.java @@ -5,6 +5,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import me.xmrvizzy.skyblocker.utils.NEURepo; +import net.minecraft.item.FireworkRocketItem; import net.minecraft.item.ItemStack; import net.minecraft.nbt.*; import net.minecraft.text.Text; @@ -90,6 +91,16 @@ public class ItemStackBuilder { enchantments.add(new NbtCompound()); tag.put("Enchantments", enchantments); } + + // Add firework star color + Matcher explosionColorMatcher = Pattern.compile("\\{Explosion:\\{(?:Type:[0-9a-z]+,)?Colors:\\[(?<color>[0-9]+)\\]\\}").matcher(nbttag); + if (explosionColorMatcher.find()) { + NbtCompound explosion = new NbtCompound(); + + explosion.putInt("Type", FireworkRocketItem.Type.SMALL_BALL.getId()); //Forget about the actual ball type because it probably doesn't matter + explosion.putIntArray("Colors", new int[] { Integer.parseInt(explosionColorMatcher.group("color")) }); + tag.put("Explosion", explosion); + } return ItemStack.fromNbt(root); } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ResultButtonWidget.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ResultButtonWidget.java index 12636ce1..19f656e5 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ResultButtonWidget.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/ResultButtonWidget.java @@ -1,19 +1,13 @@ package me.xmrvizzy.skyblocker.skyblock.itemlist; import java.util.List; -import java.util.function.Function; import java.util.ArrayList; -import com.google.common.collect.Lists; -import com.mojang.blaze3d.systems.RenderSystem; - 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.client.render.GameRenderer; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.text.OrderedText; @@ -55,17 +49,17 @@ public class ResultButtonWidget extends ClickableWidget { MinecraftClient client = MinecraftClient.getInstance(); 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); } @Override protected void appendClickableNarrations(NarrationMessageBuilder builder) { // TODO Auto-generated method stub - + } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/SearchResultsWidget.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/SearchResultsWidget.java index 7f628a19..feb7f30b 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/SearchResultsWidget.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/itemlist/SearchResultsWidget.java @@ -126,7 +126,7 @@ public class SearchResultsWidget implements Drawable { craftText = textRenderer.trimToWidth(craftText, MAX_TEXT_WIDTH) + ELLIPSIS; } context.drawTextWithShadow(textRenderer, craftText, this.parentX + 11, this.parentY + 31, 0xffffffff); - + //Item name Text resultText = this.recipeResults.get(this.currentPage).result.getName(); if (textRenderer.getWidth(Formatting.strip(resultText.getString())) > MAX_TEXT_WIDTH) { @@ -134,7 +134,7 @@ public class SearchResultsWidget implements Drawable { resultText = Text.literal(getLegacyFormatting(resultText.getString()) + textRenderer.trimToWidth(Formatting.strip(resultText.getString()), MAX_TEXT_WIDTH) + ELLIPSIS).setStyle(resultText.getStyle()); } context.drawTextWithShadow(textRenderer, resultText, this.parentX + 11, this.parentY + 43, 0xffffffff); - + //Arrow pointing to result item from the recipe context.drawTextWithShadow(textRenderer, "â–¶", this.parentX + 96, this.parentY + 90, 0xaaffffff); } @@ -149,7 +149,7 @@ public class SearchResultsWidget implements Drawable { if (this.nextPageButton.active) this.nextPageButton.render(context, mouseX, mouseY, delta); RenderSystem.enableDepthTest(); } - + /** * Used for drawing tooltips over truncated text */ @@ -160,7 +160,7 @@ public class SearchResultsWidget implements Drawable { } RenderSystem.enableDepthTest(); } - + /** * @see #drawTooltip(TextRenderer, DrawContext, Text, int, int, int, int) */ diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java index 351cef48..fb8f438b 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java @@ -5,20 +5,25 @@ import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; 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.HandledScreen; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.StringNbtReader; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.regex.PatternSyntaxException; public class QuickNav { private static final String skyblockHubIconNbt = "{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;-300151517,-631415889,-1193921967,-1821784279],Properties:{textures:[{Value:\"e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0=\"}]}}}}"; private static final String dungeonHubIconNbt = "{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;1605800870,415127827,-1236127084,15358548],Properties:{textures:[{Value:\"e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzg5MWQ1YjI3M2ZmMGJjNTBjOTYwYjJjZDg2ZWVmMWM0MGExYjk0MDMyYWU3MWU3NTQ3NWE1NjhhODI1NzQyMSJ9fX0=\"}]}}}}"; public static void init() { - ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { if (Utils.isOnSkyblock() && SkyblockerConfig.get().quickNav.enableQuickNav && screen instanceof HandledScreen<?> && client.player != null && !client.player.isCreative()) { String screenTitle = screen.getTitle().getString().trim(); List<QuickNavButton> buttons = QuickNav.init(screenTitle); @@ -54,8 +59,18 @@ public class QuickNav { String nbtString = "{id:\"minecraft:" + itemData.itemName.toLowerCase(Locale.ROOT) + "\",Count:1"; if (itemData.nbt.length() > 2) nbtString += "," + itemData.nbt; nbtString += "}"; + boolean uiTitleMatches = false; + try { + uiTitleMatches = screenTitle.matches(buttonInfo.uiTitle); + } catch (PatternSyntaxException e) { + e.printStackTrace(); + ClientPlayerEntity player = MinecraftClient.getInstance().player; + if (player != null) { + player.sendMessage(Text.of(Formatting.RED + "[Skyblocker] Invalid regex in quicknav button " + (id + 1) + "!"), false); + } + } return new QuickNavButton(id, - screenTitle.matches(buttonInfo.uiTitle), + uiTitleMatches, buttonInfo.clickEvent, ItemStack.fromNbt(StringNbtReader.parse(nbtString)) ); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java index d9e97976..e41ea768 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java @@ -2,8 +2,8 @@ package me.xmrvizzy.skyblocker.skyblock.quicknav; import com.mojang.blaze3d.systems.RenderSystem; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.mixin.accessor.HandledScreenAccessor; +import me.xmrvizzy.skyblocker.utils.scheduler.MessageScheduler; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; @@ -54,7 +54,7 @@ public class QuickNavButton extends ClickableWidget { public void onClick(double mouseX, double mouseY) { if (!this.toggled) { this.toggled = true; - SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown(command); + MessageScheduler.INSTANCE.sendMessageAfterCooldown(command); // TODO : add null check with log error } } @@ -102,6 +102,6 @@ public class QuickNavButton extends ClickableWidget { @Override protected void appendClickableNarrations(NarrationMessageBuilder builder) { // TODO Auto-generated method stub - + } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TheRift.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TheRift.java index 5ca89dcf..4b11fcb0 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TheRift.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TheRift.java @@ -1,7 +1,7 @@ package me.xmrvizzy.skyblocker.skyblock.rift; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; public class TheRift { @@ -13,9 +13,9 @@ public class TheRift { public static void init() { WorldRenderEvents.AFTER_TRANSLUCENT.register(MirrorverseWaypoints::render); WorldRenderEvents.AFTER_TRANSLUCENT.register(EffigyWaypoints::render); - SkyblockerMod.getInstance().scheduler.scheduleCyclic(EffigyWaypoints::updateEffigies, SkyblockerConfig.get().slayer.vampireSlayer.effigyUpdateFrequency); - SkyblockerMod.getInstance().scheduler.scheduleCyclic(TwinClawsIndicator::updateIce, SkyblockerConfig.get().slayer.vampireSlayer.holyIceUpdateFrequency); - SkyblockerMod.getInstance().scheduler.scheduleCyclic(ManiaIndicator::updateMania, SkyblockerConfig.get().slayer.vampireSlayer.maniaUpdateFrequency); - SkyblockerMod.getInstance().scheduler.scheduleCyclic(StakeIndicator::updateStake, SkyblockerConfig.get().slayer.vampireSlayer.steakStakeUpdateFrequency); + Scheduler.INSTANCE.scheduleCyclic(EffigyWaypoints::updateEffigies, SkyblockerConfig.get().slayer.vampireSlayer.effigyUpdateFrequency); + Scheduler.INSTANCE.scheduleCyclic(TwinClawsIndicator::updateIce, SkyblockerConfig.get().slayer.vampireSlayer.holyIceUpdateFrequency); + Scheduler.INSTANCE.scheduleCyclic(ManiaIndicator::updateMania, SkyblockerConfig.get().slayer.vampireSlayer.maniaUpdateFrequency); + Scheduler.INSTANCE.scheduleCyclic(StakeIndicator::updateStake, SkyblockerConfig.get().slayer.vampireSlayer.steakStakeUpdateFrequency); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TwinClawsIndicator.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TwinClawsIndicator.java index e141b6a8..6e6fad2d 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TwinClawsIndicator.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/rift/TwinClawsIndicator.java @@ -1,12 +1,12 @@ package me.xmrvizzy.skyblocker.skyblock.rift; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.SlayerUtils; import me.xmrvizzy.skyblocker.utils.Utils; import me.xmrvizzy.skyblocker.utils.render.RenderHelper; import me.xmrvizzy.skyblocker.utils.render.title.Title; import me.xmrvizzy.skyblocker.utils.render.title.TitleContainer; +import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.minecraft.entity.Entity; import net.minecraft.util.Formatting; @@ -29,7 +29,7 @@ public class TwinClawsIndicator { anyClaws = true; if (!TitleContainer.containsTitle(title) && !scheduled) { scheduled = true; - SkyblockerMod.getInstance().scheduler.schedule(() -> { + Scheduler.INSTANCE.schedule(() -> { RenderHelper.displayInTitleContainerAndPlaySound(title); scheduled = false; }, SkyblockerConfig.get().slayer.vampireSlayer.holyIceIndicatorTickDelay); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/shortcut/Shortcuts.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/shortcut/Shortcuts.java index 89329c6c..14bc1db4 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/shortcut/Shortcuts.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/shortcut/Shortcuts.java @@ -63,7 +63,7 @@ public class Shortcuts { LOGGER.info("[Skyblocker] Loaded {} command shortcuts and {} command argument shortcuts", commands.size(), commandArgs.size()); } catch (FileNotFoundException e) { registerDefaultShortcuts(); - LOGGER.warn("[Skyblocker] Shortcuts file not found, using default shortcuts. This is normal when using for the first time.", e); + LOGGER.warn("[Skyblocker] Shortcuts file not found, using default shortcuts. This is normal when using for the first time."); } catch (IOException e) { LOGGER.error("[Skyblocker] Failed to load shortcuts file", e); } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/special/SpecialEffects.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/special/SpecialEffects.java new file mode 100644 index 00000000..a3d7e5c5 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/special/SpecialEffects.java @@ -0,0 +1,96 @@ +package me.xmrvizzy.skyblocker.skyblock.special; + +import com.mojang.blaze3d.systems.RenderSystem; +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.StringNbtReader; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.text.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpecialEffects { + private static final Logger LOGGER = LoggerFactory.getLogger(SpecialEffects.class); + private static final Pattern DROP_PATTERN = Pattern.compile("(?:\\[[A-Z+]+] )?(?<player>[A-Za-z0-9_]+) unlocked (?<item>.+)!"); + private static final ItemStack NECRON_HANDLE = new ItemStack(Items.STICK); + private static final ItemStack SCROLL = new ItemStack(Items.WRITABLE_BOOK); + private static ItemStack TIER_5_SKULL; + private static ItemStack FIFTH_STAR; + + static { + NECRON_HANDLE.addEnchantment(Enchantments.PROTECTION, 1); + SCROLL.addEnchantment(Enchantments.PROTECTION, 1); + try { + TIER_5_SKULL = ItemStack.fromNbt(StringNbtReader.parse("{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;-1613868903,-527154034,-1445577520,748807544],Properties:{textures:[{Value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTEwZjlmMTA4NWQ0MDcxNDFlYjc3NjE3YTRhYmRhYWEwOGQ4YWYzM2I5NjAyMDBmZThjMTI2YzFkMTQ0NTY4MiJ9fX0=\"}]}}}}")); + FIFTH_STAR = ItemStack.fromNbt(StringNbtReader.parse("{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;1904417095,756174249,-1302927470,1407004198],Properties:{textures:[{Value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzFjODA0MjUyN2Y4MWM4ZTI5M2UyODEwMTEzNDg5ZjQzOTRjYzZlZmUxNWQxYWZhYzQzMTU3MWM3M2I2MmRjNCJ9fX0=\"}]}}}}")); + } catch (Exception e) { + TIER_5_SKULL = ItemStack.EMPTY; + FIFTH_STAR = ItemStack.EMPTY; + LOGGER.error("[Skyblocker Special Effects] Failed to parse NBT for a player head!", e); + } + } + + public static void init() { + ClientReceiveMessageEvents.GAME.register(SpecialEffects::displayRareDropEffect); + } + + private static void displayRareDropEffect(Text message, boolean overlay) { + //We don't check if we're in dungeons because that check doesn't work in m7 which defeats the point of this + //It might also allow it to work with Croesus + if (Utils.isOnSkyblock() && SkyblockerConfig.get().general.specialEffects.rareDungeonDropEffects) { + try { + String stringForm = message.getString(); + Matcher matcher = DROP_PATTERN.matcher(stringForm); + + if (matcher.matches()) { + MinecraftClient client = MinecraftClient.getInstance(); + String player = matcher.group("player"); + + if (player.equals(client.getSession().getUsername())) { + ItemStack stack = getStackFromName(matcher.group("item")); + + if (!stack.isEmpty()) { + if (RenderSystem.isOnRenderThread()) { + client.particleManager.addEmitter(client.player, ParticleTypes.PORTAL, 30); + client.gameRenderer.showFloatingItem(stack); + } else { + RenderSystem.recordRenderCall(() -> { + client.particleManager.addEmitter(client.player, ParticleTypes.PORTAL, 30); + client.gameRenderer.showFloatingItem(stack); + }); + } + } + } + } + } catch (Exception e) { //In case there's a regex failure or something else bad happens + LOGGER.error("[Skyblocker Special Effects] An unexpected exception was encountered: ", e); + } + } + } + + private static ItemStack getStackFromName(String itemName) { + return switch (itemName) { + //M7 + case "Necron Dye" -> new ItemStack(Items.ORANGE_DYE); + case "Dark Claymore" -> new ItemStack(Items.STONE_SWORD); + case "Necron's Handle", "Shiny Necron's Handle" -> NECRON_HANDLE; + case "Enchanted Book (Thunderlord VII)" -> new ItemStack(Items.ENCHANTED_BOOK); + case "Master Skull - Tier 5" -> TIER_5_SKULL; + case "Shadow Warp", "Wither Shield", "Implosion" -> SCROLL; + case "Fifth Master Star" -> FIFTH_STAR; + + //M6 + case "Giant's Sword" -> new ItemStack(Items.IRON_SWORD); + + default -> ItemStack.EMPTY; + }; + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/spidersden/Relics.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/spidersden/Relics.java new file mode 100644 index 00000000..12ce0715 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/spidersden/Relics.java @@ -0,0 +1,170 @@ +package me.xmrvizzy.skyblocker.skyblock.spidersden; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.brigadier.CommandDispatcher; +import me.xmrvizzy.skyblocker.SkyblockerMod; +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.PosUtils; +import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.render.RenderHelper; +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.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class Relics { + private static final Logger LOGGER = LoggerFactory.getLogger(Relics.class); + private static CompletableFuture<Void> relicsLoaded; + @SuppressWarnings({"unused", "FieldCanBeLocal"}) + private static int totalRelics = 0; + private static final List<BlockPos> relics = new ArrayList<>(); + private static final Map<String, Set<BlockPos>> foundRelics = new HashMap<>(); + + public static void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(Relics::loadRelics); + ClientLifecycleEvents.CLIENT_STOPPING.register(Relics::saveFoundRelics); + ClientCommandRegistrationCallback.EVENT.register(Relics::registerCommands); + WorldRenderEvents.AFTER_TRANSLUCENT.register(Relics::render); + ClientReceiveMessageEvents.GAME.register(Relics::onChatMessage); + } + + private static void loadRelics(MinecraftClient client) { + relicsLoaded = CompletableFuture.runAsync(() -> { + try (BufferedReader reader = client.getResourceManager().openAsReader(new Identifier(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(); + } else if (json.getKey().equals("locations")) { + for (JsonElement locationJson : json.getValue().getAsJsonArray().asList()) { + JsonObject posData = locationJson.getAsJsonObject(); + relics.add(new BlockPos(posData.get("x").getAsInt(), posData.get("y").getAsInt(), posData.get("z").getAsInt())); + } + } + } + LOGGER.info("[Skyblocker] Loaded relics locations"); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to load relics locations", e); + } + + try (BufferedReader reader = new BufferedReader(new FileReader(SkyblockerMod.CONFIG_DIR.resolve("found_relics.json").toFile()))) { + for (Map.Entry<String, JsonElement> profileJson : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) { + Set<BlockPos> foundRelicsForProfile = new HashSet<>(); + for (JsonElement foundRelicsJson : profileJson.getValue().getAsJsonArray().asList()) { + foundRelicsForProfile.add(PosUtils.parsePosString(foundRelicsJson.getAsString())); + } + foundRelics.put(profileJson.getKey(), foundRelicsForProfile); + } + LOGGER.debug("[Skyblocker] Loaded found relics"); + } catch (FileNotFoundException ignored) { + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to load found relics", e); + } + }); + } + + private static void saveFoundRelics(MinecraftClient client) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(SkyblockerMod.CONFIG_DIR.resolve("found_relics.json").toFile()))) { + JsonObject json = new JsonObject(); + for (Map.Entry<String, Set<BlockPos>> foundRelicsForProfile : foundRelics.entrySet()) { + JsonArray foundRelicsJson = new JsonArray(); + for (BlockPos foundRelic : foundRelicsForProfile.getValue()) { + foundRelicsJson.add(PosUtils.getPosString(foundRelic)); + } + json.add(foundRelicsForProfile.getKey(), foundRelicsJson); + } + SkyblockerMod.GSON.toJson(json, writer); + LOGGER.debug("[Skyblocker] Saved found relics"); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to write found relics to file", e); + } + } + + private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(literal(SkyblockerMod.NAMESPACE) + .then(literal("relics") + .then(literal("markAllFound").executes(context -> { + Relics.markAllFound(); + context.getSource().sendFeedback(Text.translatable("skyblocker.relics.markAllFound")); + return 1; + })) + .then(literal("markAllMissing").executes(context -> { + Relics.markAllMissing(); + context.getSource().sendFeedback(Text.translatable("skyblocker.relics.markAllMissing")); + return 1; + })))); + } + + private static void render(WorldRenderContext context) { + SkyblockerConfig.Relics config = SkyblockerConfig.get().locations.spidersDen.relics; + + if (config.enableRelicsHelper && relicsLoaded.isDone() && Utils.getLocationRaw().equals("combat_1")) { + for (BlockPos fairySoulPos : relics) { + boolean isRelicMissing = isRelicMissing(fairySoulPos); + if (!isRelicMissing && !config.highlightFoundRelics) continue; + float[] colorComponents = isRelicMissing ? DyeColor.YELLOW.getColorComponents() : DyeColor.BROWN.getColorComponents(); + RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, fairySoulPos, colorComponents, 0.5F); + } + } + } + + private static void onChatMessage(Text text, boolean overlay) { + String message = text.getString(); + if (message.equals("You've already found this relic!") || message.startsWith("+10,000 Coins! (") && message.endsWith("/28 Relics)")) { + markClosestRelicFound(); + } + } + + private static void markClosestRelicFound() { + if (!relicsLoaded.isDone()) return; + PlayerEntity player = MinecraftClient.getInstance().player; + if (player == null) { + LOGGER.warn("[Skyblocker] Failed to mark closest relic as found because player is null"); + return; + } + relics.stream() + .filter(Relics::isRelicMissing) + .min(Comparator.comparingDouble(relicPos -> relicPos.getSquaredDistance(player.getPos()))) + .filter(relicPos -> relicPos.getSquaredDistance(player.getPos()) <= 16) + .ifPresent(relicPos -> { + foundRelics.computeIfAbsent(Utils.getProfile(), profileKey -> new HashSet<>()); + foundRelics.get(Utils.getProfile()).add(relicPos); + }); + } + + private static boolean isRelicMissing(BlockPos relicPos) { + Set<BlockPos> foundRelicsForProfile = foundRelics.get(Utils.getProfile()); + return foundRelicsForProfile == null || !foundRelicsForProfile.contains(relicPos); + } + + private static void markAllFound() { + foundRelics.computeIfAbsent(Utils.getProfile(), profileKey -> new HashSet<>()); + foundRelics.get(Utils.getProfile()).addAll(relics); + } + + private static void markAllMissing() { + Set<BlockPos> foundRelicsForProfile = foundRelics.get(Utils.getProfile()); + if (foundRelicsForProfile != null) { + foundRelicsForProfile.clear(); + } + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java index 084a6ffd..f02c7dc6 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java @@ -99,8 +99,6 @@ public class ScreenBuilder { } // reflect something together for the "normal" ones. - // TODO don't get package list for every widget; do it once and cache. - // fine for now, as this would only shorten the load time anyways // list all packages that might contain widget classes // using Package isn't reliable, as some classes might not be loaded yet, diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java index 5eb575c3..078927ef 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java @@ -94,7 +94,6 @@ public class StackStage extends PipelineStage { } public void stackWidgetsHoriz(int screenW) { - // TODO not centered (?) int compWidth = -ScreenConst.WIDGET_PAD; for (Widget wid : primary) { compWidth += wid.getWidth() + ScreenConst.WIDGET_PAD; diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java index b7f58763..1d056e0c 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java @@ -10,7 +10,6 @@ import java.util.Arrays; import java.util.Comparator; // this widget shows a list of obtained dungeon buffs -// TODO: could be more pretty, can't be arsed atm public class DungeonBuffWidget extends Widget { diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java index 2e8e2c40..afa883be 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java @@ -60,7 +60,7 @@ public class IslandOwnersWidget extends Widget { PlainTextComponent ptc = new PlainTextComponent(entry); this.addComponent(ptc); } - + } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java index 1be6adda..9f60ed34 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java @@ -19,7 +19,7 @@ public class PlayerComponent extends Component { private final Identifier tex; public PlayerComponent(PlayerListEntry ple) { - + boolean plainNames = SkyblockerConfig.get().general.tabHud.plainPlayerNames; Team team = ple.getScoreboardTeam(); String username = ple.getProfile().getName(); diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Constants.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Constants.java new file mode 100644 index 00000000..aef55687 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Constants.java @@ -0,0 +1,8 @@ +package me.xmrvizzy.skyblocker.utils; + +/** + * Holds generic static constants + */ +public interface Constants { + String LEVEL_EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u26C2\u12DE"; +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Http.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Http.java new file mode 100644 index 00000000..3461189c --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Http.java @@ -0,0 +1,89 @@ +package me.xmrvizzy.skyblocker.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import me.xmrvizzy.skyblocker.SkyblockerMod; +import net.minecraft.SharedConstants; + +/** + * @implNote All http requests are sent using HTTP 2 + */ +public class Http { + private static final String USER_AGENT = "Skyblocker/" + SkyblockerMod.VERSION + " (" + SharedConstants.getGameVersion().getName() + ")"; + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .build(); + + public static String sendGetRequest(String url) throws IOException, InterruptedException { + HttpRequest request = 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(); + + HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream()); + InputStream decodedInputStream = getDecodedInputStream(response); + String body = new String(decodedInputStream.readAllBytes()); + + return body; + } + + public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .method("HEAD", BodyPublishers.noBody()) + .header("User-Agent", USER_AGENT) + .version(Version.HTTP_2) + .uri(URI.create(url)) + .build(); + + HttpResponse<Void> response = HTTP_CLIENT.send(request, BodyHandlers.discarding()); + return response.headers(); + } + + private static InputStream getDecodedInputStream(HttpResponse<InputStream> response) { + String encoding = getContentEncoding(response); + + try { + switch (encoding) { + case "": + return response.body(); + case "gzip": + return new GZIPInputStream(response.body()); + case "deflate": + return new InflaterInputStream(response.body()); + default: + throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static String getContentEncoding(HttpResponse<InputStream> response) { + return response.headers().firstValue("Content-Encoding").orElse(""); + } + + public static String getEtag(HttpHeaders headers) { + return headers.firstValue("Etag").orElse(""); + } + + public static String getLastModified(HttpHeaders headers) { + return headers.firstValue("Last-Modified").orElse(""); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/PosUtils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/PosUtils.java new file mode 100644 index 00000000..4f32292c --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/PosUtils.java @@ -0,0 +1,14 @@ +package me.xmrvizzy.skyblocker.utils; + +import net.minecraft.util.math.BlockPos; + +public final class PosUtils { + public static BlockPos parsePosString(String posData) { + String[] posArray = posData.split(","); + return new BlockPos(Integer.parseInt(posArray[0]), Integer.parseInt(posArray[1]), Integer.parseInt(posArray[2])); + } + + public static String getPosString(BlockPos blockPos) { + return blockPos.getX() + "," + blockPos.getY() + "," + blockPos.getZ(); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java index 839e0dae..755e191d 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java @@ -2,13 +2,14 @@ package me.xmrvizzy.skyblocker.utils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip; import me.xmrvizzy.skyblocker.skyblock.rift.TheRift; +import me.xmrvizzy.skyblocker.utils.scheduler.MessageScheduler; 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; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; @@ -19,6 +20,8 @@ import net.minecraft.scoreboard.ScoreboardPlayerScore; import net.minecraft.scoreboard.Team; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; @@ -28,6 +31,7 @@ import java.util.List; * Utility variables and methods for retrieving Skyblock related information. */ public class Utils { + private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); private static final String ALTERNATE_HYPIXEL_ADDRESS = System.getProperty("skyblocker.alternateHypixelAddress", ""); private static final String PROFILE_PREFIX = "Profile: "; private static boolean isOnHypixel = false; @@ -45,7 +49,7 @@ public class Utils { private static String map = ""; private static long clientWorldJoinTime = 0; private static boolean sentLocRaw = false; - private static long lastLocRaw = 0; + private static boolean canSendLocRaw = false; public static boolean isOnHypixel() { return isOnHypixel; @@ -124,19 +128,25 @@ public class Utils { public static void updateFromScoreboard(MinecraftClient client) { List<String> sidebar; - if (client.world == null || client.isInSingleplayer() || (sidebar = getSidebar()) == null) { - isOnSkyblock = false; - isInDungeons = false; - return; + FabricLoader fabricLoader = FabricLoader.getInstance(); + if ((client.world == null || client.isInSingleplayer() || (sidebar = getSidebar()) == null)) { + if (fabricLoader.isDevelopmentEnvironment()) { + sidebar = Collections.emptyList(); + } else { + isOnSkyblock = false; + isInDungeons = false; + return; + } } + + if (sidebar.isEmpty() && !fabricLoader.isDevelopmentEnvironment()) return; String string = sidebar.toString(); - if (sidebar.isEmpty()) return; - if (isConnectedToHypixel(client)) { + if (fabricLoader.isDevelopmentEnvironment() || isConnectedToHypixel(client)) { if (!isOnHypixel) { isOnHypixel = true; } - if (sidebar.get(0).contains("SKYBLOCK") || sidebar.get(0).contains("SKIBLOCK")) { + if (fabricLoader.isDevelopmentEnvironment() || sidebar.get(0).contains("SKYBLOCK") || sidebar.get(0).contains("SKIBLOCK")) { if (!isOnSkyblock) { if (!isInjected) { isInjected = true; @@ -148,19 +158,18 @@ public class Utils { } else { leaveSkyblock(); } - isInDungeons = isOnSkyblock && string.contains("The Catacombs"); + isInDungeons = fabricLoader.isDevelopmentEnvironment() || isOnSkyblock && string.contains("The Catacombs"); } else if (isOnHypixel) { isOnHypixel = false; leaveSkyblock(); } } - + private static boolean isConnectedToHypixel(MinecraftClient client) { - String serverAddress = (client.getCurrentServerEntry() != null) ? client.getCurrentServerEntry().address.toLowerCase() : ""; - String serverBrand = (client.player != null && client.player.getServerBrand() != null) ? client.player.getServerBrand() : ""; - boolean isOnHypixel = (serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord")); - - return isOnHypixel; + String serverAddress = (client.getCurrentServerEntry() != null) ? client.getCurrentServerEntry().address.toLowerCase() : ""; + String serverBrand = (client.player != null && client.player.getServerBrand() != null) ? client.player.getServerBrand() : ""; + + return serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord"); } private static void leaveSkyblock() { @@ -184,7 +193,7 @@ public class Utils { location = location.strip(); } } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); + LOGGER.error("[Skyblocker] Failed to get location from sidebar", e); } return location; } @@ -206,7 +215,7 @@ public class Utils { else purse = 0; } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); + LOGGER.error("[Skyblocker] Failed to get purse from sidebar", e); } return purse; } @@ -225,12 +234,11 @@ public class Utils { bits = Integer.parseInt(bitsString.replaceAll("[^0-9.]", "").strip()); } } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); + LOGGER.error("[Skyblocker] Failed to get bits from sidebar", e); } return bits; } - public static List<String> getSidebar() { try { ClientPlayerEntity client = MinecraftClient.getInstance().player; @@ -242,7 +250,7 @@ public class Utils { Team team = scoreboard.getPlayerTeam(score.getPlayerName()); if (team != null) { String line = team.getPrefix().getString() + team.getSuffix().getString(); - if (line.trim().length() > 0) { + if (!line.trim().isEmpty()) { String formatted = Formatting.strip(line); lines.add(formatted); } @@ -285,10 +293,10 @@ public class Utils { private static void updateLocRaw() { if (isOnSkyblock) { long currentTime = System.currentTimeMillis(); - if (!sentLocRaw && currentTime > clientWorldJoinTime + 1000 && currentTime > lastLocRaw + 15000) { - SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown("/locraw"); + if (!sentLocRaw && canSendLocRaw && currentTime > clientWorldJoinTime + 1000) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/locraw"); sentLocRaw = true; - lastLocRaw = currentTime; + canSendLocRaw = false; } } else { resetLocRawInfo(); @@ -315,7 +323,11 @@ public class Utils { if (locRaw.has("map")) { map = locRaw.get("map").getAsString(); } - return !sentLocRaw; + + boolean shouldFilter = !sentLocRaw; + sentLocRaw = false; + + return shouldFilter; } } return true; @@ -323,6 +335,7 @@ public class Utils { private static void resetLocRawInfo() { sentLocRaw = false; + canSendLocRaw = true; server = ""; gameType = ""; locationRaw = ""; diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/render/RenderHelper.java b/src/main/java/me/xmrvizzy/skyblocker/utils/render/RenderHelper.java index aaf92d68..907896e2 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/render/RenderHelper.java @@ -95,7 +95,7 @@ public class RenderHelper { matrices.translate(-camera.getX(), -camera.getY(), -camera.getZ()); buffer.begin(DrawMode.LINES, VertexFormats.LINES); - WorldRenderer.drawBox(matrices, buffer, box, colorComponents[0] * 255f, colorComponents[1] * 255f, colorComponents[2] * 255f, 1f); + WorldRenderer.drawBox(matrices, buffer, box, colorComponents[0], colorComponents[1], colorComponents[2], 1f); tessellator.draw(); matrices.pop(); diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/render/title/TitleContainer.java b/src/main/java/me/xmrvizzy/skyblocker/utils/render/title/TitleContainer.java index 6e15c871..2555572c 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/render/title/TitleContainer.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/render/title/TitleContainer.java @@ -1,6 +1,5 @@ package me.xmrvizzy.skyblocker.utils.render.title; -import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; @@ -66,7 +65,7 @@ public class TitleContainer { */ public static boolean addTitle(Title title, int ticks) { if (addTitle(title)) { - SkyblockerMod.getInstance().scheduler.schedule(() -> TitleContainer.removeTitle(title), ticks); + Scheduler.INSTANCE.schedule(() -> TitleContainer.removeTitle(title), ticks); return true; } return false; diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/MessageScheduler.java b/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/MessageScheduler.java index bde29c13..b8ffa548 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/MessageScheduler.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/MessageScheduler.java @@ -3,19 +3,22 @@ package me.xmrvizzy.skyblocker.utils.scheduler; import net.minecraft.client.MinecraftClient; /** - * A scheduler for sending chat messages or commands. Use the instance in {@link me.xmrvizzy.skyblocker.SkyblockerMod#messageScheduler SkyblockerMod.messageScheduler}. Do not instantiate this class. + * A scheduler for sending chat messages or commands. Use the instance in {@link #INSTANCE}. Do not instantiate this class. */ -@SuppressWarnings("deprecation") public class MessageScheduler extends Scheduler { /** * The minimum delay that the server will accept between chat messages. */ private static final int MIN_DELAY = 200; + public static final MessageScheduler INSTANCE = new MessageScheduler(); /** * The timestamp of the last message send, */ private long lastMessage = 0; + protected MessageScheduler() { + } + /** * Sends a chat message or command after the minimum cooldown. Prefer this method to send messages or commands to the server. * diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/Scheduler.java b/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/Scheduler.java index 76112e0d..700bdce3 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/Scheduler.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/scheduler/Scheduler.java @@ -1,30 +1,28 @@ package me.xmrvizzy.skyblocker.utils.scheduler; import com.mojang.brigadier.Command; -import me.xmrvizzy.skyblocker.SkyblockerMod; +import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.PriorityQueue; +import java.util.ArrayList; +import java.util.List; import java.util.function.Supplier; /** - * A scheduler for running tasks at a later time. Tasks will be run synchronously on the main client thread. Use the instance stored in {@link SkyblockerMod#scheduler}. Do not instantiate this class. + * A scheduler for running tasks at a later time. Tasks will be run synchronously on the main client thread. Use the instance stored in {@link #INSTANCE}. Do not instantiate this class. */ public class Scheduler { private static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class); + public static final Scheduler INSTANCE = new Scheduler(); private int currentTick = 0; - private final PriorityQueue<ScheduledTask> tasks = new PriorityQueue<>(); + private final AbstractInt2ObjectMap<List<ScheduledTask>> tasks = new Int2ObjectOpenHashMap<>(); - /** - * Do not instantiate this class. Use {@link SkyblockerMod#scheduler} instead. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - public Scheduler() { + protected Scheduler() { } /** @@ -34,11 +32,11 @@ public class Scheduler { * @param delay the delay in ticks */ public void schedule(Runnable task, int delay) { - if (delay < 0) { + if (delay >= 0) { + addTask(new ScheduledTask(task), currentTick + delay); + } else { LOGGER.warn("Scheduled a task with negative delay"); } - ScheduledTask tmp = new ScheduledTask(task, currentTick + delay); - tasks.add(tmp); } /** @@ -48,15 +46,15 @@ public class Scheduler { * @param period the period in ticks */ public void scheduleCyclic(Runnable task, int period) { - if (period <= 0) { - LOGGER.error("Attempted to schedule a cyclic task with period lower than 1"); + if (period > 0) { + addTask(new CyclicTask(task, period), currentTick); } else { - new CyclicTask(task, period).run(); + LOGGER.error("Attempted to schedule a cyclic task with period lower than 1"); } } public static Command<FabricClientCommandSource> queueOpenScreenCommand(Supplier<Screen> screenSupplier) { - return context -> SkyblockerMod.getInstance().scheduler.queueOpenScreen(screenSupplier); + return context -> INSTANCE.queueOpenScreen(screenSupplier); } /** @@ -71,11 +69,18 @@ public class Scheduler { } public void tick() { - currentTick += 1; - ScheduledTask task; - while ((task = tasks.peek()) != null && task.schedule <= currentTick && runTask(task)) { - tasks.poll(); + if (tasks.containsKey(currentTick)) { + List<ScheduledTask> currentTickTasks = tasks.get(currentTick); + //noinspection ForLoopReplaceableByForEach (or else we get a ConcurrentModificationException) + for (int i = 0; i < currentTickTasks.size(); i++) { + ScheduledTask task = currentTickTasks.get(i); + if (!runTask(task)) { + tasks.computeIfAbsent(currentTick + 1, key -> new ArrayList<>()).add(task); + } + } + tasks.remove(currentTick); } + currentTick += 1; } /** @@ -89,30 +94,42 @@ public class Scheduler { return true; } + private void addTask(ScheduledTask scheduledTask, int schedule) { + if (tasks.containsKey(schedule)) { + tasks.get(schedule).add(scheduledTask); + } else { + List<ScheduledTask> list = new ArrayList<>(); + list.add(scheduledTask); + tasks.put(schedule, list); + } + } + /** * A task that runs every period ticks. More specifically, this task reschedules itself to run again after period ticks every time it runs. - * - * @param inner the task to run - * @param period the period in ticks */ - protected record CyclicTask(Runnable inner, int period) implements Runnable { + protected class CyclicTask extends ScheduledTask { + private final int period; + + CyclicTask(Runnable inner, int period) { + super(inner); + this.period = period; + } + @Override public void run() { - SkyblockerMod.getInstance().scheduler.schedule(this, period); - inner.run(); + super.run(); + addTask(this, currentTick + period); } } /** * A task that runs at a specific tick, relative to {@link #currentTick}. - * - * @param inner the task to run - * @param schedule the tick to run at */ - protected record ScheduledTask(Runnable inner, int schedule) implements Comparable<ScheduledTask>, Runnable { - @Override - public int compareTo(ScheduledTask o) { - return schedule - o.schedule; + protected static class ScheduledTask implements Runnable { + private final Runnable inner; + + public ScheduledTask(Runnable inner) { + this.inner = inner; } @Override |