diff options
author | msg-programs <msgdoesstuff@gmail.com> | 2023-10-31 21:03:42 +0100 |
---|---|---|
committer | msg-programs <msgdoesstuff@gmail.com> | 2023-10-31 21:03:42 +0100 |
commit | d560c4611e603fa9e72ff6842bc14518d7bdbd63 (patch) | |
tree | 36eb1e24443bc9d9fbba51e14f12e5aacfac935c /src/main/java/de/hysky/skyblocker/skyblock/item | |
parent | 1df4ef827d8a2e2fcc3767c1f5bf961f16b7fa19 (diff) | |
parent | 5bb91104d3275283d7479f0b35c1b18be470d632 (diff) | |
download | Skyblocker-d560c4611e603fa9e72ff6842bc14518d7bdbd63.tar.gz Skyblocker-d560c4611e603fa9e72ff6842bc14518d7bdbd63.tar.bz2 Skyblocker-d560c4611e603fa9e72ff6842bc14518d7bdbd63.zip |
Merge branch 'master' of https://github.com/SkyblockerMod/Skyblocker into cleanup-2
# Conflicts:
# src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
# src/main/java/de/hysky/skyblocker/utils/Http.java
# src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainerConfigScreen.java
Pull changes from upstream master
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock/item')
13 files changed, 764 insertions, 441 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java deleted file mode 100644 index 122ffe9b..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java +++ /dev/null @@ -1,235 +0,0 @@ -package de.hysky.skyblocker.skyblock.item; - -import com.mojang.blaze3d.systems.RenderSystem; -import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.utils.Utils; -import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -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.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.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 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; - - private static final Inventory[] storage = new Inventory[STORAGE_SIZE]; - private static final boolean[] dirty = new boolean[STORAGE_SIZE]; - - private static String loaded = ""; // uuid + sb profile currently loaded - private static Path save_dir = null; - - public static void init() { - ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { - if (screen instanceof HandledScreen<?> handledScreen) { - updateStorage(handledScreen); - } - }); - } - - public static void tick() { - Utils.update(); // force update isOnSkyblock to prevent crash on disconnect - if (Utils.isOnSkyblock()) { - // save all dirty storages - saveStorage(); - // update save dir based on uuid and sb profile - String uuid = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", ""); - 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)) { - // mark currently opened storage as dirty - if (MinecraftClient.getInstance().currentScreen != null) { - String title = MinecraftClient.getInstance().currentScreen.getTitle().getString(); - int index = getStorageIndexFromTitle(title); - if (index != -1) dirty[index] = true; - } - } else { - // load storage again because uuid/profile changed - loaded = uuid + "/" + profile; - loadStorage(); - } - } - } - } - - public static void loadStorage() { - assert (save_dir != null); - for (int index = 0; index < STORAGE_SIZE; ++index) { - storage[index] = null; - dirty[index] = false; - File file = save_dir.resolve(index + ".nbt").toFile(); - if (file.isFile()) { - try { - NbtCompound root = NbtIo.read(file); - storage[index] = new DummyInventory(root); - } catch (Exception e) { - LOGGER.error("Failed to load backpack preview file: " + file.getName(), e); - } - } - } - } - - private static void saveStorage() { - assert (save_dir != null); - for (int index = 0; index < STORAGE_SIZE; ++index) { - if (dirty[index]) { - if (storage[index] != null) { - try { - NbtCompound root = new NbtCompound(); - NbtList list = new NbtList(); - for (int i = 9; i < storage[index].size(); ++i) { - ItemStack stack = storage[index].getStack(i); - NbtCompound item = new NbtCompound(); - if (stack.isEmpty()) { - item.put("id", NbtString.of("minecraft:air")); - item.put("Count", NbtInt.of(1)); - } else { - item.put("id", NbtString.of(stack.getItem().toString())); - item.put("Count", NbtInt.of(stack.getCount())); - item.put("tag", stack.getNbt()); - } - list.add(item); - } - root.put("list", list); - root.put("size", NbtInt.of(storage[index].size() - 9)); - NbtIo.write(root, save_dir.resolve(index + ".nbt").toFile()); - dirty[index] = false; - } catch (Exception e) { - LOGGER.error("Failed to save backpack preview file: " + index + ".nbt", e); - } - } - } - } - } - - public static void updateStorage(HandledScreen<?> screen) { - String title = screen.getTitle().getString(); - int index = getStorageIndexFromTitle(title); - if (index != -1) { - storage[index] = screen.getScreenHandler().slots.get(0).inventory; - dirty[index] = true; - } - } - - public static boolean renderPreview(DrawContext context, int index, int mouseX, int mouseY) { - if (index >= 9 && index < 18) index -= 9; - else if (index >= 27 && index < 45) index -= 18; - else return false; - - if (storage[index] == null) return false; - 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) { - 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(); - TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - for (int i = 9; i < storage[index].size(); ++i) { - int itemX = x + (i - 9) % 9 * 18 + 8; - int itemY = y + (i - 9) / 9 * 18 + 8; - matrices.push(); - matrices.translate(0, 0, 200); - context.drawItem(storage[index].getStack(i), itemX, itemY); - context.drawItemInSlot(textRenderer, storage[index].getStack(i), itemX, itemY); - matrices.pop(); - } - - return true; - } - - private static int getStorageIndexFromTitle(String title) { - Matcher echest = ECHEST_PATTERN.matcher(title); - if (echest.find()) return Integer.parseInt(echest.group(1)) - 1; - Matcher backpack = BACKPACK_PATTERN.matcher(title); - if (backpack.find()) return Integer.parseInt(backpack.group(1)) + 8; - return -1; - } -} - -class DummyInventory implements Inventory { - private final List<ItemStack> stacks; - - public DummyInventory(NbtCompound root) { - stacks = new ArrayList<>(root.getInt("size") + 9); - for (int i = 0; i < 9; ++i) stacks.add(ItemStack.EMPTY); - root.getList("list", NbtCompound.COMPOUND_TYPE).forEach(item -> - stacks.add(ItemStack.fromNbt((NbtCompound) item)) - ); - } - - @Override - public int size() { - return stacks.size(); - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public ItemStack getStack(int slot) { - return stacks.get(slot); - } - - @Override - public ItemStack removeStack(int slot, int amount) { - return null; - } - - @Override - public ItemStack removeStack(int slot) { - return null; - } - - @Override - public void setStack(int slot, ItemStack stack) { - stacks.set(slot, stack); - } - - @Override - public void markDirty() { - } - - @Override - public boolean canPlayerUse(PlayerEntity player) { - return false; - } - - @Override - public void clear() { - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java index 1496c90f..509f79b7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java @@ -4,6 +4,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -34,7 +35,7 @@ public class CustomArmorDyeColors { ItemStack heldItem = source.getPlayer().getMainHandStack(); if (hex != null && !isHexadecimalColor(hex)) { - source.sendError(Text.translatable("skyblocker.customDyeColors.invalidHex")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.invalidHex"))); return Command.SINGLE_SUCCESS; } @@ -49,24 +50,24 @@ public class CustomArmorDyeColors { if (customDyeColors.containsKey(itemUuid)) { customDyeColors.removeInt(itemUuid); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.customDyeColors.removed")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.removed"))); } else { - source.sendFeedback(Text.translatable("skyblocker.customDyeColors.neverHad")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.neverHad"))); } } else { customDyeColors.put(itemUuid, Integer.decode("0x" + hex.replace("#", "")).intValue()); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.customDyeColors.added")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.added"))); } } else { - source.sendError(Text.translatable("skyblocker.customDyeColors.noItemUuid")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.noItemUuid"))); } } else { - source.sendError(Text.translatable("skyblocker.customDyeColors.notDyeable")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.notDyeable"))); return Command.SINGLE_SUCCESS; } } else { - source.sendError(Text.translatable("skyblocker.customDyeColors.unableToSetColor")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.unableToSetColor"))); } return Command.SINGLE_SUCCESS; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java index 9242d47b..cec84b38 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java @@ -3,8 +3,12 @@ package de.hysky.skyblocker.skyblock.item; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import dev.isxander.yacl3.config.v2.api.SerialEntry; @@ -105,38 +109,43 @@ public class CustomArmorTrims { if (customArmorTrims.containsKey(itemUuid)) { customArmorTrims.remove(itemUuid); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.customArmorTrims.removed")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.removed"))); } else { - source.sendFeedback(Text.translatable("skyblocker.customArmorTrims.neverHad")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.neverHad"))); } } else { // Ensure that the material & trim are valid ArmorTrimId trimId = new ArmorTrimId(material, pattern); if (TRIMS_CACHE.get(trimId) == null) { - source.sendError(Text.translatable("skyblocker.customArmorTrims.invalidMaterialOrPattern")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.invalidMaterialOrPattern"))); return Command.SINGLE_SUCCESS; } customArmorTrims.put(itemUuid, trimId); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.customArmorTrims.added")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.added"))); } } else { - source.sendError(Text.translatable("skyblocker.customArmorTrims.noItemUuid")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.noItemUuid"))); } } else { - source.sendError(Text.translatable("skyblocker.customArmorTrims.notAnArmorPiece")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.notAnArmorPiece"))); return Command.SINGLE_SUCCESS; } } else { - source.sendError(Text.translatable("skyblocker.customArmorTrims.unableToSetTrim")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.unableToSetTrim"))); } return Command.SINGLE_SUCCESS; } public record ArmorTrimId(@SerialEntry Identifier material, @SerialEntry Identifier pattern) implements Pair<Identifier, Identifier> { + public static final Codec<ArmorTrimId> CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("material").forGetter(ArmorTrimId::material), + Identifier.CODEC.fieldOf("pattern").forGetter(ArmorTrimId::pattern)) + .apply(instance, ArmorTrimId::new)); + @Override public Identifier left() { return material(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java index b6213eb6..e47444cf 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.item; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -42,9 +43,9 @@ public class CustomItemNames { //Remove custom item name when the text argument isn't passed customItemNames.remove(itemUuid); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.customItemNames.removed")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.removed"))); } else { - source.sendFeedback(Text.translatable("skyblocker.customItemNames.neverHad")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.neverHad"))); } } else { //If the text is provided then set the item's custom name to it @@ -55,13 +56,13 @@ public class CustomItemNames { customItemNames.put(itemUuid, text); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.customItemNames.added")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.added"))); } } else { - source.sendError(Text.translatable("skyblocker.customItemNames.noItemUuid")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.noItemUuid"))); } } else { - source.sendError(Text.translatable("skyblocker.customItemNames.unableToSetName")); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.unableToSetName"))); } return Command.SINGLE_SUCCESS; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java index ff88ef8d..2d929c28 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.item; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -44,18 +45,18 @@ public class ItemProtection { protectedItems.add(itemUuid); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.itemProtection.added", heldItem.getName())); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.added", heldItem.getName()))); } else { protectedItems.remove(itemUuid); SkyblockerConfigManager.save(); - source.sendFeedback(Text.translatable("skyblocker.itemProtection.removed", heldItem.getName())); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.removed", heldItem.getName()))); } } else { - source.sendFeedback(Text.translatable("skyblocker.itemProtection.noItemUuid")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.noItemUuid"))); } } else { - source.sendFeedback(Text.translatable("skyblocker.itemProtection.unableToProtect")); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.unableToProtect"))); } return Command.SINGLE_SUCCESS; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java new file mode 100644 index 00000000..ac9b1bf0 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java @@ -0,0 +1,144 @@ +package de.hysky.skyblocker.skyblock.item; + +import java.io.ByteArrayInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; +import com.mojang.util.UndashedUuid; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.utils.Http.ApiResponse; +import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtList; +import net.minecraft.util.Util; + +public class MuseumItemCache { + private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class); + private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json"); + private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>(); + private static final Type MAP_TYPE = new TypeToken<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>>>() {}.getType(); + + private static CompletableFuture<Void> loaded; + + public static void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(MuseumItemCache::load); + } + + private static void load(MinecraftClient client) { + loaded = CompletableFuture.runAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(CACHE_FILE)) { + Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> cachedData = SkyblockerMod.GSON.fromJson(reader, MAP_TYPE); + + MUSEUM_ITEM_CACHE.putAll(cachedData); + LOGGER.info("[Skyblocker] Loaded museum items cache"); + } catch (NoSuchFileException ignored) { + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to load cached museum items", e); + } + }); + } + + private static void save() { + CompletableFuture.runAsync(() -> { + try (BufferedWriter writer = Files.newBufferedWriter(CACHE_FILE)) { + SkyblockerMod.GSON.toJson(MUSEUM_ITEM_CACHE, writer); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to save cached museum items!", e); + } + }); + } + + private static void updateData4ProfileMember(String uuid, String profileId) { + CompletableFuture.runAsync(() -> { + try (ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId)) { + //The request was successful + if (response.ok()) { + JsonObject profileData = JsonParser.parseString(response.content()).getAsJsonObject(); + JsonObject memberData = profileData.get("members").getAsJsonObject().get(uuid).getAsJsonObject(); + + //We call them sets because it could either be a singular item or an entire armour set + Map<String, JsonElement> donatedSets = memberData.get("items").getAsJsonObject().asMap(); + + //Set of all found item ids on profile + ObjectOpenHashSet<String> itemIds = new ObjectOpenHashSet<>(); + + for (Map.Entry<String, JsonElement> donatedSet : donatedSets.entrySet()) { + //Item is plural here because the nbt is a list + String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString(); + NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData))).getList("i", NbtElement.COMPOUND_TYPE); + + for (int i = 0; i < items.size(); i++) { + NbtCompound tag = items.getCompound(i).getCompound("tag"); + + if (tag.contains("ExtraAttributes")) { + NbtCompound extraAttributes = tag.getCompound("ExtraAttributes"); + + if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id")); + } + } + } + + MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds)); + save(); + + LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId); + } + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to refresh museum item data for profile {}", profileId, e); + } + }); + } + + /** + * The cache is ticked upon switching skyblock servers + */ + public static void tick(String profileId) { + if (loaded.isDone()) { + String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + Object2ObjectOpenHashMap<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, uuid1 -> Util.make(new Object2ObjectOpenHashMap<>(), map -> { + map.put(profileId, ProfileMuseumData.EMPTY); + })); + + if (playerData.get(profileId).stale()) updateData4ProfileMember(uuid, profileId); + } + } + + public static boolean hasItemInMuseum(String id) { + String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + ObjectOpenHashSet<String> collectedItemIds = MUSEUM_ITEM_CACHE.get(uuid).get(Utils.getProfileId()).collectedItemIds(); + + return collectedItemIds != null && collectedItemIds.contains(id); + } + + private record ProfileMuseumData(long lastUpdated, ObjectOpenHashSet<String> collectedItemIds) { + private static final ProfileMuseumData EMPTY = new ProfileMuseumData(0L, null); + private static final long MAX_AGE = 86_400_000; + + private boolean stale() { + return System.currentTimeMillis() > lastUpdated + MAX_AGE; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java index d4e6a0df..38121ea3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java @@ -1,23 +1,25 @@ package de.hysky.skyblocker.skyblock.item; -import de.hysky.skyblocker.skyblock.itemlist.ItemRegistry; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.Utils; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; import net.minecraft.util.Util; import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.CompletableFuture; public class WikiLookup { + private static final Logger LOGGER = LoggerFactory.getLogger(WikiLookup.class); public static KeyBinding wikiLookup; - static final MinecraftClient client = MinecraftClient.getInstance(); - static String id; + private static String id; public static void init() { wikiLookup = KeyBindingHelper.registerKeyBinding(new KeyBinding( @@ -28,22 +30,22 @@ public class WikiLookup { )); } - public static String getSkyblockId(Slot slot) { + public static void getSkyblockId(Slot slot) { //Grabbing the skyblock NBT data ItemUtils.getItemIdOptional(slot.getStack()).ifPresent(newId -> id = newId); - return id; } - public static void openWiki(Slot slot) { - if (Utils.isOnSkyblock()) { - id = getSkyblockId(slot); + public static void openWiki(Slot slot, PlayerEntity player) { + if (SkyblockerConfigManager.get().general.wikiLookup.enableWikiLookup) { + getSkyblockId(slot); try { - String wikiLink = ItemRegistry.getWikiLink(id); + String wikiLink = ItemRepository.getWikiLink(id, player); CompletableFuture.runAsync(() -> Util.getOperatingSystem().open(wikiLink)); } catch (IndexOutOfBoundsException | IllegalStateException e) { - e.printStackTrace(); - if (client.player != null) - client.player.sendMessage(Text.of("Error while retrieving wiki article..."), false); + LOGGER.error("[Skyblocker] Error while retrieving wiki article...", e); + if (player != null) { + player.sendMessage(Text.of("[Skyblocker] Error while retrieving wiki article, see logs..."), false); + } } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java new file mode 100644 index 00000000..5627b56d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java @@ -0,0 +1,204 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import com.mojang.blaze3d.systems.RenderSystem; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds; +import de.hysky.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtInt; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtList; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +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("textures/gui/container/generic_54.png"); + private static final Pattern ECHEST_PATTERN = Pattern.compile("Ender Chest.*\\((\\d+)/\\d+\\)"); + private static final Pattern BACKPACK_PATTERN = Pattern.compile("Backpack.*\\(Slot #(\\d+)\\)"); + private static final int STORAGE_SIZE = 27; + + private static final Storage[] storages = new Storage[STORAGE_SIZE]; + + /** + * The profile id of the currently loaded backpack preview. + */ + private static String loaded; + private static Path saveDir; + + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + if (screen instanceof HandledScreen<?> handledScreen) { + ScreenEvents.remove(screen).register(screen1 -> updateStorage(handledScreen)); + } + }); + } + + public static void tick() { + Utils.update(); // force update isOnSkyblock to prevent crash on disconnect + if (Utils.isOnSkyblock()) { + // save all dirty storages + saveStorages(); + // update save dir based on sb profile id + String id = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", "") + "/" + Utils.getProfileId(); + if (!id.equals(loaded)) { + saveDir = SkyblockerMod.CONFIG_DIR.resolve("backpack-preview/" + id); + //noinspection ResultOfMethodCallIgnored + saveDir.toFile().mkdirs(); + // load storage again because profile id changed + loaded = id; + loadStorages(); + } + } + } + + private static void loadStorages() { + for (int index = 0; index < STORAGE_SIZE; ++index) { + storages[index] = null; + File storageFile = saveDir.resolve(index + ".nbt").toFile(); + if (storageFile.isFile()) { + try { + storages[index] = Storage.fromNbt(Objects.requireNonNull(NbtIo.read(storageFile))); + } catch (Exception e) { + LOGGER.error("Failed to load backpack preview file: " + storageFile.getName(), e); + } + } + } + } + + private static void saveStorages() { + for (int index = 0; index < STORAGE_SIZE; ++index) { + if (storages[index] != null && storages[index].dirty) { + saveStorage(index); + } + } + } + + private static void saveStorage(int index) { + try { + NbtIo.write(storages[index].toNbt(), saveDir.resolve(index + ".nbt").toFile()); + storages[index].markClean(); + } catch (Exception e) { + LOGGER.error("Failed to save backpack preview file: " + index + ".nbt", e); + } + } + + private static void updateStorage(HandledScreen<?> handledScreen) { + String title = handledScreen.getTitle().getString(); + int index = getStorageIndexFromTitle(title); + if (index != -1) { + storages[index] = new Storage(handledScreen.getScreenHandler().slots.get(0).inventory, title, true); + } + } + + public static boolean renderPreview(DrawContext context, Screen screen, int index, int mouseX, int mouseY) { + if (index >= 9 && index < 18) index -= 9; + else if (index >= 27 && index < 45) index -= 18; + else return false; + + if (storages[index] == null) return false; + int rows = (storages[index].size() - 9) / 9; + + int x = mouseX + 184 >= screen.width ? mouseX - 188 : mouseX + 8; + int y = Math.max(0, mouseY - 16); + + MatrixStack matrices = context.getMatrices(); + matrices.push(); + matrices.translate(0f, 0f, 400f); + + RenderSystem.enableDepthTest(); + context.drawTexture(TEXTURE, x, y, 0, 0, 176, rows * 18 + 17); + context.drawTexture(TEXTURE, x, y + rows * 18 + 17, 0, 215, 176, 7); + + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + context.drawText(textRenderer, storages[index].name, x + 8, y + 6, 0x404040, false); + + matrices.translate(0f, 0f, 200f); + for (int i = 9; i < storages[index].size(); ++i) { + ItemStack currentStack = storages[index].getStack(i); + int itemX = x + (i - 9) % 9 * 18 + 8; + int itemY = y + (i - 9) / 9 * 18 + 18; + + if (SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) { + ItemRarityBackgrounds.tryDraw(currentStack, context, itemX, itemY); + } + + context.drawItem(currentStack, itemX, itemY); + context.drawItemInSlot(textRenderer, currentStack, itemX, itemY); + } + + matrices.pop(); + + return true; + } + + private static int getStorageIndexFromTitle(String title) { + Matcher echest = ECHEST_PATTERN.matcher(title); + if (echest.find()) return Integer.parseInt(echest.group(1)) - 1; + Matcher backpack = BACKPACK_PATTERN.matcher(title); + if (backpack.find()) return Integer.parseInt(backpack.group(1)) + 8; + return -1; + } + + static class Storage { + private final Inventory inventory; + private final String name; + private boolean dirty; + + private Storage(Inventory inventory, String name, boolean dirty) { + this.inventory = inventory; + this.name = name; + this.dirty = dirty; + } + + private int size() { + return inventory.size(); + } + + private ItemStack getStack(int index) { + return inventory.getStack(index); + } + + private void markClean() { + dirty = false; + } + + @NotNull + private static Storage fromNbt(NbtCompound root) { + SimpleInventory inventory = new SimpleInventory(root.getList("list", NbtCompound.COMPOUND_TYPE).stream().map(NbtCompound.class::cast).map(ItemStack::fromNbt).toArray(ItemStack[]::new)); + return new Storage(inventory, root.getString("name"), false); + } + + @NotNull + private NbtCompound toNbt() { + NbtCompound root = new NbtCompound(); + NbtList list = new NbtList(); + for (int i = 0; i < size(); ++i) { + list.add(getStack(i).writeNbt(new NbtCompound())); + } + root.put("list", list); + root.put("size", NbtInt.of(size())); + root.putString("name", name); + return root; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorDeletorPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java index 7f5b96b9..9cf0356b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorDeletorPreview.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java @@ -1,7 +1,7 @@ -package de.hysky.skyblocker.skyblock.item; +package de.hysky.skyblocker.skyblock.item.tooltip; import de.hysky.skyblocker.mixin.accessor.DrawContextInvoker; -import de.hysky.skyblocker.skyblock.itemlist.ItemRegistry; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntObjectPair; @@ -43,7 +43,7 @@ public class CompactorDeletorPreview { NbtCompound extraAttributes = ItemUtils.getExtraAttributes(stack); if (extraAttributes == null) return false; // 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<IntObjectPair<ItemStack>> slots = extraAttributes.getKeys().stream().filter(slot -> slot.contains(type.toLowerCase().substring(0, 7))).map(slot -> IntObjectPair.of(Integer.parseInt(slot.substring(17)), ItemRepository.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); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java index 513d7d72..22498c02 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.item; +package de.hysky.skyblocker.skyblock.item.tooltip; import de.hysky.skyblocker.SkyblockerMod; import it.unimi.dsi.fastutil.ints.IntIntPair; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java new file mode 100644 index 00000000..66d94890 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java @@ -0,0 +1,96 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import de.hysky.skyblocker.utils.Constants; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringIdentifiable; + +public class ExoticTooltip { + public static String getExpectedHex(String id) { + String color = TooltipInfoType.COLOR.getData().get(id).getAsString(); + if (color != null) { + String[] RGBValues = color.split(","); + return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2])); + } else { + ItemTooltip.LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id); + return null; + } + } + + public static boolean isException(String id, String hex) { + if (id.startsWith("LEATHER") || id.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(id)) { + return true; + } + if (id.startsWith("RANCHER")) { + return Constants.RANCHERS.contains(hex); + } + if (id.contains("ADAPTIVE_CHESTPLATE")) { + return Constants.ADAPTIVE_CHEST.contains(hex); + } else if (id.contains("ADAPTIVE")) { + return Constants.ADAPTIVE.contains(hex); + } + if (id.startsWith("REAPER")) { + return Constants.REAPER.contains(hex); + } + if (id.startsWith("FAIRY")) { + return Constants.FAIRY_HEXES.contains(hex); + } + if (id.startsWith("CRYSTAL")) { + return Constants.CRYSTAL_HEXES.contains(hex); + } + if (id.contains("SPOOK")) { + return Constants.SPOOK.contains(hex); + } + return false; + } + + public static DyeType checkDyeType(String hex) { + if (Constants.CRYSTAL_HEXES.contains(hex)) { + return DyeType.CRYSTAL; + } + if (Constants.FAIRY_HEXES.contains(hex)) { + return DyeType.FAIRY; + } + if (Constants.OG_FAIRY_HEXES.contains(hex)) { + return DyeType.OG_FAIRY; + } + if (Constants.SPOOK.contains(hex)) { + return DyeType.SPOOK; + } + if (Constants.GLITCHED.contains(hex)) { + return DyeType.GLITCHED; + } + return DyeType.EXOTIC; + } + + public static boolean intendedDyed(NbtCompound ItemData) { + return ItemData.getCompound("ExtraAttributes").contains("dye_item"); + } + + public enum DyeType implements StringIdentifiable { + CRYSTAL("crystal", Formatting.AQUA), + FAIRY("fairy", Formatting.LIGHT_PURPLE), + OG_FAIRY("og_fairy", Formatting.DARK_PURPLE), + SPOOK("spook", Formatting.RED), + GLITCHED("glitched", Formatting.BLUE), + EXOTIC("exotic", Formatting.GOLD); + private final String name; + private final Formatting formatting; + + DyeType(String name, Formatting formatting) { + this.name = name; + this.formatting = formatting; + } + + @Override + public String asString() { + return name; + } + + public MutableText getTranslatedText() { + return Text.translatable("skyblocker.exotic." + name).formatted(formatting); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/PriceInfoTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 0f84deea..e050aff5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/PriceInfoTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -1,10 +1,10 @@ -package de.hysky.skyblocker.skyblock.item; +package de.hysky.skyblocker.skyblock.item.tooltip; -import com.google.gson.Gson; import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; @@ -12,36 +12,25 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.item.TooltipContext; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.http.HttpHeaders; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CompletableFuture; -public class PriceInfoTooltip { - private static final Logger LOGGER = LoggerFactory.getLogger(PriceInfoTooltip.class.getName()); +public class ItemTooltip { + protected static final Logger LOGGER = LoggerFactory.getLogger(ItemTooltip.class.getName()); private static final MinecraftClient client = MinecraftClient.getInstance(); - private static JsonObject npcPricesJson; - private static JsonObject bazaarPricesJson; - private static JsonObject oneDayAvgPricesJson; - private static JsonObject threeDayAvgPricesJson; - private static JsonObject lowestPricesJson; - private static JsonObject isMuseumJson; - private static JsonObject motesPricesJson; - private static volatile 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) { + protected static final SkyblockerConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip; + private static volatile boolean sentNullWarning = false; + + public static void getTooltip(ItemStack stack, TooltipContext context, List<Text> lines) { if (!Utils.isOnSkyblock() || client.player == null) return; String name = getInternalNameFromNBT(stack, false); @@ -54,65 +43,47 @@ public class PriceInfoTooltip { neuName = internalID; } + if (lines.isEmpty()) { + return; + } + int count = stack.getCount(); boolean bazaarOpened = lines.stream().anyMatch(each -> each.getString().contains("Buy price:") || each.getString().contains("Sell price:")); - if (SkyblockerConfigManager.get().general.itemTooltip.enableNPCPrice) { - if (npcPricesJson == null) { - nullWarning(); - } else if (npcPricesJson.has(internalID)) { - lines.add(Text.literal(String.format("%-21s", "NPC Price:")) - .formatted(Formatting.YELLOW) - .append(getCoinsMessage(npcPricesJson.get(internalID).getAsDouble(), count))); - } - } - - if (SkyblockerConfigManager.get().general.itemTooltip.enableMotesPrice && Utils.isInTheRift()) { - if (motesPricesJson == null) { - nullWarning(); - } else if (motesPricesJson.has(internalID)) { - lines.add(Text.literal(String.format("%-20s", "Motes Price:")) - .formatted(Formatting.LIGHT_PURPLE) - .append(getMotesMessage(motesPricesJson.get(internalID).getAsInt(), count))); - } + if (TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) { + lines.add(Text.literal(String.format("%-21s", "NPC Price:")) + .formatted(Formatting.YELLOW) + .append(getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), count))); } boolean bazaarExist = false; - if (SkyblockerConfigManager.get().general.itemTooltip.enableBazaarPrice && !bazaarOpened) { - if (bazaarPricesJson == null) { - nullWarning(); - } else if (bazaarPricesJson.has(name)) { - JsonObject getItem = bazaarPricesJson.getAsJsonObject(name); - lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) - .formatted(Formatting.GOLD) - .append(getItem.get("buyPrice").isJsonNull() - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count))); - lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) - .formatted(Formatting.GOLD) - .append(getItem.get("sellPrice").isJsonNull() - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count))); - bazaarExist = true; - } + if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened) { + JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name); + lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) + .formatted(Formatting.GOLD) + .append(getItem.get("buyPrice").isJsonNull() + ? Text.literal("No data").formatted(Formatting.RED) + : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count))); + lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) + .formatted(Formatting.GOLD) + .append(getItem.get("sellPrice").isJsonNull() + ? Text.literal("No data").formatted(Formatting.RED) + : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count))); + bazaarExist = true; } // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api boolean lbinExist = false; - if (SkyblockerConfigManager.get().general.itemTooltip.enableLowestBIN && !bazaarOpened && !bazaarExist) { - if (lowestPricesJson == null) { - nullWarning(); - } else if (lowestPricesJson.has(name)) { - lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) - .formatted(Formatting.GOLD) - .append(getCoinsMessage(lowestPricesJson.get(name).getAsDouble(), count))); - lbinExist = true; - } + if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened && !bazaarExist) { + lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) + .formatted(Formatting.GOLD) + .append(getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), count))); + lbinExist = true; } if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) { - if (threeDayAvgPricesJson == null || oneDayAvgPricesJson == null) { + if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { nullWarning(); } else { /* @@ -142,16 +113,16 @@ public class PriceInfoTooltip { } if (!neuName.isEmpty() && lbinExist) { - SkyblockerConfig.Average type = SkyblockerConfigManager.get().general.itemTooltip.avg; + SkyblockerConfig.Average type = config.avg; // "No data" line because of API not keeping old data, it causes NullPointerException if (type == SkyblockerConfig.Average.ONE_DAY || type == SkyblockerConfig.Average.BOTH) { lines.add( Text.literal(String.format("%-19s", "1 Day Avg. Price:")) .formatted(Formatting.GOLD) - .append(oneDayAvgPricesJson.get(neuName) == null + .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(oneDayAvgPricesJson.get(neuName).getAsDouble(), count) + : getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count) ) ); } @@ -159,9 +130,9 @@ public class PriceInfoTooltip { lines.add( Text.literal(String.format("%-19s", "3 Day Avg. Price:")) .formatted(Formatting.GOLD) - .append(threeDayAvgPricesJson.get(neuName) == null + .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(threeDayAvgPricesJson.get(neuName).getAsDouble(), count) + : getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count) ) ); } @@ -169,35 +140,68 @@ public class PriceInfoTooltip { } } - if (SkyblockerConfigManager.get().general.itemTooltip.enableMuseumDate && !bazaarOpened) { - if (isMuseumJson == null) { - nullWarning(); - } else { - String timestamp = getTimestamp(stack); - - if (isMuseumJson.has(internalID)) { - String itemCategory = isMuseumJson.get(internalID).getAsString(); - String format = switch (itemCategory) { - case "Weapons" -> "%-18s"; - case "Armor" -> "%-19s"; - default -> "%-20s"; - }; - lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) - .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(timestamp).formatted(Formatting.RED))); - } else if (!timestamp.isEmpty()) { - lines.add(Text.literal(String.format("%-21s", "Obtained: ")) - .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(timestamp).formatted(Formatting.RED))); + if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) { + lines.add(Text.literal(String.format("%-20s", "Motes Price:")) + .formatted(Formatting.LIGHT_PURPLE) + .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), count))); + } + + if (TooltipInfoType.MUSEUM.isTooltipEnabled() && !bazaarOpened) { + String timestamp = getTimestamp(stack); + + if (TooltipInfoType.MUSEUM.hasOrNullWarning(internalID)) { + String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString(); + String format = switch (itemCategory) { + case "Weapons" -> "%-18s"; + case "Armor" -> "%-19s"; + default -> "%-20s"; + }; + lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) + .formatted(Formatting.LIGHT_PURPLE) + .append(Text.literal(timestamp).formatted(Formatting.RED))); + } else if (!timestamp.isEmpty()) { + lines.add(Text.literal(String.format("%-21s", "Obtained: ")) + .formatted(Formatting.LIGHT_PURPLE) + .append(Text.literal(timestamp).formatted(Formatting.RED))); + } + } + + if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.getNbt() != null) { + final NbtElement color = stack.getNbt().getCompound("display").get("color"); + + if (color != null) { + String colorHex = String.format("%06X", Integer.parseInt(color.asString())); + String expectedHex = ExoticTooltip.getExpectedHex(internalID); + + boolean correctLine = false; + for (Text text : lines) { + String existingTooltip = text.getString() + " "; + if (existingTooltip.startsWith("Color: ")) { + correctLine = true; + + addExoticTooltip(lines, internalID, stack.getNbt(), colorHex, expectedHex, existingTooltip); + break; + } + } + + if (!correctLine) { + addExoticTooltip(lines, internalID, stack.getNbt(), colorHex, expectedHex, ""); } } } } - private static void nullWarning() { - if (!nullMsgSend && client.player != null) { - client.player.sendMessage(Text.translatable("skyblocker.itemTooltip.nullMessage"), false); - nullMsgSend = true; + private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound nbt, String colorHex, String expectedHex, String existingTooltip) { + if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !ExoticTooltip.isException(internalID, colorHex) && !ExoticTooltip.intendedDyed(nbt)) { + final ExoticTooltip.DyeType type = ExoticTooltip.checkDyeType(colorHex); + lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")")); + } + } + + public static void nullWarning() { + if (!sentNullWarning && client.player != null) { + client.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")), false); + sentNullWarning = true; } } @@ -233,6 +237,7 @@ public class PriceInfoTooltip { return ""; } + // TODO What in the world is this? public static String getInternalNameFromNBT(ItemStack stack, boolean internalIDOnly) { NbtCompound ea = ItemUtils.getExtraAttributes(stack); @@ -261,7 +266,7 @@ public class PriceInfoTooltip { } case "PET" -> { if (ea.contains("petInfo")) { - JsonObject petInfo = gson.fromJson(ea.getString("petInfo"), JsonObject.class); + JsonObject petInfo = SkyblockerMod.GSON.fromJson(ea.getString("petInfo"), JsonObject.class); return "LVL_1_" + petInfo.get("tier").getAsString() + "_" + petInfo.get("type").getAsString(); } } @@ -339,86 +344,36 @@ public class PriceInfoTooltip { public static void init() { Scheduler.INSTANCE.scheduleCyclic(() -> { if (!Utils.isOnSkyblock() && 0 < minute++) { - nullMsgSend = false; + sentNullWarning = false; return; } List<CompletableFuture<Void>> futureList = new ArrayList<>(); - if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) { - SkyblockerConfig.Average type = SkyblockerConfigManager.get().general.itemTooltip.avg; - - if (type == SkyblockerConfig.Average.BOTH || oneDayAvgPricesJson == null || threeDayAvgPricesJson == null || minute % 5 == 0) { - futureList.add(CompletableFuture.runAsync(() -> { - oneDayAvgPricesJson = downloadPrices("1 day avg"); - threeDayAvgPricesJson = downloadPrices("3 day avg"); - })); + + TooltipInfoType.NPC.downloadIfEnabled(futureList); + TooltipInfoType.BAZAAR.downloadIfEnabled(futureList); + TooltipInfoType.LOWEST_BINS.downloadIfEnabled(futureList); + + if (config.enableAvgBIN) { + SkyblockerConfig.Average type = config.avg; + + if (type == SkyblockerConfig.Average.BOTH || TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null || minute % 5 == 0) { + TooltipInfoType.ONE_DAY_AVERAGE.download(futureList); + TooltipInfoType.THREE_DAY_AVERAGE.download(futureList); } else if (type == SkyblockerConfig.Average.ONE_DAY) { - futureList.add(CompletableFuture.runAsync(() -> oneDayAvgPricesJson = downloadPrices("1 day avg"))); + TooltipInfoType.ONE_DAY_AVERAGE.download(futureList); } else if (type == SkyblockerConfig.Average.THREE_DAY) { - futureList.add(CompletableFuture.runAsync(() -> threeDayAvgPricesJson = downloadPrices("3 day avg"))); + TooltipInfoType.THREE_DAY_AVERAGE.download(futureList); } } - if (SkyblockerConfigManager.get().general.itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator) - futureList.add(CompletableFuture.runAsync(() -> lowestPricesJson = downloadPrices("lowest bins"))); - - if (SkyblockerConfigManager.get().general.itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator) - futureList.add(CompletableFuture.runAsync(() -> bazaarPricesJson = downloadPrices("bazaar"))); - - if (SkyblockerConfigManager.get().general.itemTooltip.enableNPCPrice && npcPricesJson == null) - futureList.add(CompletableFuture.runAsync(() -> npcPricesJson = downloadPrices("npc"))); - - if (SkyblockerConfigManager.get().general.itemTooltip.enableMuseumDate && isMuseumJson == null) - futureList.add(CompletableFuture.runAsync(() -> isMuseumJson = downloadPrices("museum"))); - if (SkyblockerConfigManager.get().general.itemTooltip.enableMotesPrice && motesPricesJson == null) - futureList.add(CompletableFuture.runAsync(() -> motesPricesJson = downloadPrices("motes"))); + TooltipInfoType.MOTES.downloadIfEnabled(futureList); + TooltipInfoType.MUSEUM.downloadIfEnabled(futureList); + TooltipInfoType.COLOR.downloadIfEnabled(futureList); minute++; - CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) - .whenComplete((unused, throwable) -> nullMsgSend = false); + CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new)) + .whenComplete((unused, throwable) -> sentNullWarning = false); }, 1200, true); } - - private static JsonObject downloadPrices(String type) { - try { - String url = apiAddresses.get(type); - - 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"); - 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/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java new file mode 100644 index 00000000..086fcb00 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -0,0 +1,145 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfig; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.utils.Utils; +import org.jetbrains.annotations.Nullable; + +import java.net.http.HttpHeaders; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +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().locations.dungeons.dungeonChestProfit.enableProfitCalculator, itemTooltip -> itemTooltip.enableBazaarPrice, false), + LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator, itemTooltip -> itemTooltip.enableLowestBIN, false), + ONE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false), + THREE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/3day.json", itemTooltip -> itemTooltip.enableAvgBIN, false), + MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true), + MUSEUM("https://hysky.de/api/museum", itemTooltip -> itemTooltip.enableMuseumDate, true), + COLOR("https://hysky.de/api/color", itemTooltip -> itemTooltip.enableExoticTooltip, true); + + private final String address; + private final Predicate<SkyblockerConfig.ItemTooltip> dataEnabled; + private final Predicate<SkyblockerConfig.ItemTooltip> tooltipEnabled; + private JsonObject data; + private final boolean cacheable; + private long hash; + + /** + * @param address the address to download the data from + * @param enabled the predicate to check if the data should be downloaded and the tooltip should be shown + * @param cacheable whether the data should be cached + */ + TooltipInfoType(String address, Predicate<SkyblockerConfig.ItemTooltip> enabled, boolean cacheable) { + this(address, enabled, enabled, null, cacheable); + } + + /** + * @param address the address to download the data from + * @param dataEnabled the predicate to check if data should be downloaded + * @param tooltipEnabled the predicate to check if the tooltip should be shown + * @param cacheable whether the data should be cached + */ + TooltipInfoType(String address, Predicate<SkyblockerConfig.ItemTooltip> dataEnabled, Predicate<SkyblockerConfig.ItemTooltip> tooltipEnabled, boolean cacheable) { + this(address, dataEnabled, tooltipEnabled, null, cacheable); + } + + /** + * @param address the address to download the data from + * @param dataEnabled the predicate to check if data should be downloaded + * @param tooltipEnabled the predicate to check if the tooltip should be shown + * @param data the data + * @param cacheable whether the data should be cached + */ + TooltipInfoType(String address, Predicate<SkyblockerConfig.ItemTooltip> dataEnabled, Predicate<SkyblockerConfig.ItemTooltip> tooltipEnabled, @Nullable JsonObject data, boolean cacheable) { + this.address = address; + this.dataEnabled = dataEnabled; + this.tooltipEnabled = tooltipEnabled; + this.data = data; + this.cacheable = cacheable; + } + + /** + * @return whether the data should be downloaded + */ + private boolean isDataEnabled() { + return dataEnabled.test(ItemTooltip.config); + } + + /** + * @return whether the tooltip should be shown + */ + public boolean isTooltipEnabled() { + return tooltipEnabled.test(ItemTooltip.config); + } + + public JsonObject getData() { + return data; + } + + /** + * Checks if the data has the given member name and sends a warning message if data is null. + * + * @param memberName the member name to check + * @return whether the data has the given member name or not + */ + public boolean hasOrNullWarning(String memberName) { + if (data == null) { + ItemTooltip.nullWarning(); + return false; + } else return data.has(memberName); + } + + /** + * Checks if the tooltip is enabled and the data has the given member name and sends a warning message if data is null. + * + * @param memberName the member name to check + * @return whether the tooltip is enabled and the data has the given member name or not + */ + public boolean isTooltipEnabledAndHasOrNullWarning(String memberName) { + return isTooltipEnabled() && hasOrNullWarning(memberName); + } + + /** + * Downloads the data if it is enabled. + * + * @param futureList the list to add the future to + */ + public void downloadIfEnabled(List<CompletableFuture<Void>> futureList) { + if (isDataEnabled()) { + download(futureList); + } + } + + /** + * Downloads the data. + * + * @param futureList the list to add the future to + */ + public void download(List<CompletableFuture<Void>> futureList) { + futureList.add(CompletableFuture.runAsync(this)); + } + + /** + * Downloads the data. + */ + @Override + public void run() { + try { + if (cacheable) { + HttpHeaders headers = Http.sendHeadRequest(address); + long hash = Http.getEtag(headers).hashCode() + Http.getLastModified(headers).hashCode(); + if (this.hash == hash) return; + else this.hash = hash; + } + data = SkyblockerMod.GSON.fromJson(Http.sendGetRequest(address), JsonObject.class); + } catch (Exception e) { + ItemTooltip.LOGGER.warn("[Skyblocker] Failed to download " + this + " prices!", e); + } + } +} |