diff options
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock')
9 files changed, 402 insertions, 66 deletions
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() { |