From b84dd664e4656ee0c8fd82fc1caa10cbbccf6c21 Mon Sep 17 00:00:00 2001 From: Peyton Brown <81496880+PeytonBrown@users.noreply.github.com> Date: Tue, 10 Jun 2025 04:50:11 -0400 Subject: Add waypoints to forest nodes (#1325) * Initial Implementation * Remove waypoint if block is right clicked * Changed to HIGHLIGHT waypoint type * Remove from list if not seen for 5 seconds * resolve pr comments --- .../config/categories/ForagingCategory.java | 17 ++- .../skyblocker/config/configs/ForagingConfig.java | 8 + .../mixins/ClientPlayNetworkHandlerMixin.java | 2 + .../skyblocker/skyblock/galatea/ForestNodes.java | 168 +++++++++++++++++++++ 4 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/galatea/ForestNodes.java (limited to 'src/main/java/de') diff --git a/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java index d5c844b7..81e64ea4 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java @@ -1,7 +1,10 @@ package de.hysky.skyblocker.config.categories; +import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import dev.isxander.yacl3.api.ConfigCategory; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.OptionGroup; import net.minecraft.text.Text; public class ForagingCategory { @@ -9,9 +12,21 @@ public class ForagingCategory { public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig config) { return ConfigCategory.createBuilder() .name(Text.translatable("skyblocker.config.foraging")) - //Modern Foraging island + //Galatea + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.foraging.galatea")) + .collapsed(false) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.foraging.galatea.enableForestNodeHelper")) + .binding(defaults.foraging.galatea.enableForestNodeHelper, + () -> config.foraging.galatea.enableForestNodeHelper, + newValue -> config.foraging.galatea.enableForestNodeHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) + //Hunting - YACL doesn't like empty option groups /*.group(OptionGroup.createBuilder() .name(Text.translatable("skyblocker.config.foraging.hunting")) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/ForagingConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/ForagingConfig.java index 1a7c3598..aa4dcc6d 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/ForagingConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/ForagingConfig.java @@ -4,9 +4,17 @@ import dev.isxander.yacl3.config.v2.api.SerialEntry; public class ForagingConfig { + @SerialEntry + public Galatea galatea = new Galatea(); + @SerialEntry public Hunting hunting = new Hunting(); + public static class Galatea { + @SerialEntry + public boolean enableForestNodeHelper = true; + } + public static class Hunting { } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 6372bd4d..22e921ff 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -24,6 +24,7 @@ import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.fishing.FishingHelper; import de.hysky.skyblocker.skyblock.fishing.FishingHookDisplayHelper; import de.hysky.skyblocker.skyblock.fishing.SeaCreatureTracker; +import de.hysky.skyblocker.skyblock.galatea.ForestNodes; import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.skyblock.slayers.boss.demonlord.FirePillarAnnouncer; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; @@ -186,6 +187,7 @@ public abstract class ClientPlayNetworkHandlerMixin extends ClientCommonNetworkH DojoManager.onParticle(packet); CrystalsChestHighlighter.onParticle(packet); EnderNodes.onParticle(packet); + ForestNodes.onParticle(packet); WishingCompassSolver.onParticle(packet); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/galatea/ForestNodes.java b/src/main/java/de/hysky/skyblocker/skyblock/galatea/ForestNodes.java new file mode 100644 index 00000000..01819f47 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/galatea/ForestNodes.java @@ -0,0 +1,168 @@ +package de.hysky.skyblocker.skyblock.galatea; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import de.hysky.skyblocker.utils.waypoint.Waypoint; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.api.event.player.AttackBlockCallback; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; +import net.minecraft.particle.ParticleType; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.util.ActionResult; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class ForestNodes { + private static final Logger LOGGER = LoggerFactory.getLogger(ForestNodes.class); + private static final MinecraftClient client = MinecraftClient.getInstance(); + private static final Map forestNodes = new HashMap<>(); + + @Init + public static void init() { + Scheduler.INSTANCE.scheduleCyclic(ForestNodes::update, 20); + WorldRenderEvents.AFTER_TRANSLUCENT.register(ForestNodes::render); + AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> { + if (!shouldProcess()) { + return ActionResult.PASS; + } + forestNodes.remove(pos); + return ActionResult.PASS; + }); + UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { + if (!shouldProcess()) { + return ActionResult.PASS; + } + BlockPos pos = hitResult.getBlockPos(); + forestNodes.remove(pos); + return ActionResult.PASS; + }); + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset()); + } + + public static void onParticle(ParticleS2CPacket packet) { + if (!shouldProcess()) { + return; + } + + ParticleType particleType = packet.getParameters().getType(); + if (!ParticleTypes.HAPPY_VILLAGER.getType().equals(particleType)) { + return; + } + + double x = packet.getX(); + double y = packet.getY() - 1; + double z = packet.getZ(); + BlockPos pos = BlockPos.ofFloored(x, y, z); + + // Check for three ItemDisplayEntity with minecraft:string in the same block + if (client.world != null) { + int stringItemCount = countStringItemDisplays(pos); + if (stringItemCount == 3) { + forestNodes.computeIfAbsent(pos, ForestNode::new); + } + } + } + + private static int countStringItemDisplays(BlockPos pos) { + ClientWorld world = client.world; + if (world == null) { + return 0; + } + + // Get all ItemDisplayEntity within the same block + List entities = world.getEntitiesByClass( + DisplayEntity.ItemDisplayEntity.class, + Box.of(pos.toCenterPos(), 1.0, 1.0, 1.0), + entity -> true + ); + + // Count those with minecraft:string + return (int) entities.stream() + .filter(entity -> { + ItemStack stack = entity.getItemStack(); + return !stack.isEmpty() && stack.getItem().equals(Items.STRING); + }) + .count(); + } + + private static void update() { + if (!shouldProcess()) { + return; + } + Iterator> iterator = forestNodes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + ForestNode forestNode = entry.getValue(); + forestNode.updateWaypoint(); + if (!forestNode.shouldRender()) { + iterator.remove(); + } + } + } + + private static void render(WorldRenderContext context) { + if (!shouldProcess()) { + return; + } + for (ForestNode forestNode : forestNodes.values()) { + if (forestNode.shouldRender()) { + forestNode.render(context); + } + } + } + + private static boolean shouldProcess() { + return SkyblockerConfigManager.get().foraging.galatea.enableForestNodeHelper && Utils.isInGalatea(); + } + + private static void reset() { + forestNodes.clear(); + } + + public static class ForestNode extends Waypoint { + private long lastConfirmed; + + private ForestNode(BlockPos pos) { + super(pos, + () -> Type.HIGHLIGHT, + ColorUtils.getFloatComponents(DyeColor.ORANGE), + DEFAULT_HIGHLIGHT_ALPHA, + DEFAULT_LINE_WIDTH, + false + ); + this.lastConfirmed = System.currentTimeMillis(); + } + + private void updateWaypoint() { + long currentTimeMillis = System.currentTimeMillis(); + if (lastConfirmed + 2000 > currentTimeMillis || client.world == null) { + return; + } + int stringItemCount = countStringItemDisplays(pos); + if (stringItemCount == 3) { + lastConfirmed = System.currentTimeMillis(); + } + } + + @Override + public boolean shouldRender() { + return super.shouldRender() && lastConfirmed + 5000 > System.currentTimeMillis(); + } + } +} -- cgit