diff options
Diffstat (limited to 'src')
24 files changed, 592 insertions, 117 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 1f766000..f1eb4321 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -186,6 +186,7 @@ public class SkyblockerMod implements ClientModInitializer { DojoManager.init(); RenderHelper.init(); FancyStatusBars.init(); + SkyblockInventoryScreen.initEquipment(); EventNotifications.init(); containerSolverManager.init(); statusBarTracker.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java index bf6eefd5..96bb226d 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.SkyblockerScreen; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; import de.hysky.skyblocker.skyblock.shortcut.ShortcutsConfigScreen; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; @@ -163,6 +164,14 @@ public class GeneralCategory { newValue -> config.general.itemTooltip.enableBazaarPrice = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.<GeneralConfig.Craft>createBuilder() + .name(Text.translatable("skyblocker.config.general.itemTooltip.craft")) + .binding(defaults.general.itemTooltip.enableCraftingCost, + () -> config.general.itemTooltip.enableCraftingCost, + newValue -> config.general.itemTooltip.enableCraftingCost = newValue) + .listener((Option<GeneralConfig.Craft> ignored, GeneralConfig.Craft ignored2) -> CraftPriceTooltip.clearCache()) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) .option(Option.<Boolean>createBuilder() .name(Text.translatable("skyblocker.config.general.itemTooltip.enableObtainedDate")) .binding(defaults.general.itemTooltip.enableObtainedDate, diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index a2a0f815..889b253a 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -68,6 +68,13 @@ public class UIAndVisualsCategory { newValue -> config.uiAndVisuals.hideStatusEffectOverlay = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.showEquipmentInInventory")) + .binding(defaults.uiAndVisuals.showEquipmentInInventory, + () -> config.uiAndVisuals.showEquipmentInInventory, + newValue -> config.uiAndVisuals.showEquipmentInInventory = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) //Chest Value FIXME change dropdown to color controller .group(OptionGroup.createBuilder() diff --git a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java index 9f612028..754e15f1 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java @@ -115,6 +115,9 @@ public class GeneralConfig { public boolean enableBazaarPrice = true; @SerialEntry + public Craft enableCraftingCost = Craft.OFF; + + @SerialEntry public boolean enableObtainedDate = true; @SerialEntry @@ -139,6 +142,23 @@ public class GeneralConfig { } } + public enum Craft { + SELL_ORDER, BUY_ORDER, OFF; + + @Override + public String toString() { + return I18n.translate("skyblocker.config.general.itemTooltip.craft." + name()); + } + + public String getOrder() { + return switch (this) { + case SELL_ORDER -> "sellPrice"; + case BUY_ORDER -> "buyPrice"; + case OFF -> null; + }; + } + } + public static class ItemInfoDisplay { @SerialEntry public boolean slotText = true; diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index e016988b..80bdb1c9 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -28,6 +28,9 @@ public class UIAndVisualsConfig { public boolean hideStatusEffectOverlay = false; @SerialEntry + public boolean showEquipmentInInventory = true; + + @SerialEntry public ChestValue chestValue = new ChestValue(); @SerialEntry diff --git a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java index c268103d..93426143 100644 --- a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java +++ b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java @@ -26,6 +26,16 @@ public final class SkyblockEvents { } }); + /** + * Called when the player's Skyblock profile changes. + * @implNote This is called upon receiving the chat message for the profile change rather than the exact moment of profile change, so it may be delayed by a few seconds. + */ + public static final Event<ProfileChange> PROFILE_CHANGE = EventFactory.createArrayBacked(ProfileChange.class, callbacks -> (prev, profile) -> { + for (ProfileChange callback : callbacks) { + callback.onSkyblockProfileChange(prev, profile); + } + }); + @Environment(EnvType.CLIENT) @FunctionalInterface public interface SkyblockJoin { @@ -43,4 +53,10 @@ public final class SkyblockEvents { public interface SkyblockLocationChange { void onSkyblockLocationChange(Location location); } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface ProfileChange { + void onSkyblockProfileChange(String prevProfileId, String profileId); + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java index f75af09a..3c3dbd52 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java @@ -2,7 +2,11 @@ package de.hysky.skyblocker.mixins; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; +import de.hysky.skyblocker.skyblock.item.SkyblockInventoryScreen; +import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.item.ItemStack; import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.ScreenHandler; @@ -22,8 +26,21 @@ public abstract class GenericContainerScreenHandlerMixin extends ScreenHandler { public void setStackInSlot(int slot, int revision, ItemStack stack) { super.setStackInSlot(slot, revision, stack); SkyblockerMod.getInstance().containerSolverManager.markDirty(); - if (MinecraftClient.getInstance().currentScreen instanceof PartyFinderScreen screen) { - screen.markDirty(); + + Screen currentScreen = MinecraftClient.getInstance().currentScreen; + switch (currentScreen) { + case PartyFinderScreen screen -> screen.markDirty(); + case GenericContainerScreen screen when screen.getTitle().getString().toLowerCase().contains("equipment") -> { + int line = slot/9; + if (line > 0 && line < 5 && slot % 9 == 1) { + boolean empty = stack.getName().getString().trim().toLowerCase().startsWith("empty"); + if (Utils.isInTheRift()) + SkyblockInventoryScreen.equipment_rift[line - 1] = empty ? ItemStack.EMPTY : stack; + else + SkyblockInventoryScreen.equipment[line - 1] = empty ? ItemStack.EMPTY : stack; + } + } + case null, default -> {} } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java index 0d833c22..2194e7a8 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java @@ -1,11 +1,16 @@ package de.hysky.skyblocker.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.itemlist.ItemListWidget; import de.hysky.skyblocker.utils.Utils; +import net.minecraft.client.gui.screen.ButtonTextures; import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TexturedButtonWidget; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -15,4 +20,16 @@ public abstract class InventoryScreenMixin { private RecipeBookWidget skyblocker$replaceRecipeBook(RecipeBookWidget original) { return SkyblockerConfigManager.get().general.itemList.enableItemList && Utils.isOnSkyblock() ? new ItemListWidget() : original; } + + @WrapOperation(method = "init", at = @At(value = "NEW", target = "(IIIILnet/minecraft/client/gui/screen/ButtonTextures;Lnet/minecraft/client/gui/widget/ButtonWidget$PressAction;)Lnet/minecraft/client/gui/widget/TexturedButtonWidget;")) + private TexturedButtonWidget skyblocker$moveButton(int x, int y, int width, int height, ButtonTextures textures, ButtonWidget.PressAction pressAction, Operation<TexturedButtonWidget> original) { + if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) return original.call(x, y, width, height, textures, pressAction); + return new TexturedButtonWidget(x + 21, y, width, height, textures, pressAction); + } + + @WrapOperation(method = "method_19891", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ButtonWidget;setPosition(II)V")) + private void skyblocker$moveButtonWhenPressed(ButtonWidget instance, int i, int j, Operation<Void> original) { + if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) original.call(instance, i, j); + else instance.setPosition(i + 21, j); + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java index b04f958f..f91ddc86 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java @@ -1,7 +1,11 @@ package de.hysky.skyblocker.mixins; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.item.HotbarSlotLock; import de.hysky.skyblocker.skyblock.item.ItemProtection; +import de.hysky.skyblocker.skyblock.item.SkyblockInventoryScreen; import de.hysky.skyblocker.utils.JoinWorldPlaceholderScreen; import de.hysky.skyblocker.utils.ReconfiguringPlaceholderScreen; import de.hysky.skyblocker.utils.Utils; @@ -9,8 +13,10 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.DownloadingTerrainScreen; import net.minecraft.client.gui.screen.ReconfiguringScreen; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerEntity; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -54,4 +60,10 @@ public abstract class MinecraftClientMixin { private Screen modifyJoinWorld(Screen screen) { return Utils.isOnSkyblock() ? new JoinWorldPlaceholderScreen() : screen; } + + @WrapOperation(method = "handleInputEvents", at = @At(value = "NEW", target = "(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/client/gui/screen/ingame/InventoryScreen;")) + private InventoryScreen skyblocker$skyblockInventoryScreen(PlayerEntity player, Operation<InventoryScreen> original) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) return new SkyblockInventoryScreen(player); + else return original.call(player); + } }
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java index 42883c4f..620da37c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java @@ -24,7 +24,6 @@ import net.minecraft.text.ClickEvent; import net.minecraft.text.HoverEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import org.apache.commons.lang3.mutable.MutableObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +33,7 @@ import java.util.regex.Pattern; public class EggFinder { private static final Pattern eggFoundPattern = Pattern.compile("^(?:HOPPITY'S HUNT You found a Chocolate|You have already collected this Chocolate) (Breakfast|Lunch|Dinner)"); + private static final Pattern newEggPattern = Pattern.compile("^HOPPITY'S HUNT A Chocolate (Breakfast|Lunch|Dinner) Egg has appeared!$"); private static final Logger logger = LoggerFactory.getLogger("Skyblocker Egg Finder"); private static final LinkedList<ArmorStandEntity> armorStandQueue = new LinkedList<>(); private static final Location[] possibleLocations = {Location.CRIMSON_ISLE, Location.CRYSTAL_HOLLOWS, Location.DUNGEON_HUB, Location.DWARVEN_MINES, Location.HUB, Location.THE_END, Location.THE_PARK, Location.GOLD_MINE, Location.DEEP_CAVERNS, Location.SPIDERS_DEN, Location.THE_FARMING_ISLAND}; @@ -96,7 +96,7 @@ public class EggFinder { for (ItemStack itemStack : armorStand.getArmorItems()) { ItemUtils.getHeadTextureOptional(itemStack).ifPresent(texture -> { for (EggType type : EggType.entries) { //Compare blockPos rather than entity to avoid incorrect matches when the entity just moves rather than a new one being spawned elsewhere - if (texture.equals(type.texture) && (type.egg.getValue() == null || !type.egg.getValue().entity.getBlockPos().equals(armorStand.getBlockPos()))) { + if (texture.equals(type.texture) && (type.egg == null || !type.egg.entity.getBlockPos().equals(armorStand.getBlockPos()))) { handleFoundEgg(armorStand, type); return; } @@ -109,27 +109,29 @@ public class EggFinder { if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; isLocationCorrect = false; for (EggType type : EggType.entries) { - type.egg.setValue(null); + type.egg = null; } } private static void handleFoundEgg(ArmorStandEntity entity, EggType eggType) { - eggType.egg.setValue(new Egg(entity, new Waypoint(entity.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(eggType.color)))); - - if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages) return; - MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get() - .append("Found a ") - .append(Text.literal("Chocolate " + eggType + " Egg") - .withColor(eggType.color)) - .append(" at " + entity.getBlockPos().up(2).toShortString() + "!") - .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + entity.getBlockX() + " " + (entity.getBlockY() + 2) + " " + entity.getBlockZ() + " " + eggType)) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN))))); + eggType.egg = new Egg(entity, new Waypoint(entity.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(eggType.color))); + + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages || System.currentTimeMillis() - eggType.messageLastSent < 1000) return; + eggType.messageLastSent = System.currentTimeMillis(); + MinecraftClient.getInstance().player.sendMessage( + Constants.PREFIX.get() + .append("Found a ") + .append(Text.literal("Chocolate " + eggType + " Egg") + .withColor(eggType.color)) + .append(" at " + entity.getBlockPos().up(2).toShortString() + "!") + .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + entity.getBlockX() + " " + (entity.getBlockY() + 2) + " " + entity.getBlockZ() + " " + eggType)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN))))); } private static void renderWaypoints(WorldRenderContext context) { if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; for (EggType type : EggType.entries) { - Egg egg = type.egg.getValue(); + Egg egg = type.egg; if (egg != null && egg.waypoint.shouldRender()) egg.waypoint.render(context); } } @@ -139,30 +141,47 @@ public class EggFinder { Matcher matcher = eggFoundPattern.matcher(text.getString()); if (matcher.find()) { try { - Egg egg = EggType.valueOf(matcher.group(1).toUpperCase()).egg.getValue(); + Egg egg = EggType.valueOf(matcher.group(1).toUpperCase()).egg; if (egg != null) egg.waypoint.setFound(); } catch (IllegalArgumentException e) { logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg found message. Tried to match against: " + matcher.group(0), e); } } + + matcher.usePattern(newEggPattern); + if (matcher.find()) { + try { + EggType.valueOf(matcher.group(1).toUpperCase()).egg = null; + } catch (IllegalArgumentException e) { + logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg spawn message. Tried to match against: " + matcher.group(0), e); + } + } } - record Egg(ArmorStandEntity entity, Waypoint waypoint) { } + record Egg(ArmorStandEntity entity, Waypoint waypoint) {} + @SuppressWarnings("DataFlowIssue") //Removes that pesky "unboxing of Integer might cause NPE" warning when we already know it's not null enum EggType { - LUNCH(new MutableObject<>(), Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"), - DINNER(new MutableObject<>(), Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"), - BREAKFAST(new MutableObject<>(), Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0"); + LUNCH(Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"), + DINNER(Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"), + BREAKFAST(Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0"); - public final MutableObject<Egg> egg; + private Egg egg = null; public final int color; public final String texture; + /* + When a new egg spawns in the player's range, the order of packets/messages goes like this: + set_equipment -> new egg message -> set_entity_data + We have to set the egg to null to prevent the highlight from staying where it was before the new egg spawned, + and doing so causes the found message to get sent twice. This is the reason for the existence of this field, so that we can not send the 2nd message. + This doesn't fix the field being set twice, but that's not an issue anyway. It'd be much harder to fix the highlight issue mentioned above if it wasn't being set twice. + */ + private long messageLastSent = 0; //This is to not create an array each time we iterate over the values public static final ObjectImmutableList<EggType> entries = ObjectImmutableList.of(BREAKFAST, LUNCH, DINNER); - EggType(MutableObject<Egg> egg, int color, String texture) { - this.egg = egg; + EggType(int color, String texture) { this.color = color; this.texture = texture; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java new file mode 100644 index 00000000..42a52a85 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java @@ -0,0 +1,194 @@ +package de.hysky.skyblocker.skyblock.item; + +import com.mojang.serialization.Codec; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.mixins.accessors.SlotAccessor; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringNbtReader; +import net.minecraft.nbt.visitor.StringNbtWriter; +import net.minecraft.screen.slot.Slot; +import net.minecraft.util.Identifier; +import org.apache.commons.lang3.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Opened here {@code de.hysky.skyblocker.mixins.MinecraftClientMixin#skyblocker$skyblockInventoryScreen} + * <br> + * Book button is moved here {@code de.hysky.skyblocker.mixins.InventoryScreenMixin#skyblocker} + */ +public class SkyblockInventoryScreen extends InventoryScreen { + private static final Logger LOGGER = LoggerFactory.getLogger("Equipment"); + private static final Supplier<ItemStack[]> EMPTY_EQUIPMENT = () -> new ItemStack[]{ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY}; + public static final ItemStack[] equipment = EMPTY_EQUIPMENT.get(); + public static final ItemStack[] equipment_rift = EMPTY_EQUIPMENT.get(); + private static final Codec<ItemStack[]> CODEC = ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.listOf(4, 8) // min size at 4 for backwards compat + .xmap(itemStacks -> itemStacks.toArray(ItemStack[]::new), List::of).fieldOf("items").codec(); + + private static final Identifier SLOT_TEXTURE = Identifier.ofVanilla("container/slot"); + private static final Identifier EMPTY_SLOT = Identifier.of(SkyblockerMod.NAMESPACE, "equipment/empty_icon"); + private static final Path FOLDER = SkyblockerMod.CONFIG_DIR.resolve("equipment"); + + private final Slot[] equipmentSlots = new Slot[4]; + + private static void save(String profileId) { + try { + Files.createDirectories(FOLDER); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to create folder for equipment!", e); + } + Path resolve = FOLDER.resolve(profileId + ".nbt"); + + try (BufferedWriter writer = Files.newBufferedWriter(resolve)) { + + writer.write(new StringNbtWriter().apply(CODEC.encodeStart(NbtOps.INSTANCE, ArrayUtils.addAll(equipment, equipment_rift)).getOrThrow())); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to save Equipment data", e); + } + } + + private static void load(String profileId) { + Path resolve = FOLDER.resolve(profileId + ".nbt"); + CompletableFuture.supplyAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(resolve)) { + return CODEC.parse(NbtOps.INSTANCE, StringNbtReader.parse(reader.lines().collect(Collectors.joining()))).getOrThrow(); + } catch (NoSuchFileException ignored) { + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to load Equipment data", e); + } + return EMPTY_EQUIPMENT.get(); + // Schedule on main thread to avoid any async weirdness + }).thenAccept(itemStacks -> MinecraftClient.getInstance().execute(() -> { + System.arraycopy(itemStacks, 0, equipment, 0, Math.min(itemStacks.length, 4)); + if (itemStacks.length <= 4) return; + System.arraycopy(itemStacks, 4, equipment_rift, 0, Math.clamp(itemStacks.length - 4, 0, 4)); + })); + } + + public static void initEquipment() { + + SkyblockEvents.PROFILE_CHANGE.register(((prevProfileId, profileId) -> { + if (!prevProfileId.isEmpty()) CompletableFuture.runAsync(() -> save(prevProfileId)).thenRun(() -> load(profileId)); + else load(profileId); + })); + + ClientLifecycleEvents.CLIENT_STOPPING.register(client1 -> { + String profileId = Utils.getProfileId(); + if (!profileId.isBlank()) { + CompletableFuture.runAsync(() -> save(profileId)); + } + }); + } + + public SkyblockInventoryScreen(PlayerEntity player) { + super(player); + SimpleInventory inventory = new SimpleInventory(Utils.isInTheRift() ? equipment_rift: equipment); + + Slot slot = handler.slots.get(45); + ((SlotAccessor) slot).setX(slot.x + 21); + for (int i = 0; i < 4; i++) { + equipmentSlots[i] = new EquipmentSlot(inventory, i, 77, 8 + i * 18); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (Slot equipmentSlot : equipmentSlots) { + if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, mouseX, mouseY)) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/equipment"); + return true; + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + + /** + * Draws the equipment slots in the foreground layer after vanilla slots are drawn + * in {@link net.minecraft.client.gui.screen.ingame.HandledScreen#render(DrawContext, int, int, float) HandledScreen#render(DrawContext, int, int, float)}. + */ + @Override + protected void drawForeground(DrawContext context, int mouseX, int mouseY) { + for (Slot equipmentSlot : equipmentSlots) { + drawSlot(context, equipmentSlot); + if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, mouseX, mouseY)) drawSlotHighlight(context, equipmentSlot.x, equipmentSlot.y, 0); + } + + super.drawForeground(context, mouseX, mouseY); + } + + @Override + protected void drawMouseoverTooltip(DrawContext context, int x, int y) { + super.drawMouseoverTooltip(context, x, y); + if (!handler.getCursorStack().isEmpty()) return; + for (Slot equipmentSlot : equipmentSlots) { + if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, x, y) && equipmentSlot.hasStack()) { + ItemStack itemStack = equipmentSlot.getStack(); + context.drawTooltip(this.textRenderer, this.getTooltipFromItem(itemStack), itemStack.getTooltipData(), x, y); + } + } + } + + @Override + public void removed() { + super.removed(); + // put the handler back how it was, the handler is the same while the player is alive/in the same world + Slot slot = handler.slots.get(45); + ((SlotAccessor) slot).setX(slot.x - 21); + } + + @Override + protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) { + super.drawBackground(context, delta, mouseX, mouseY); + for (int i = 0; i < 4; i++) { + context.drawGuiTexture(SLOT_TEXTURE, x + 76 + (i == 3 ? 21 : 0), y + 7 + i * 18, 18, 18); + } + } + + @Override + protected void drawSlot(DrawContext context, Slot slot) { + super.drawSlot(context, slot); + if (slot instanceof EquipmentSlot && !slot.hasStack()) { + context.drawGuiTexture(EMPTY_SLOT, slot.x, slot.y, 16, 16); + } + } + + private static class EquipmentSlot extends Slot { + + public EquipmentSlot(Inventory inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + @Override + public boolean canTakeItems(PlayerEntity playerEntity) { + return false; + } + + @Override + public boolean canInsert(ItemStack stack) { + return false; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java index 88d48fbf..3049cd3f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java @@ -2,11 +2,11 @@ package de.hysky.skyblocker.skyblock.item.slottext.adders; import de.hysky.skyblocker.skyblock.item.slottext.SlotText; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import org.apache.commons.lang3.math.NumberUtils; import org.jetbrains.annotations.NotNull; @@ -22,8 +22,8 @@ public class PetLevelAdder extends SlotTextAdder { ItemStack itemStack = slot.getStack(); if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of(); String level = CatacombsLevelAdder.getBracketedLevelFromName(itemStack); - if (!NumberUtils.isDigits(level)) return List.of(); - if ("100".equals(level) || "200".equals(level)) return List.of(); + if (!NumberUtils.isDigits(level) || "100".equals(level) || "200".equals(level)) return List.of(); + if (!ItemUtils.getItemId(itemStack).equals("PET")) return List.of(); return List.of(SlotText.topLeft(Text.literal(level).withColor(0xFFDDC1))); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 49d170b9..955ebc87 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.item.tooltip; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; @@ -129,6 +130,8 @@ public class ItemTooltip { LOGGER.error("Encountered unknown error while downloading tooltip data", e); return null; }); + + CraftPriceTooltip.clearCache(); }, 1200, true); } }
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java index d82b2682..92adf49d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -1,29 +1,23 @@ package de.hysky.skyblocker.skyblock.item.tooltip; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.stream.JsonReader; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Utils; +import org.jetbrains.annotations.Nullable; -import java.io.StringReader; import java.net.http.HttpHeaders; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Predicate; -import org.jetbrains.annotations.Nullable; - public enum TooltipInfoType implements Runnable { NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true), - BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false), - LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false), + BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false), + LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false), ONE_DAY_AVERAGE("https://hysky.de/api/auctions/lowestbins/average/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false), THREE_DAY_AVERAGE("https://hysky.de/api/auctions/lowestbins/average/3day.json", itemTooltip -> itemTooltip.enableAvgBIN || SkyblockerConfigManager.get().uiAndVisuals.searchOverlay.enableAuctionHouse, itemTooltip -> itemTooltip.enableAvgBIN, false), MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java index e3a2ef04..cb8efb0c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.item.tooltip; import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver; import de.hysky.skyblocker.skyblock.item.tooltip.adders.*; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; import de.hysky.skyblocker.utils.Utils; import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; @@ -27,12 +28,13 @@ public class TooltipManager { new BazaarPriceTooltip(2), new LBinTooltip(3), new AvgBinTooltip(4), - new DungeonQualityTooltip(5), - new MotesTooltip(6), - new ObtainedDateTooltip(7), - new MuseumTooltip(8), - new ColorTooltip(9), - new AccessoryTooltip(10), + new CraftPriceTooltip(5), + new DungeonQualityTooltip(6), + new MotesTooltip(7), + new ObtainedDateTooltip(8), + new MuseumTooltip(9), + new ColorTooltip(10), + new AccessoryTooltip(11), }; private static final ArrayList<TooltipAdder> currentScreenAdders = new ArrayList<>(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java new file mode 100644 index 00000000..f7af446e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java @@ -0,0 +1,115 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.NEURepoManager; +import io.github.moulberry.repo.data.NEUIngredient; +import io.github.moulberry.repo.data.NEUItem; +import io.github.moulberry.repo.data.NEURecipe; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CraftPriceTooltip extends TooltipAdder { + protected static final Logger LOGGER = LoggerFactory.getLogger(CraftPriceTooltip.class.getName()); + private static final Map<String, Double> cachedCraftCosts = new ConcurrentHashMap<>(); + private static final int MAX_RECURSION_DEPTH = 15; + + public CraftPriceTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSloFt, ItemStack stack, List<Text> lines) { + if (SkyblockerConfigManager.get().general.itemTooltip.enableCraftingCost == GeneralConfig.Craft.OFF) return; + + String internalID = stack.getSkyblockId(); + if (stack.getNeuName() == null || internalID == null) return; + + if (TooltipInfoType.LOWEST_BINS.getData() == null || TooltipInfoType.BAZAAR.getData() == null) { + ItemTooltip.nullWarning(); + return; + } + + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(internalID); + if (neuItem == null) return; + + List<NEURecipe> neuRecipes = neuItem.getRecipes(); + if (neuRecipes.isEmpty() || neuRecipes.getFirst() instanceof io.github.moulberry.repo.data.NEUKatUpgradeRecipe) return; + + try { + double totalCraftCost = getItemCost(neuRecipes.getFirst(), 0); + + if (totalCraftCost == 0) return; + + int amountInStack; + if (lines.get(1).getString().endsWith("Sack")) { + String line = lines.get(3).getSiblings().get(1).getString().replace(",", ""); + amountInStack = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount(); + } else amountInStack = stack.getCount(); + + neuRecipes.getFirst().getAllOutputs().stream().findFirst().ifPresent(outputIngredient -> + lines.add(Text.literal(String.format("%-20s", "Crafting Price:")).formatted(Formatting.GOLD) + .append(ItemTooltip.getCoinsMessage(totalCraftCost / outputIngredient.getAmount(), amountInStack)))); + + } catch (Exception e) { + LOGGER.error("[Skyblocker Craft Price] Error calculating craftprice tooltip for: " + internalID, e); + } + } + + private double getItemCost(NEURecipe recipe, int depth) { + if (depth >= MAX_RECURSION_DEPTH) return -1; + + double totalCraftCost = 0; + for (NEUIngredient input : recipe.getAllInputs()) { + String inputItemName = input.getItemId(); + double inputItemCount = input.getAmount(); + if (cachedCraftCosts.containsKey(inputItemName)) { + totalCraftCost += cachedCraftCosts.get(inputItemName) * inputItemCount; + continue; + } + + double itemCost = 0; + + if (TooltipInfoType.BAZAAR.getData().has(inputItemName)) { + itemCost = TooltipInfoType.BAZAAR.getData().getAsJsonObject(inputItemName).get(SkyblockerConfigManager.get().general.itemTooltip.enableCraftingCost.getOrder()).getAsDouble(); + } else if (TooltipInfoType.LOWEST_BINS.getData().has(inputItemName)) { + itemCost = TooltipInfoType.LOWEST_BINS.getData().get(inputItemName).getAsDouble(); + } + + if (itemCost > 0) { + cachedCraftCosts.put(inputItemName, itemCost); + } + + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(inputItemName); + if (neuItem != null) { + List<NEURecipe> neuRecipes = neuItem.getRecipes(); + if (!neuRecipes.isEmpty()) { + double craftCost = getItemCost(neuRecipes.getFirst(), depth + 1); + if (craftCost != -1) itemCost = Math.min(itemCost, craftCost); + cachedCraftCosts.put(inputItemName, itemCost); + } + } + + totalCraftCost += itemCost * inputItemCount; + } + return totalCraftCost; + } + + public static void clearCache() { + cachedCraftCosts.clear(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java index 672201d5..d556c9b2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java @@ -7,6 +7,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -20,9 +21,18 @@ public class NpcPriceTooltip extends TooltipAdder { public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { final String internalID = stack.getSkyblockId(); if (internalID != null && TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) { + int amount; + if (lines.get(1).getString().endsWith("Sack")) { + //The amount is in the 2nd sibling of the 3rd line of the lore. here V + //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]] + String line = lines.get(3).getSiblings().get(1).getString().replace(",", ""); + amount = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount(); + } else { + amount = stack.getCount(); + } lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) .formatted(Formatting.YELLOW) - .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), stack.getCount()))); + .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), amount))); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java index f8930882..11ec1b8d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java @@ -1,28 +1,5 @@ package de.hysky.skyblocker.skyblock.waypoint; -import static com.mojang.brigadier.arguments.StringArgumentType.getString; -import static com.mojang.brigadier.arguments.StringArgumentType.word; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Semaphore; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.slf4j.Logger; - import com.google.common.primitives.Floats; import com.google.gson.Gson; import com.google.gson.JsonParser; @@ -33,8 +10,8 @@ import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; - import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.item.CustomArmorDyeColors; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; @@ -59,6 +36,28 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import org.slf4j.Logger; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Semaphore; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class OrderedWaypoints { private static final Logger LOGGER = LogUtils.getLogger(); @@ -400,7 +399,7 @@ public class OrderedWaypoints { private int waypointIndex; OrderedWaypoint(BlockPos pos, float[] colorComponents) { - super(pos, Type.WAYPOINT, colorComponents); + super(pos, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType, colorComponents); } private BlockPos getPos() { diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java index 1adf75d3..051bd52e 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Http.java +++ b/src/main/java/de/hysky/skyblocker/utils/Http.java @@ -33,7 +33,7 @@ public class Http { .followRedirects(Redirect.NORMAL) .build(); - private static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException { + public static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException { HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .GET() .header("Accept", "application/json") @@ -66,9 +66,8 @@ public class Http { .build(); HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream()); - InputStream decodedInputStream = getDecodedInputStream(response); - return decodedInputStream; + return getDecodedInputStream(response); } public static String sendGetRequest(String url) throws IOException, InterruptedException { @@ -125,16 +124,12 @@ public class Http { String encoding = getContentEncoding(response.headers()); 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); - } + return switch (encoding) { + case "" -> response.body(); + case "gzip" -> new GZIPInputStream(response.body()); + case "deflate" -> new InflaterInputStream(response.body()); + default -> throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding); + }; } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index de7a0f9e..e43ce783 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -10,9 +10,9 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; -import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.longs.LongBooleanPair; @@ -62,8 +62,13 @@ public class ItemUtils { }); } + /** + * Gets the nbt in the custom data component of the item stack. + * @return The {@link DataComponentTypes#CUSTOM_DATA custom data} of the itemstack, + * or an empty {@link NbtCompound} if the itemstack is missing a custom data component + */ @SuppressWarnings("deprecation") - public static NbtCompound getCustomData(@NotNull ComponentHolder stack) { + public static @NotNull NbtCompound getCustomData(@NotNull ComponentHolder stack) { return stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).getNbt(); } @@ -73,7 +78,7 @@ public class ItemUtils { * @param stack the item stack to get the internal name from * @return an optional containing the internal name of the item stack */ - public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) { + public static @NotNull Optional<String> getItemIdOptional(@NotNull ItemStack stack) { NbtCompound customData = getCustomData(stack); return customData.contains(ID) ? Optional.of(customData.getString(ID)) : Optional.empty(); } @@ -84,7 +89,7 @@ public class ItemUtils { * @param stack the item stack to get the internal name from * @return the internal name of the item stack, or an empty string if the item stack is null or does not have an internal name */ - public static String getItemId(@NotNull ItemStack stack) { + public static @NotNull String getItemId(@NotNull ItemStack stack) { return getCustomData(stack).getString(ID); } @@ -94,7 +99,7 @@ public class ItemUtils { * @param stack the item stack to get the UUID from * @return an optional containing the UUID of the item stack */ - public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) { + public static @NotNull Optional<String> getItemUuidOptional(@NotNull ItemStack stack) { NbtCompound customData = getCustomData(stack); return customData.contains(UUID) ? Optional.of(customData.getString(UUID)) : Optional.empty(); } @@ -105,7 +110,7 @@ public class ItemUtils { * @param stack the item stack to get the UUID from * @return the UUID of the item stack, or an empty string if the item stack is null or does not have a UUID */ - public static String getItemUuid(@NotNull ComponentHolder stack) { + public static @NotNull String getItemUuid(@NotNull ComponentHolder stack) { return getCustomData(stack).getString(UUID); } @@ -115,7 +120,7 @@ public class ItemUtils { * @return An {@link LongBooleanPair} with the {@code left long} representing the item's price, * and the {@code right boolean} indicating if the price was based on complete data. */ - public static DoubleBooleanPair getItemPrice(@NotNull ItemStack stack) { + public static @NotNull DoubleBooleanPair getItemPrice(@NotNull ItemStack stack) { return getItemPrice(getItemId(stack)); } @@ -125,7 +130,7 @@ public class ItemUtils { * @return An {@link LongBooleanPair} with the {@code left long} representing the item's price, * and the {@code right boolean} indicating if the price was based on complete data. */ - public static DoubleBooleanPair getItemPrice(@Nullable String id) { + public static @NotNull DoubleBooleanPair getItemPrice(@Nullable String id) { JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData(); JsonObject lowestBinPrices = TooltipInfoType.LOWEST_BINS.getData(); @@ -168,15 +173,15 @@ public class ItemUtils { public static boolean hasCustomDurability(@NotNull ItemStack stack) { NbtCompound customData = getCustomData(stack); - return customData != null && (customData.contains("drill_fuel") || customData.getString(ID).equals("PICKONIMBUS")); + return !customData.isEmpty() && (customData.contains("drill_fuel") || customData.getString(ID).equals("PICKONIMBUS")); } @Nullable public static IntIntPair getDurability(@NotNull ItemStack stack) { NbtCompound customData = getCustomData(stack); - if (customData == null) return null; + if (customData.isEmpty()) return null; - // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level + // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level // TODO Cache the max durability and only update the current durability on inventory tick int pickonimbusDurability = customData.getInt("pickonimbus_durability"); @@ -218,15 +223,15 @@ public class ItemUtils { return null; } - public static List<Text> getLore(ItemStack item) { + public static @NotNull List<Text> getLore(ItemStack item) { return item.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines(); } - public static PropertyMap propertyMapWithTexture(String textureValue) { + public static @NotNull PropertyMap propertyMapWithTexture(String textureValue) { return Codecs.GAME_PROFILE_PROPERTY_MAP.parse(JsonOps.INSTANCE, JsonParser.parseString("[{\"name\":\"textures\",\"value\":\"" + textureValue + "\"}]")).getOrThrow(); } - public static String getHeadTexture(ItemStack stack) { + public static @NotNull String getHeadTexture(@NotNull ItemStack stack) { if (!stack.isOf(Items.PLAYER_HEAD) || !stack.contains(DataComponentTypes.PROFILE)) return ""; ProfileComponent profile = stack.get(DataComponentTypes.PROFILE); @@ -238,13 +243,13 @@ public class ItemUtils { .orElse(""); } - public static Optional<String> getHeadTextureOptional(ItemStack stack) { + public static @NotNull Optional<String> getHeadTextureOptional(ItemStack stack) { String texture = getHeadTexture(stack); if (texture.isBlank()) return Optional.empty(); return Optional.of(texture); } - public static ItemStack getSkyblockerStack() { + public static @NotNull ItemStack getSkyblockerStack() { try { ItemStack stack = new ItemStack(Items.PLAYER_HEAD); stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of("SkyblockerStack"), Optional.of(java.util.UUID.randomUUID()), propertyMapWithTexture("e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0="))); diff --git a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java index 045ecc4e..ec3effaf 100644 --- a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java +++ b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java @@ -21,10 +21,10 @@ public class SkyblockTime { public static void init() { updateTime(); //ScheduleCyclic already runs the task upon scheduling, so there's no need to call updateTime() here - Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 24), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50); + Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 20), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50); } - private static long getSkyblockMillis() { + public static long getSkyblockMillis() { return System.currentTimeMillis() - SKYBLOCK_EPOCH; } diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index dad7ec02..051110b2 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -3,7 +3,6 @@ package de.hysky.skyblocker.utils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.mojang.util.UndashedUuid; - import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; @@ -22,11 +21,11 @@ import net.minecraft.scoreboard.*; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.apache.http.client.HttpResponseException; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.time.Instant; import java.util.Collections; import java.util.List; @@ -77,7 +76,8 @@ public class Utils { private static boolean canSendLocRaw = false; //This is required to prevent the location change event from being fired twice. private static boolean locationChanged = true; - + private static boolean mayorTickScheduled = false; + private static int mayorTickRetryAttempts = 0; private static String mayor = ""; /** @@ -194,11 +194,16 @@ public class Utils { } public static void init() { - SkyblockEvents.JOIN.register(() -> tickMayorCache(false)); + SkyblockEvents.JOIN.register(() -> { + if (!mayorTickScheduled) { + tickMayorCache(); + scheduleMayorTick(); + mayorTickScheduled = true; + } + }); ClientPlayConnectionEvents.JOIN.register(Utils::onClientWorldJoin); ClientReceiveMessageEvents.ALLOW_GAME.register(Utils::onChatMessage); ClientReceiveMessageEvents.GAME_CANCELED.register(Utils::onChatMessage); // Somehow this works even though onChatMessage returns a boolean - Scheduler.INSTANCE.scheduleCyclic(() -> tickMayorCache(true), 24_000, true); // Update every 20 minutes } /** @@ -361,8 +366,8 @@ public class Utils { // TODO: Combine with `ChocolateFactorySolver.formatTime` and move into `SkyblockTime`. public static Text getDurationText(int timeInSeconds) { int seconds = timeInSeconds % 60; - int minutes = (timeInSeconds/60) % 60; - int hours = (timeInSeconds/3600); + int minutes = (timeInSeconds / 60) % 60; + int hours = (timeInSeconds / 3600); MutableText time = Text.empty(); if (hours > 0) { @@ -463,7 +468,11 @@ public class Utils { if (message.startsWith(PROFILE_MESSAGE_PREFIX)) { profile = message.substring(PROFILE_MESSAGE_PREFIX.length()).split("§b")[0]; } else if (message.startsWith(PROFILE_ID_PREFIX)) { + String prevProfileId = profileId; profileId = message.substring(PROFILE_ID_PREFIX.length()); + if (!prevProfileId.equals(profileId)) { + SkyblockEvents.PROFILE_CHANGE.invoker().onSkyblockProfileChange(prevProfileId, profileId); + } MuseumItemCache.tick(profileId); } @@ -482,27 +491,47 @@ public class Utils { location = Location.UNKNOWN; } - private static void tickMayorCache(boolean refresh) { - if (!mayor.isEmpty() && !refresh) return; + private static void scheduleMayorTick() { + long currentYearMillis = SkyblockTime.getSkyblockMillis() % 446400000L; //446400000ms is 1 year, 105600000ms is the amount of time from early spring 1st to late spring 27th + // If current time is past late spring 27th, the next mayor change is at next year's spring 27th, otherwise it's at this year's spring 27th + long millisUntilNextMayorChange = currentYearMillis > 105600000L ? 446400000L - currentYearMillis + 105600000L : 105600000L - currentYearMillis; + Scheduler.INSTANCE.schedule(Utils::tickMayorCache, (int) (millisUntilNextMayorChange / 50) + 5 * 60 * 20); // 5 extra minutes to allow the cache to expire. This is a simpler than checking age and subtracting from max age and rescheduling again. + } + private static void tickMayorCache() { CompletableFuture.supplyAsync(() -> { try { - JsonObject json = JsonParser.parseString(Http.sendGetRequest("https://api.hypixel.net/v2/resources/skyblock/election")).getAsJsonObject(); - if (json.get("success").getAsBoolean()) return json.get("mayor").getAsJsonObject().get("name").getAsString(); - throw new IOException(json.get("cause").getAsString()); + Http.ApiResponse response = Http.sendCacheableGetRequest("https://api.hypixel.net/v2/resources/skyblock/election", null); //Authentication is not required for this endpoint + if (!response.ok()) throw new HttpResponseException(response.statusCode(), response.content()); + JsonObject json = JsonParser.parseString(response.content()).getAsJsonObject(); + if (!json.get("success").getAsBoolean()) throw new RuntimeException("Request failed!"); //Can't find a more appropriate exception to throw here. + return json.get("mayor").getAsJsonObject().get("name").getAsString(); } catch (Exception e) { - LOGGER.error("[Skyblocker] Failed to get mayor status!", e); + throw new RuntimeException(e); //Wrap the exception to be handled by the exceptionally block + } + }).exceptionally(throwable -> { + LOGGER.error("[Skyblocker] Failed to get mayor status!", throwable.getCause()); + if (mayorTickRetryAttempts < 5) { + int minutes = 5 * 1 << mayorTickRetryAttempts; //5, 10, 20, 40, 80 minutes + mayorTickRetryAttempts++; + LOGGER.warn("[Skyblocker] Retrying in {} minutes.", minutes); + Scheduler.INSTANCE.schedule(Utils::tickMayorCache, minutes * 60 * 20); + } else { + LOGGER.warn("[Skyblocker] Failed to get mayor status after 5 retries! Stopping further retries until next reboot."); + } + return ""; //Have to return a value for the thenAccept block. + }).thenAccept(result -> { + if (!result.isEmpty()) { + mayor = result; + LOGGER.info("[Skyblocker] Mayor set to {}.", mayor); + scheduleMayorTick(); //Ends up as a cyclic task with finer control over scheduled time } - return ""; - }).thenAccept(s -> { - if (!s.isEmpty()) mayor = s; }); - } /** * Used to avoid triggering things like chat rules or chat listeners infinitely, do not use otherwise. - * + * <p> * Bypasses MessageHandler#onGameMessage */ public static void sendMessageToBypassEvents(Text message) { diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 6c285f2f..6c2fa6cf 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -228,6 +228,7 @@ "skyblocker.config.general.itemTooltip.avg.BOTH": "Both", "skyblocker.config.general.itemTooltip.avg.ONE_DAY": "1 day price", "skyblocker.config.general.itemTooltip.avg.THREE_DAY": "3 day price", + "skyblocker.config.general.itemTooltip.avg": "Average Type", "skyblocker.config.general.itemTooltip.dungeonQuality": "Dungeon Quality", "skyblocker.config.general.itemTooltip.dungeonQuality.@Tooltip": "Displays quality and tier of dungeon drops from mobs.\n\n\nReminder:\nTier 1-3 dropped from F1-F3\nTier 4-7 dropped from F4-F7 or M1-M4\nTier 8-10 are dropped only from M5-M7", "skyblocker.config.general.itemTooltip.enableAccessoriesHelper": "Enable Accessories Helper", @@ -239,6 +240,11 @@ "skyblocker.config.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]": "You don't own any accessory from this family.", "skyblocker.config.general.itemTooltip.enableAvgBIN": "Enable Avg. BIN Price", "skyblocker.config.general.itemTooltip.enableBazaarPrice": "Enable Bazaar buy/sell Price", + "skyblocker.config.general.itemTooltip.craft": "Crafting Cost", + "skyblocker.config.general.itemTooltip.craft.@Tooltip": "You can choose which Bazaar order type to use in crafting calculation", + "skyblocker.config.general.itemTooltip.craft.OFF": "Off", + "skyblocker.config.general.itemTooltip.craft.SELL_ORDER": "Sell Order", + "skyblocker.config.general.itemTooltip.craft.BUY_ORDER": "Buy Order", "skyblocker.config.general.itemTooltip.enableExoticTooltip": "Enable Exotic Tooltip", "skyblocker.config.general.itemTooltip.enableExoticTooltip.@Tooltip": "Displays the type of exotic below the item's name if an armor piece is exotic.", "skyblocker.config.general.itemTooltip.enableLowestBIN": "Enable Lowest BIN Price", @@ -615,6 +621,8 @@ "skyblocker.config.uiAndVisuals.searchOverlay.maxSuggestions": "Maximum Suggestions", "skyblocker.config.uiAndVisuals.searchOverlay.maxSuggestions.@Tooltip": "The maximum number of suggested items to show.", + "skyblocker.config.uiAndVisuals.showEquipmentInInventory": "Show Equipment in Inventory", + "skyblocker.config.uiAndVisuals.tabHud": "Fancy tab HUD (Temporarily disabled outside dungeons)", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground": "Enable HUD Background", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground.@Tooltip": "Enables the background of the non-tab HUD.", diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png b/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png Binary files differnew file mode 100644 index 00000000..be89af40 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png |