diff options
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/utils')
15 files changed, 382 insertions, 213 deletions
diff --git a/src/main/java/de/hysky/skyblocker/utils/Constants.java b/src/main/java/de/hysky/skyblocker/utils/Constants.java index 403689ac..d900f917 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Constants.java +++ b/src/main/java/de/hysky/skyblocker/utils/Constants.java @@ -12,7 +12,7 @@ import java.util.function.Supplier; * Holds generic static constants */ public interface Constants { - String LEVEL_EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u26C2\u0FC7\uA925\uA56A\u12DE"; + String LEVEL_EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u26C2\u0FC7\uA925\uA56A\u2592\u12DE"; Supplier<MutableText> PREFIX = () -> { LocalDate time = LocalDate.now(); return Text.empty() diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 70a8c241..086686a7 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -1,29 +1,41 @@ package de.hysky.skyblocker.utils; +import com.google.gson.Gson; +import com.google.gson.JsonParser; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import de.hysky.skyblocker.mixin.accessor.ItemStackAccessor; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.ints.IntIntPair; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.component.ComponentChanges; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.LoreComponent; +import net.minecraft.component.type.NbtComponent; +import net.minecraft.component.type.ProfileComponent; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; -import net.minecraft.nbt.StringNbtReader; +import net.minecraft.registry.Registries; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.Text; -import net.minecraft.text.Texts; import net.minecraft.util.Formatting; +import net.minecraft.util.dynamic.Codecs; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.time.temporal.TemporalAccessor; +import java.util.List; +import java.util.Locale; +import java.util.Optional; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,83 +43,72 @@ import java.util.regex.Pattern; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class ItemUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(ItemUtils.class); - public static final String EXTRA_ATTRIBUTES = "ExtraAttributes"; public static final String ID = "id"; public static final String UUID = "uuid"; private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH); - private static final SimpleDateFormat OLD_OBTAINED_DATE_FORMAT = new SimpleDateFormat("MM/dd/yy"); + private static final DateTimeFormatter OLD_OBTAINED_DATE_FORMAT = DateTimeFormatter.ofPattern("M/d/yy h:m a").withZone(ZoneId.of("UTC")).localizedBy(Locale.ENGLISH); public static final Pattern NOT_DURABILITY = Pattern.compile("[^0-9 /]"); public static final Predicate<String> FUEL_PREDICATE = line -> line.contains("Fuel: "); - - public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemNbtCommand() { - return literal("dumpHeldItemNbt").executes(context -> { - context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item Nbt: " + context.getSource().getPlayer().getMainHandStack().writeNbt(new NbtCompound()))); + private static final Gson GSON = new Gson(); //GSON Instance with no config + private static final Codec<RegistryEntry<Item>> EMPTY_ALLOWING_ITEM_CODEC = Registries.ITEM.getEntryCodec(); + public static final Codec<ItemStack> EMPTY_ALLOWING_ITEMSTACK_CODEC = Codec.lazyInitialized(() -> RecordCodecBuilder.create(instance -> instance.group( + EMPTY_ALLOWING_ITEM_CODEC.fieldOf("id").forGetter(ItemStack::getRegistryEntry), + Codec.INT.orElse(1).fieldOf("count").forGetter(ItemStack::getCount), + ComponentChanges.CODEC.optionalFieldOf("components", ComponentChanges.EMPTY).forGetter(ItemStack::getComponentChanges) + ).apply(instance, ItemStack::new))); + + public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemCommand() { + return literal("dumpHeldItem").executes(context -> { + context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item: " + GSON.toJson(ItemStack.CODEC.encodeStart(JsonOps.INSTANCE, context.getSource().getPlayer().getMainHandStack()).getOrThrow()))); return Command.SINGLE_SUCCESS; }); } - /** - * Gets the {@code ExtraAttributes} NBT tag from the item stack. - * - * @param stack the item stack to get the {@code ExtraAttributes} NBT tag from - * @return an optional containing the {@code ExtraAttributes} NBT tag of the item stack - */ - public static Optional<NbtCompound> getExtraAttributesOptional(@NotNull ItemStack stack) { - return Optional.ofNullable(stack.getSubNbt(EXTRA_ATTRIBUTES)); - } - - /** - * Gets the {@code ExtraAttributes} NBT tag from the item stack. - * - * @param stack the item stack to get the {@code ExtraAttributes} NBT tag from - * @return the {@code ExtraAttributes} NBT tag of the item stack, or null if the item stack is null or does not have an {@code ExtraAttributes} NBT tag - */ - @Nullable - public static NbtCompound getExtraAttributes(@NotNull ItemStack stack) { - return stack.getSubNbt(EXTRA_ATTRIBUTES); + @SuppressWarnings("deprecation") + public static NbtCompound getCustomData(@NotNull ItemStack stack) { + return stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).getNbt(); } /** - * Gets the internal name of the item stack from the {@code ExtraAttributes} NBT tag. + * Gets the Skyblock item id of the item stack. * * @param stack the item stack to get the internal name from * @return an optional containing the internal name of the item stack */ - public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) { - return getExtraAttributesOptional(stack).map(extraAttributes -> extraAttributes.getString(ID)); + public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) { + NbtCompound customData = getCustomData(stack); + return customData.contains(ID) ? Optional.of(customData.getString(ID)) : Optional.empty(); } /** - * Gets the internal name of the item stack from the {@code ExtraAttributes} NBT tag. + * Gets the Skyblock item id of the item stack. * * @param stack the item stack to get the internal name from * @return the internal name of the item stack, or an empty string if the item stack is null or does not have an internal name */ - public static String getItemId(@NotNull ItemStack stack) { - NbtCompound extraAttributes = getExtraAttributes(stack); - return extraAttributes != null ? extraAttributes.getString(ID) : ""; + public static String getItemId(@NotNull ItemStack stack) { + return getCustomData(stack).getString(ID); } /** - * Gets the UUID of the item stack from the {@code ExtraAttributes} NBT tag. + * Gets the UUID of the item stack. * * @param stack the item stack to get the UUID from * @return an optional containing the UUID of the item stack */ - public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) { - return getExtraAttributesOptional(stack).map(extraAttributes -> extraAttributes.getString(UUID)); + public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) { + NbtCompound customData = getCustomData(stack); + return customData.contains(UUID) ? Optional.of(customData.getString(UUID)) : Optional.empty(); } /** - * Gets the UUID of the item stack from the {@code ExtraAttributes} NBT tag. + * Gets the UUID of the item stack. * * @param stack the item stack to get the UUID from * @return the UUID of the item stack, or an empty string if the item stack is null or does not have a UUID */ - public static String getItemUuid(@NotNull ItemStack stack) { - NbtCompound extraAttributes = getExtraAttributes(stack); - return extraAttributes != null ? extraAttributes.getString(UUID) : ""; + public static String getItemUuid(@NotNull ItemStack stack) { + return getCustomData(stack).getString(UUID); } /** @@ -126,47 +127,41 @@ public class ItemUtils { * @param stack the item under the pointer * @return if the item have a "Timestamp" it will be shown formated on the tooltip */ - public static String getTimestamp(ItemStack stack) { - NbtCompound ea = getExtraAttributes(stack); - - if (ea != null && ea.contains("timestamp", NbtElement.LONG_TYPE)) { - Instant date = Instant.ofEpochMilli(ea.getLong("timestamp")); + public static String getTimestamp(ItemStack stack) { + NbtCompound customData = getCustomData(stack); + if (customData != null && customData.contains("timestamp", NbtElement.LONG_TYPE)) { + Instant date = Instant.ofEpochMilli(customData.getLong("timestamp")); return OBTAINED_DATE_FORMATTER.format(date); } - if (ea != null && ea.contains("timestamp", NbtElement.STRING_TYPE)) { - try { - Instant date = OLD_OBTAINED_DATE_FORMAT.parse(ea.getString("timestamp")).toInstant(); - - return OBTAINED_DATE_FORMATTER.format(date); - } catch (ParseException e) { - LOGGER.warn("[Skyblocker Item Utils] Encountered an unknown exception while parsing time stamp of item {} with extra attributes {}", stack, ea, e); - } + if (customData != null && customData.contains("timestamp", NbtElement.STRING_TYPE)) { + TemporalAccessor date = OLD_OBTAINED_DATE_FORMAT.parse(customData.getString("timestamp")); + return OBTAINED_DATE_FORMATTER.format(date); } return ""; } public static boolean hasCustomDurability(@NotNull ItemStack stack) { - NbtCompound extraAttributes = getExtraAttributes(stack); - return extraAttributes != null && (extraAttributes.contains("drill_fuel") || extraAttributes.getString(ID).equals("PICKONIMBUS")); + NbtCompound customData = getCustomData(stack); + return customData != null && (customData.contains("drill_fuel") || customData.getString(ID).equals("PICKONIMBUS")); } @Nullable public static IntIntPair getDurability(@NotNull ItemStack stack) { - NbtCompound extraAttributes = getExtraAttributes(stack); - if (extraAttributes == null) return null; + NbtCompound customData = getCustomData(stack); + if (customData == null) return null; // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level // TODO Cache the max durability and only update the current durability on inventory tick - int pickonimbusDurability = extraAttributes.getInt("pickonimbus_durability"); + int pickonimbusDurability = customData.getInt("pickonimbus_durability"); if (pickonimbusDurability > 0) { return IntIntPair.of(pickonimbusDurability, 5000); } - String drillFuel = Formatting.strip(getNbtTooltip(stack, FUEL_PREDICATE)); + String drillFuel = Formatting.strip(getLoreLineIf(stack, FUEL_PREDICATE)); if (drillFuel != null) { String[] drillFuelStrings = NOT_DURABILITY.matcher(drillFuel).replaceAll("").trim().split("/"); return IntIntPair.of(Integer.parseInt(drillFuelStrings[0]), Integer.parseInt(drillFuelStrings[1]) * 1000); @@ -176,8 +171,8 @@ public class ItemUtils { } @Nullable - public static String getNbtTooltip(ItemStack item, Predicate<String> predicate) { - for (Text line : getNbtTooltips(item)) { + public static String getLoreLineIf(ItemStack item, Predicate<String> predicate) { + for (Text line : getLore(item)) { String string = line.getString(); if (predicate.test(string)) { return string; @@ -188,8 +183,8 @@ public class ItemUtils { } @Nullable - public static Matcher getNbtTooltip(ItemStack item, Pattern pattern) { - for (Text line : getNbtTooltips(item)) { + public static Matcher getLoreLineIfMatch(ItemStack item, Pattern pattern) { + for (Text line : getLore(item)) { String string = line.getString(); Matcher matcher = pattern.matcher(string); if (matcher.matches()) { @@ -200,19 +195,32 @@ public class ItemUtils { return null; } - public static List<Text> getNbtTooltips(ItemStack item) { - NbtCompound displayNbt = item.getSubNbt("display"); - if (displayNbt == null || !displayNbt.contains("Lore", NbtElement.LIST_TYPE)) { - return Collections.emptyList(); - } + public static List<Text> getLore(ItemStack item) { + return item.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines(); + } + + public static PropertyMap propertyMapWithTexture(String textureValue) { + return Codecs.GAME_PROFILE_PROPERTY_MAP.parse(JsonOps.INSTANCE, JsonParser.parseString("[{\"name\":\"textures\",\"value\":\"" + textureValue + "\"}]")).getOrThrow(); + } + + public static String getHeadTexture(ItemStack stack) { + if (!stack.isOf(Items.PLAYER_HEAD) || !stack.contains(DataComponentTypes.PROFILE)) return ""; + + ProfileComponent profile = stack.get(DataComponentTypes.PROFILE); + String texture = profile.properties().get("textures").stream() + .map(Property::value) + .findFirst() + .orElse(""); - return displayNbt.getList("Lore", NbtElement.STRING_TYPE).stream().map(NbtElement::asString).map(Text.Serialization::fromJson).filter(Objects::nonNull).map(text -> Texts.setStyleIfAbsent(text, ItemStackAccessor.getLORE_STYLE())).map(Text.class::cast).toList(); + return texture; } public static ItemStack getSkyblockerStack() { try { - return ItemStack.fromNbt(StringNbtReader.parse("{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;-300151517,-631415889,-1193921967,-1821784279],Properties:{textures:[{Value:\"e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0=\"}]}}}}")); - } catch (CommandSyntaxException e) { + ItemStack stack = new ItemStack(Items.PLAYER_HEAD); + stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of("SkyblockerStack"), Optional.of(java.util.UUID.randomUUID()), propertyMapWithTexture("e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0="))); + return stack; + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java index cda92273..f8716ca4 100644 --- a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java +++ b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java @@ -51,6 +51,10 @@ public class NEURepoManager { ); } + public static boolean isLoading() { + return REPO_LOADING != null && !REPO_LOADING.isDone(); + } + private static CompletableFuture<Boolean> loadRepository() { return CompletableFuture.supplyAsync(() -> { try { @@ -78,7 +82,7 @@ public class NEURepoManager { } private static void deleteAndDownloadRepository(PlayerEntity player) { - if (REPO_LOADING != null && !REPO_LOADING.isDone()) { + if (isLoading()) { sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.loading"))); return; } diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index bbee3ce1..dc13b61d 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -3,6 +3,7 @@ package de.hysky.skyblocker.utils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; @@ -25,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.time.Instant; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -217,7 +219,6 @@ public class Utils { } if (sidebar.isEmpty() && !fabricLoader.isDevelopmentEnvironment()) return; - String string = sidebar.toString(); if (fabricLoader.isDevelopmentEnvironment() || isConnectedToHypixel(client)) { if (!isOnHypixel) { @@ -466,4 +467,17 @@ public class Utils { }); } + + /** + * Used to avoid triggering things like chat rules or chat listeners infinitely, do not use otherwise. + * + * Bypasses MessageHandler#onGameMessage + */ + public static void sendMessageToBypassEvents(Text message) { + MinecraftClient client = MinecraftClient.getInstance(); + + client.inGameHud.getChatHud().addMessage(message); + ((MessageHandlerAccessor) client.getMessageHandler()).invokeAddToChatLog(message, Instant.now()); + client.getNarratorManager().narrateSystemMessage(message); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java index bdff2d94..1f0caff5 100644 --- a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java +++ b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java @@ -15,6 +15,7 @@ import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; @FunctionalInterface public interface ChatMessageListener { @@ -68,7 +69,7 @@ public interface ChatMessageListener { if (!Utils.isOnSkyblock()) { return true; } - ChatFilterResult result = EVENT.invoker().onMessage(message, message.getString()); + ChatFilterResult result = EVENT.invoker().onMessage(message, Formatting.strip(message.getString())); switch (result) { case ACTION_BAR -> { if (overlay) { diff --git a/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java b/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java new file mode 100644 index 00000000..3543a2f1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java @@ -0,0 +1,82 @@ +package de.hysky.skyblocker.utils.datafixer; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import com.mojang.brigadier.StringReader; +import com.mojang.serialization.Dynamic; + +import net.minecraft.command.argument.ItemStringReader; +import net.minecraft.command.argument.ItemStringReader.ItemResult; +import net.minecraft.component.DataComponentType; +import net.minecraft.datafixer.Schemas; +import net.minecraft.datafixer.TypeReferences; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtOps; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryOps; +import net.minecraft.util.Identifier; + +/** + * Contains a data fixer to convert legacy item NBT to the new components system, among other fixers related to the item components system. + * + * @see net.minecraft.datafixer.fix.ItemStackComponentizationFix + */ +public class ItemStackComponentizationFixer { + private static final int ITEM_NBT_DATA_VERSION = 3817; + private static final int ITEM_COMPONENTS_DATA_VERSION = 3825; + private static final DynamicRegistryManager REGISTRY_MANAGER = new DynamicRegistryManager.ImmutableImpl(List.of(Registries.ITEM, Registries.DATA_COMPONENT_TYPE)); + + public static ItemStack fixUpItem(NbtCompound nbt) { + Dynamic<NbtElement> dynamic = Schemas.getFixer().update(TypeReferences.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nbt), ITEM_NBT_DATA_VERSION, ITEM_COMPONENTS_DATA_VERSION); + + return ItemStack.CODEC.parse(dynamic).getOrThrow(); + } + + /** + * Modified version of {@link net.minecraft.command.argument.ItemStackArgument#asString(net.minecraft.registry.RegistryWrapper.WrapperLookup)} to only care about changed components. + * + * @return The {@link ItemStack}'s components as a string which is in the format that the {@code /give} command accepts. + */ + public static String componentsAsString(ItemStack stack) { + RegistryOps<NbtElement> nbtRegistryOps = REGISTRY_MANAGER.getOps(NbtOps.INSTANCE); + + return Arrays.toString(stack.getComponentChanges().entrySet().stream().map(entry -> { + @SuppressWarnings("unchecked") + DataComponentType<Object> dataComponentType = (DataComponentType<Object>) entry.getKey(); + Identifier componentId = Registries.DATA_COMPONENT_TYPE.getId(dataComponentType); + Optional<NbtElement> encodedComponent = dataComponentType.getCodec().encodeStart(nbtRegistryOps, entry.getValue().orElseThrow()).result(); + + if (componentId == null || encodedComponent.isEmpty()) { + return null; + } + + return componentId + "=" + encodedComponent.orElseThrow(); + }).filter(Objects::nonNull).toArray()); + } + + /** + * Constructs an {@link ItemStack} from an {@code itemId}, with item components in string format as returned by {@link #componentsAsString(ItemStack)}, and with a specified stack count. + * + * @return an {@link ItemStack} or {@link ItemStack#EMPTY} if there was an exception thrown. + */ + public static ItemStack fromComponentsString(String itemId, int count, String componentsString) { + ItemStringReader reader = new ItemStringReader(REGISTRY_MANAGER); + + try { + ItemResult result = reader.consume(new StringReader(itemId + componentsString)); + ItemStack stack = new ItemStack(result.item(), count); + + stack.applyComponentsFrom(result.components()); + + return stack; + } catch (Exception ignored) {} + + return ItemStack.EMPTY; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java b/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java index 3fe79e43..d82b4497 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java @@ -1,7 +1,7 @@ package de.hysky.skyblocker.utils.render; -import de.hysky.skyblocker.mixin.accessor.FrustumInvoker; -import de.hysky.skyblocker.mixin.accessor.WorldRendererAccessor; +import de.hysky.skyblocker.mixins.accessors.FrustumInvoker; +import de.hysky.skyblocker.mixins.accessors.WorldRendererAccessor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.Frustum; import net.minecraft.util.math.Box; diff --git a/src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java new file mode 100644 index 00000000..220dbf86 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java @@ -0,0 +1,30 @@ +package de.hysky.skyblocker.utils.render; + +import org.joml.Matrix4f; + +import net.minecraft.client.util.math.MatrixStack; + +/** + * Matrix helper methods + */ +public interface MatrixHelper { + + /** + * Copies the {@code matrix} into a new {@link Matrix4f}. This is necessary otherwise + * any transformations applied will affect other uses of the same matrix. + */ + static Matrix4f copyOf(Matrix4f matrix) { + return new Matrix4f(matrix); + } + + /** + * Creates a blank {@link MatrixStack} and sets it's position matrix to the supplied + * {@code positionMatrix}. + */ + static MatrixStack toStack(Matrix4f positionMatrix) { + MatrixStack matrices = new MatrixStack(); + matrices.peek().getPositionMatrix().set(positionMatrix); + + return matrices; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java index da179d0e..a6772fb2 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -3,8 +3,8 @@ package de.hysky.skyblocker.utils.render; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.logging.LogUtils; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.mixin.accessor.BeaconBlockEntityRendererInvoker; -import de.hysky.skyblocker.mixin.accessor.DrawContextInvoker; +import de.hysky.skyblocker.mixins.accessors.BeaconBlockEntityRendererInvoker; +import de.hysky.skyblocker.mixins.accessors.DrawContextInvoker; import de.hysky.skyblocker.utils.render.culling.OcclusionCulling; import de.hysky.skyblocker.utils.render.title.Title; import de.hysky.skyblocker.utils.render.title.TitleContainer; @@ -26,6 +26,7 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; + import org.joml.Matrix3f; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -243,15 +244,13 @@ public class RenderHelper { } public static void renderQuad(WorldRenderContext context, Vec3d[] points, float[] colorComponents, float alpha, boolean throughWalls) { + Matrix4f positionMatrix = new Matrix4f(); Vec3d camera = context.camera().getPos(); - MatrixStack matrices = context.matrixStack(); - matrices.push(); - matrices.translate(-camera.x, -camera.y, -camera.z); + positionMatrix.translate((float) -camera.x, (float) -camera.y, (float) -camera.z); Tessellator tessellator = RenderSystem.renderThreadTesselator(); BufferBuilder buffer = tessellator.getBuffer(); - Matrix4f positionMatrix = matrices.peek().getPositionMatrix(); RenderSystem.setShader(GameRenderer::getPositionColorProgram); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); @@ -268,8 +267,6 @@ public class RenderHelper { RenderSystem.enableCull(); RenderSystem.depthFunc(GL11.GL_LEQUAL); - - matrices.pop(); } public static void renderText(WorldRenderContext context, Text text, Vec3d pos, boolean throughWalls) { @@ -290,19 +287,18 @@ public class RenderHelper { * @param throughWalls whether the text should be able to be seen through walls or not. */ public static void renderText(WorldRenderContext context, OrderedText text, Vec3d pos, float scale, float yOffset, boolean throughWalls) { - MatrixStack matrices = context.matrixStack(); - Vec3d camera = context.camera().getPos(); + Matrix4f positionMatrix = new Matrix4f(); + Camera camera = context.camera(); + Vec3d cameraPos = camera.getPos(); TextRenderer textRenderer = client.textRenderer; scale *= 0.025f; - matrices.push(); - matrices.translate(pos.getX() - camera.getX(), pos.getY() - camera.getY(), pos.getZ() - camera.getZ()); - matrices.peek().getPositionMatrix().mul(RenderSystem.getModelViewMatrix()); - matrices.multiply(context.camera().getRotation()); - matrices.scale(-scale, -scale, scale); + positionMatrix + .translate((float) (pos.getX() - cameraPos.getX()), (float) (pos.getY() - cameraPos.getY()), (float) (pos.getZ() - cameraPos.getZ())) + .rotate(camera.getRotation()) + .scale(-scale, -scale, scale); - Matrix4f positionMatrix = matrices.peek().getPositionMatrix(); float xOffset = -textRenderer.getWidth(text) / 2f; Tessellator tessellator = RenderSystem.renderThreadTesselator(); @@ -315,7 +311,6 @@ public class RenderHelper { consumers.draw(); RenderSystem.depthFunc(GL11.GL_LEQUAL); - matrices.pop(); } /** diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java index 4f648b8c..ef2e6bf9 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java @@ -1,6 +1,6 @@ package de.hysky.skyblocker.utils.render.gui; -import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor; +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.auction.AuctionHouseScreenHandler; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.entity.player.PlayerInventory; diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java index 80a1ba9d..e2e057b3 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java @@ -1,11 +1,11 @@ package de.hysky.skyblocker.utils.render.gui; import de.hysky.skyblocker.SkyblockerMod; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.item.ItemStack; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; /** @@ -34,9 +34,12 @@ public abstract class ContainerSolver { SkyblockerMod.getInstance().containerSolverManager.markDirty(); } - protected abstract List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots); + protected void onClickSlot(int slot, ItemStack stack, int screenId, String[] groups) { + } + + protected abstract List<ColorHighlight> getColors(String[] groups, Int2ObjectMap<ItemStack> slots); - protected void trimEdges(Map<Integer, ItemStack> slots, int rows) { + protected void trimEdges(Int2ObjectMap<ItemStack> slots, int rows) { for (int i = 0; i < rows; i++) { slots.remove(9 * i); slots.remove(9 * i + 8); diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java index e5b2dddb..b37c57a4 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java @@ -1,7 +1,8 @@ package de.hysky.skyblocker.utils.render.gui; import com.mojang.blaze3d.systems.RenderSystem; -import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor; + +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakeBagHelper; import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakesHelper; import de.hysky.skyblocker.skyblock.dungeon.CroesusHelper; @@ -13,6 +14,8 @@ import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver; import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver; import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver; import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; @@ -22,8 +25,6 @@ import net.minecraft.screen.slot.Slot; import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -36,6 +37,10 @@ public class ContainerSolverManager { private ContainerSolver currentSolver = null; private String[] groups; private List<ColorHighlight> highlights; + /** + * Useful for keeping track of a solver's state in a Screen instance, such as if Hypixel closes & reopens a screen after every click (as they do with terminals). + */ + private int screenId = 0; public ContainerSolverManager() { solvers = new ContainerSolver[]{ @@ -82,6 +87,7 @@ public class ContainerSolverManager { matcher.usePattern(solver.getName()); matcher.reset(); if (matcher.matches()) { + ++screenId; currentSolver = solver; groups = new String[matcher.groupCount()]; for (int i = 0; i < groups.length; i++) { @@ -89,6 +95,7 @@ public class ContainerSolverManager { } currentSolver.start(screen); markDirty(); + return; } } @@ -107,6 +114,12 @@ public class ContainerSolverManager { highlights = null; } + public void onSlotClick(int slot, ItemStack stack) { + if (currentSolver != null) { + currentSolver.onClickSlot(slot, stack, screenId, groups); + } + } + public void onDraw(DrawContext context, List<Slot> slots) { if (currentSolver == null) return; @@ -122,8 +135,8 @@ public class ContainerSolverManager { RenderSystem.colorMask(true, true, true, true); } - private Map<Integer, ItemStack> slotMap(List<Slot> slots) { - Map<Integer, ItemStack> slotMap = new TreeMap<>(); + private Int2ObjectMap<ItemStack> slotMap(List<Slot> slots) { + Int2ObjectMap<ItemStack> slotMap = new Int2ObjectRBTreeMap<>(); for (int i = 0; i < slots.size(); i++) { slotMap.put(i, slots.get(i).getStack()); } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java index 487e3d8b..a115bb2b 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java @@ -2,10 +2,10 @@ package de.hysky.skyblocker.utils.render.title; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.HudRenderEvents; import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -26,7 +26,7 @@ public class TitleContainer { private static final Set<Title> titles = new LinkedHashSet<>(); public static void init() { - HudRenderCallback.EVENT.register(TitleContainer::render); + HudRenderEvents.BEFORE_CHAT.register(TitleContainer::render); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker") .then(ClientCommandManager.literal("hud") .then(ClientCommandManager.literal("titleContainer") @@ -148,7 +148,7 @@ public class TitleContainer { //Translate the matrix to the texts position and scale context.getMatrices().push(); - context.getMatrices().translate(title.x, title.y, 200); + context.getMatrices().translate(title.x, title.y, 0); context.getMatrices().scale(scale, scale, scale); //Draw text diff --git a/src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java b/src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java new file mode 100644 index 00000000..bd9200ad --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java @@ -0,0 +1,5 @@ +package de.hysky.skyblocker.utils.tictactoe; + +public record BoardIndex(int row, int column) { + +} diff --git a/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java b/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java index fde18f27..0725b2ce 100644 --- a/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java @@ -1,105 +1,119 @@ package de.hysky.skyblocker.utils.tictactoe; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.Comparator; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; public class TicTacToeUtils { - public static int getBestMove(char[][] board) { - HashMap<Integer, Integer> moves = new HashMap<>(); - for (int row = 0; row < board.length; row++) { - for (int col = 0; col < board[row].length; col++) { - if (board[row][col] != '\0') continue; - board[row][col] = 'O'; - int score = alphabeta(board, Integer.MIN_VALUE, Integer.MAX_VALUE, false, 0); - board[row][col] = '\0'; - moves.put(row * 3 + col + 1, score); - } - } - return Collections.max(moves.entrySet(), Map.Entry.comparingByValue()).getKey(); - } - - private static boolean hasMovesLeft(char[][] board) { - for (char[] rows : board) { - for (char col : rows) { - if (col == '\0') return true; - } - } - return false; - } - - private static int getBoardScore(char[][] board) { - for (int row = 0; row < 3; row++) { - if (board[row][0] == board[row][1] && board[row][0] == board[row][2]) { - if (board[row][0] == 'X') { - return -10; - } else if (board[row][0] == 'O') { - return 10; - } - } - } - - for (int col = 0; col < 3; col++) { - if (board[0][col] == board[1][col] && board[0][col] == board[2][col]) { - if (board[0][col] == 'X') { - return -10; - } else if (board[0][col] == 'O') { - return 10; - } - } - } - - if (board[0][0] == board[1][1] && board[0][0] == board[2][2]) { - if (board[0][0] == 'X') { - return -10; - } else if (board[0][0] == 'O') { - return 10; - } - } else if (board[0][2] == board[1][1] && board[0][2] == board[2][0]) { - if (board[0][2] == 'X') { - return -10; - } else if (board[0][2] == 'O') { - return 10; - } - } - - return 0; - } - - private static int alphabeta(char[][] board, int alpha, int beta, boolean max, int depth) { - int score = getBoardScore(board); - if (score == 10 || score == -10) return score; - if (!hasMovesLeft(board)) return 0; - - if (max) { - int bestScore = Integer.MIN_VALUE; - for (int row = 0; row < 3; row++) { - for (int col = 0; col < 3; col++) { - if (board[row][col] == '\0') { - board[row][col] = 'O'; - bestScore = Math.max(bestScore, alphabeta(board, alpha, beta, false, depth + 1)); - board[row][col] = '\0'; - alpha = Math.max(alpha, bestScore); - if (beta <= alpha) break; // Pruning - } - } - } - return bestScore - depth; - } else { - int bestScore = Integer.MAX_VALUE; - for (int row = 0; row < 3; row++) { - for (int col = 0; col < 3; col++) { - if (board[row][col] == '\0') { - board[row][col] = 'X'; - bestScore = Math.min(bestScore, alphabeta(board, alpha, beta, true, depth + 1)); - board[row][col] = '\0'; - beta = Math.min(beta, bestScore); - if (beta <= alpha) break; // Pruning - } - } - } - return bestScore + depth; - } - } + public static BoardIndex getBestMove(char[][] board) { + Object2IntOpenHashMap<BoardIndex> moves = new Object2IntOpenHashMap<>(); + + for (int row = 0; row < board.length; row++) { + for (int column = 0; column < board[row].length; column++) { + // Simulate the move as O if the square is empty to determine a solution + if (board[row][column] != '\0') continue; + board[row][column] = 'O'; + int score = alphabeta(board, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, false); + board[row][column] = '\0'; + + moves.put(new BoardIndex(row, column), score); + } + } + + return Collections.max(moves.object2IntEntrySet(), Comparator.comparingInt(Object2IntMap.Entry::getIntValue)).getKey(); + } + + private static boolean hasMovesAvailable(char[][] board) { + return Arrays.stream(board).flatMap(row -> Stream.of(row[0], row[1], row[2])).anyMatch(c -> c == '\0'); + } + + private static int getScore(char[][] board) { + // Check if X or O has won horizontally + for (int row = 0; row < 3; row++) { + if (board[row][0] == board[row][1] && board[row][0] == board[row][2]) { + switch (board[row][0]) { + case 'X': return -10; + case 'O': return 10; + } + } + } + + // Check if X or O has won vertically + for (int column = 0; column < 3; column++) { + if (board[0][column] == board[1][column] && board[0][column] == board[2][column]) { + switch (board[0][column]) { + case 'X': return -10; + case 'O': return 10; + } + } + } + + // Check if X or O has won diagonally + // Top left to bottom right + if (board[0][0] == board[1][1] && board[0][0] == board[2][2]) { + switch (board[0][0]) { + case 'X': return -10; + case 'O': return 10; + } + } + + // Top right to bottom left + if (board[0][2] == board[1][1] && board[0][2] == board[2][0]) { + switch (board[0][2]) { + case 'X': return -10; + case 'O': return 10; + } + } + + return 0; + } + + private static int alphabeta(char[][] board, int alpha, int beta, int depth, boolean maximizePlayer) { + int score = getScore(board); + + if (score == 10 || score == -10) return score; + if (!hasMovesAvailable(board)) return 0; + + if (maximizePlayer) { + int bestScore = Integer.MIN_VALUE; + + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 3; column++) { + if (board[row][column] == '\0') { + board[row][column] = 'O'; + bestScore = Math.max(bestScore, alphabeta(board, alpha, beta, depth + 1, false)); + board[row][column] = '\0'; + alpha = Math.max(alpha, bestScore); + + //Is this correct? Well the algorithm seems to solve it so I will assume it is + if (beta <= alpha) break; // Pruning + } + } + } + + return bestScore - depth; + } else { + int bestScore = Integer.MAX_VALUE; + + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 3; column++) { + if (board[row][column] == '\0') { + board[row][column] = 'X'; + bestScore = Math.min(bestScore, alphabeta(board, alpha, beta, depth + 1, true)); + board[row][column] = '\0'; + beta = Math.min(beta, bestScore); + + if (beta <= alpha) break; // Pruning + } + } + } + + return bestScore + depth; + } + } }
\ No newline at end of file |